From 9c7684f1b2428640693eca4635ad3b9ff8d9c818 Mon Sep 17 00:00:00 2001 From: library-manager Date: Fri, 10 Jul 2026 07:57:00 +0000 Subject: [PATCH] ESD: add Peak Power(W) param (template v2, skill v2) --- SKILL.md | 423 ++++++++--------- assets/template/CHANGELOG.md | 7 + assets/template/template.xlsx | Bin 25393 -> 110076 bytes assets/template/versions.json | 502 +++++++++++++++++++++ config/gitea.env | 15 +- references/taxonomy.md | 5 +- scripts/__pycache__/common.cpython-311.pyc | Bin 0 -> 10610 bytes scripts/altium_refs.py | 169 +++++++ scripts/append_parameter.py | 120 ++++- scripts/common.py | 163 +++++++ scripts/fill_templates.py | 214 ++++----- scripts/gitea_components.py | 227 ++++++++++ scripts/push_to_gitea.sh | 4 +- 13 files changed, 1479 insertions(+), 370 deletions(-) create mode 100644 assets/template/CHANGELOG.md create mode 100644 assets/template/versions.json create mode 100644 scripts/__pycache__/common.cpython-311.pyc create mode 100644 scripts/altium_refs.py create mode 100644 scripts/common.py create mode 100644 scripts/gitea_components.py diff --git a/SKILL.md b/SKILL.md index 63181c7..b69b961 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,270 +1,271 @@ --- -name: datasheet-extractor +name: library-manager description: >- - Extract key parameters from component datasheet PDFs into the Vecmocon master - template (18 component types incl. resistors, capacitors, inductors, diodes, - transistors, ICs, protection, power modules, connectors, sensors, and more). - Column A = MPN_make_typeid (make = first word of manufacturer, typeid from the - taxonomy). Writes ONE master workbook (Components_Master.xlsx) with sheets only for the - types whose datasheets were provided, plus a Meta sheet at the end (template version, - total components, date, time), assembles - a per-part DFS folder (datasheet, Altium footprint, Altium symbol), and pushes - design files to the Gitea DFS repo, Excel outputs to the Parameters repo, and skill - files plus templates to the Skill_Assets repo. Also appends new parameters to a - template as a new version. Use WHENEVER the user uploads component datasheets and - asks to extract parameters, build the parameter sheets or component library, or push - datasheets to Gitea. ALWAYS trigger when the user types "\datasheet". + Manage Vecmocon's component library: extract parameters from a component datasheet PDF into + the per-typeid Excel template (125 type-IDs across 18 classes - resistors, capacitors, + diodes, transistors, ICs, connectors, sensors, etc.). The user gives a datasheet named + as its MPN plus the make. The skill checks Gitea for a duplicate MPN_make and hard-stops if + present, classifies the part to a typeid, confirms or updates that typeid's template + (per-typeid version, changelog, backfill), fills a per-part workbook, loops on human + verification, then takes the Altium symbol (.SchLib) and footprint (.PcbLib), fills the + Library/Footprint columns, assembles a part folder (xlsx, datasheet, symbol, footprint) and + pushes it to the components repo under its Class. Use WHENEVER the user uploads a component + datasheet, builds a library entry, adds a parameter to a type template, or pushes a part to + Gitea. ALWAYS trigger on "\datasheet", "\library", or "\library-manager", or any + component-library / datasheet-extraction task. --- -# Datasheet Extractor +# Library Manager -Read component datasheets, fill the customer's master template, assemble each part's -design-file folder, and publish everything to three Gitea repos. Be careful and honest: -put every value in the right column and unit, and flag what a datasheet does not state. - -**Pushing is the default.** At the end of a run, push every output to Gitea without asking -for confirmation — that's the whole job. Only pause when there's a genuine need for the -user: a **login wall** while fetching a footprint/symbol, or an **MPN that already exists** -in Gitea (discard vs replace, because replace overwrites data). Nothing else warrants a -prompt. +Turn one component datasheet into a verified, versioned library entry in Gitea. The guiding +idea is honesty and traceability: every value lands in the right column and unit, anything +the datasheet doesn't state stays blank, and nothing reaches Gitea until a human has +confirmed it. This is an **interactive** skill — it asks at the few points where a person's +judgement or an upload is genuinely needed, and does everything else on its own. ## Inputs -- **Datasheet PDFs**, one per part. The filename is the part's MPN (search that exact - number inside a series datasheet to read the right variant). -- No project/version is needed — repos use a **flat layout**. +- **A datasheet PDF whose filename is the MPN** (e.g. `BAT46WJ.pdf`). If it's a series + datasheet, search that exact MPN inside to read the correct variant. +- **The make** (manufacturer), given by the user. The `make` tag is the first word of the + manufacturer, alphanumerics only (Texas Instruments → `Texas`, Nexperia → `Nexperia`). +- **Later in the flow**, after verification: an Altium **symbol** (`.SchLib`) and + **footprint** (`.PcbLib`) file, provided by the user. ## The identifier: `MPN_make_typeid` -Column A of every sheet (header `MPN_make_type`) and each DFS folder name is: +Every part folder, every per-part workbook, and column A of every sheet (`MPN_make_type`) +use the same tag: ``` __ e.g. BAT46WJ_Nexperia_SCH ``` -- **make** = the **first word of the manufacturer** name (Texas Instruments → `Texas`, - Nexperia → `Nexperia`, STMicroelectronics → `STMicroelectronics`). -- **typeid** = the Type ID for the part's subclass, from the taxonomy in - `references/taxonomy.md` (full source `assets/template/Type_ID.xlsx`). e.g. Schottky → - `SCH`, TVS → `TVS`, MOSFET → `MOS`, LDO → `LDO`, Common-mode choke → `CMC`. +`typeid` is the part's type-ID code from the taxonomy (`references/taxonomy.md`, full source +`assets/template/Type_ID.xlsx`) — Schottky → `SCH`, MOSFET → `MOS`, LDO → `LDO`. In the new +template **each typeid is its own sheet** (125 of them). The broader **Class** (Diode, +Transistor, IC …) is used only to organise the components repo into top-level folders. + +## Gitea layout (two repos) + +``` +skill repo/ this skill's own files (updated versions land here too) + +components repo/ + / e.g. Diode, IC, Transistor, Resistor, ... + __/ e.g. BAT46WJ_Nexperia_SCH + __.xlsx this part's own one-row parameter sheet + _data.pdf the datasheet + .SchLib user-provided + .PcbLib user-provided +``` + +There is **no single master workbook** — each part carries its own sheet inside its folder. +Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `COMPONENTS_REPO`), so runs +need no per-session token. If the host is unreachable, the git steps fail clearly and write +nothing. ## Workflow -1. **Read each datasheet.** Get MPN (from filename), manufacturer, and the parameters. -2. **Classify** to one of the 18 **types** (= template sheet names) and to a **subclass** - from `references/taxonomy.md`; record its **typeid**. The subclass name goes in the - `Class` column where the sheet has one. -3. **Extract values** into that type's columns (headers come from - `assets/template/template.xlsx`). Convert to each header's unit; leave blank if the - datasheet doesn't state it (an honest blank beats a guess). -4. **Build the master workbook** with `scripts/fill_templates.py` — a single - `Components_Master.xlsx` that mirrors the template but keeps **only the sheets for the - types you actually extracted** (the components whose datasheets were given), each with - column A = `MPN_make_typeid`, and a single **Meta** sheet appended at the end. (See - Producing outputs.) -5. **Assemble each part's DFS folder** `MPN_make_typeid/` containing `MPN_data` (the - datasheet), `MPN_fp` (footprint), `MPN_sym` (symbol). See Footprint & symbol. -6. **Push everything to Gitea — automatically.** At the end of a run, push all outputs to - all three repos with no confirmation step: DFS folders → **DFS**, per-type sheets → - **Parameters** (merged in), skill files + templates → **Skill_Assets**. The only reasons - to pause are a login wall or an already-present MPN (discard vs replace); otherwise it - all just goes. See Pushing to Gitea. +Run these in order. Each `python`/`bash` command is a helper in `scripts/`. -## Producing outputs +### 1. Duplicate check first — before any real work -Collect what you extracted into `parts.json` (keys match template headers; `typeid` and -`subclass` from the taxonomy; `manufacturer` used for the make tag): +The part's presence is keyed on **MPN + make** (typeid not known yet). If it already exists, +stop; re-doing an existing part would only risk overwriting good data. + +```bash +python scripts/gitea_components.py check-mpn --mpn --make +``` + +`EXISTS …` (exit 3) → **stop and tell the user the part is already present in Gitea. End +here.** `ABSENT` (exit 0) → continue. + +### 2. Classify → typeid (and its Class) + +Read the datasheet, identify the part, and match it to the closest subclass in +`references/taxonomy.md`; record its **typeid** (= the template sheet name). The **Class** +(for the components-repo folder) comes from the same taxonomy row — +`scripts/common.py:class_folder(typeid)` returns it (e.g. `SCH` → `Diode`). + +### 3. Confirm the typeid's template (and add parameters if asked) + +Check whether that typeid has a sheet in `assets/template/template.xlsx`. + +- **No sheet for this typeid** → ask the user to upload the template sheet for it. Add it to + `assets/template/template.xlsx`, then push the updated skill files to the skill repo (see + *Pushing the skill repo*). Then continue. +- **Sheet exists** → print **all** of that sheet's parameters (its column headers) in the + chat and ask the user whether any new parameters should be added. + - **No** → go to step 4. + - **Yes** → collect the new parameter name(s), then: + + ```bash + python scripts/append_parameter.py --typeid \ + --param "New Parameter(unit)" [--param "Another(unit)"] \ + --desc "why these were added" + ``` + + This appends the column(s) at the end of that typeid's sheet, **bumps that typeid's + Template Version and Skill Version together** (v1→v2 — see *Per-typeid versioning*), + and writes one entry to the global changelog `assets/template/CHANGELOG.md`. Push the + updated `template.xlsx` + `versions.json` + `CHANGELOG.md` to the **skill** repo, and + make sure the components repo's copies stay consistent. + + Then ask: **should this change apply to the parts of this typeid already in Gitea?** + - **No** → go to step 4 (only the current part gets the new column). + - **Yes → backfill** (see *Backfilling existing parts*), then tell the user the previous + sheets were updated, and go to step 4. + +### 4. Extract and fill the per-part workbook + +Read every parameter the datasheet actually states into that typeid's columns, converting to +each header's unit. **Leave blanks where the datasheet is silent — an honest blank beats a +guess.** Collect them into a small `part.json`: ```json -{"parts":[ - {"type":"Diode","mpn":"BAT46WJ","manufacturer":"Nexperia","typeid":"SCH","subclass":"Schottky", - "values":{"Description":"100 V 250 mA Schottky, SOD323F","Forward Voltage(V)":"0.71","Package":"SOD323F"}} -]} +{"mpn":"BAT46WJ","manufacturer":"Nexperia","typeid":"SCH", + "values":{"Description":"100 V 250 mA Schottky, SOD323F","Forward Voltage(V)":"0.71", + "Reverse Voltage(V)":"100","Forward Current(A)":"0.25","Package":"SOD323F"}} ``` ```bash -python scripts/fill_templates.py parts.json \ - --template assets/template/template.xlsx --dest +python scripts/fill_templates.py part.json \ + --template assets/template/template.xlsx --dest // ``` -Produces a **single** `Components_Master.xlsx`. It starts from a copy of the reference -template (so every kept sheet keeps its exact headers, styling, widths and freeze), fills -this run's parts into the matching sheet (column A = `MPN_make_typeid`), then **drops every -type sheet with no parts** — so the file holds only the components whose datasheets were -provided. A single **Meta** sheet is appended **at the end** with: Template Version, Total -Components, Date, Time, and a per-sheet count breakdown. Deliver this one file to the user; -the Parameters repo stores the same master, accumulated across runs (see Pushing to Gitea). +This writes `.xlsx` — just that typeid's sheet, one row — with column A = the tag, +**Skill Version (col B)** and **Template Version (col C)** stamped from this typeid's current +versions, and the four design columns left blank for now. -Sheet naming follows the template (Diode, Resistor, …), not `Sheet1/Sheet2`. If the user -supplies a new reference template, drop it in at `assets/template/template.xlsx` and the -master mirrors whatever sheets/headers it has. +### 5. Human verification loop -## Footprint & symbol (for the DFS folder) +Deliver the filled workbook to the user and ask them to verify it. If they report an error +or say it isn't right, **go back to step 4, re-read the datasheet more carefully, re-fill, +and hand it back.** Repeat until the user confirms it's verified. Nothing is pushed until +this passes — the engineer is the ground truth for the numbers. -For each part, the DFS folder needs `MPN_fp` (footprint) and `MPN_sym` (symbol) beside -`MPN_data` (the datasheet). **Deliver these as Altium files** — an Altium PCB footprint -(`.PcbLib`) and schematic symbol (`.SchLib`), or a single integrated library (`.IntLib`) -that carries both — because that's what the design team actually consumes. Get them in -this order: +### 6. Symbol + footprint → the design columns -1. **Auto-download via the user's pre-logged-in Chrome** (`scripts/fetch_cad.py`). The user - keeps signed-in CAD-site windows open; the script attaches to that running Chrome and - reuses those live sessions — it never logs in, and no password is ever handled. One-time - setup: the user quits Chrome, relaunches it with `--remote-debugging-port=9222 - --user-data-dir="$HOME/cad-chrome"`, and signs into the sites. Then, per part: - - ```bash - python scripts/fetch_cad.py --mpn BAT46WJ --dest \ - --sites snapeda,ultralibrarian,componentsearchengine,digikey - ``` - - It tries each site in order, downloads the Altium model, and drops it into the DFS folder - named `MPN_fp` / `MPN_sym` (or `MPN_cad` for a bundle). **It never solves CAPTCHAs or - defeats bot-detection** — if a site shows a login wall, CAPTCHA, or bot-check, that - adapter stops and reports `manual` with the URL. Note: this runs on the *user's* machine - (where the browsers are), and some sites' terms restrict automated downloads — treat it - as a convenience with the manual fallback below. - -2. **Manual fallback** (script returned `manual`/`notfound`, or no browser session): **share - the user the direct link** where the model lives and ask them to download it and send it - back; then attach it into the DFS folder with the agreed naming (`MPN_fp` / `MPN_sym`). - Never type, store, or ask for their password, and never write credentials into this skill - — it's pushed to Skill_Assets, and a secret in a repo is a real leak (same reason the - `GIT_TOKEN` is blanked before syncing). - - Whichever path, once you have a candidate **cross-check** its pad/pin count and key - package dimensions against the datasheet's package drawing before trusting it — library - models are sometimes wrong, or drawn for a different variant of the series. - -3. **If no good model exists, generate the footprint/symbol yourself** as Altium files - from the datasheet's package drawing and pinout — but only when you can do it reliably - — then **ask the user to verify** it against the datasheet before it's trusted, since a - generated land pattern that's slightly off is worse than an obvious blank. - -4. **If none of that is possible**, still create the folder with the datasheet and **tell - the user that MPN needs a manual footprint/symbol build**, leaving a clearly-named - placeholder or listing it so nothing silently goes missing. - -Keep the datasheet's extension on `MPN_data` (e.g. `BAT46WJ_data.pdf`). Name the -footprint/symbol files `MPN_fp` / `MPN_sym` with the Altium extension the model uses -(`.PcbLib` / `.SchLib`, or a single `.IntLib` when the source bundles both). - -## Pushing to Gitea - -Connection + repos are pre-configured in `config/gitea.env` (host, user, token, and the -three repos), so runs need no per-session token. Layout is **flat** (no project/version). - -**Every run ends by pushing all three repos — no "shall I push?" step.** The user has -already asked for this to be automatic; don't re-ask. The reconcile step self-guards -against the one risky case (overwriting an existing part), so a straight push is safe by -default. - -The three repos split into two kinds, and they're pushed differently: - -- **DFS** and **Parameters** are **MPN-indexed** — DFS has one `MPN_make_typeid/` folder - per part; Parameters holds a **single master workbook** (`Components_Master.xlsx`) whose - type sheets each have one row per part (keyed on column A). Because a re-run can collide - with a part that's already there, these go through `scripts/gitea_reconcile.py`, which is - dedup-aware and **merge-safe** (it never blows away parts it isn't touching). See - *Handling parts already in Gitea* below. -- **Skill_Assets** holds the skill itself + templates — it is **not** per-part, so there's - nothing MPN-level to reconcile. Push it with `scripts/push_to_gitea.sh` as before. +Once verified, ask the user to upload the **symbol (`.SchLib`)** and **footprint +(`.PcbLib`)** files. Copy them into the staging part folder **under their proper names** (so +the Path columns match the files that actually get stored — strip any upload-staging prefix +the environment may have added), then read all four design values in one shot: ```bash -# Skill files + templates -> Skill_Assets (flat copy; no dedup needed) -bash scripts/push_to_gitea.sh --repo "$SKILL_ASSETS_REPO" --src --message "Sync skill assets" +cp //.SchLib +cp //.PcbLib +python scripts/altium_refs.py design \ + --symbol //.SchLib \ + --footprint //.PcbLib > design.json ``` -The push script clones the repo, copies the source contents in (flat), commits, and -pushes; the repo must already exist. If the host is unreachable (e.g. Cowork without the -domain allowlisted) it fails clearly and leaves the staged files for a manual push. +This produces (verified against real Ultra-Librarian exports): -**Push the skill files organised** to Skill_Assets: keep the skill's own structure -(`SKILL.md`, `scripts/`, `assets/`, `references/`, `config/`) — but do **not** push the -real token. Before syncing skill assets, blank the `GIT_TOKEN` line in the copy you push, -or push everything except `config/gitea.env`. +- **Library Ref** = the component name **inside** the `.SchLib` (e.g. `CGA3E3X7R1H474K080AE`) +- **Library Path** = the `.SchLib` file name +- **Footprint Ref** = the **base** pattern inside the `.PcbLib`; Altium ships IPC density + variants (`-L` / `-M` / `-N`) alongside the base, and the base is the one used (e.g. + `CAP_CGA3_TDK`, not `CAP_CGA3_TDK-L`) +- **Footprint Path** = the `.PcbLib` file name -> Why not push Parameters with the flat script? `fill_templates.py` writes a master -> workbook holding **only this run's** rows. Copied flat over the repo, it would overwrite -> the stored master and delete every previously-stored part. `gitea_reconcile.py` opens the -> repo's master, merges this run's rows into the right sheets, and writes it back — so only -> the parts you decided on change, and the Meta sheet (totals, date, time) refreshes. - -## Handling parts already in Gitea (discard vs replace) - -A part's identity is its tag `MPN_make_typeid` (same tag = same part). A re-run that -brings in a *new* MPN is not a conflict — it just gets added, and you push without asking. -The prompt only exists for the case where an MPN **already lives in DFS/Parameters**, -because "replace" overwrites data that's already on the server. - -The simplest path is a single push. It clones the repos, adds everything new, and stops -**only** if it hits an existing MPN it can't resolve on its own: +These Ref names come from *inside* the files and can differ from the MPN or filename. If a Ref +comes back `null` (or a footprint shows several unrelated candidates), ask the user to confirm +the name from Altium's properties and edit `design.json`. Then re-fill so the columns land: ```bash -python scripts/gitea_reconcile.py --parts parts.json --dfs-src \ - --template assets/template/template.xlsx --push +python scripts/fill_templates.py part.json \ + --template assets/template/template.xlsx --dest // --design design.json ``` -- **No existing MPNs** → it adds all the folders/rows and pushes. No prompt. Done. -- **An MPN already exists** → it refuses (listing which) so you can ask the user, per MPN, - **discard** (keep what's in Gitea, drop the new copy) or **replace** (overwrite that MPN's - DFS folder *and* its Parameters row with the fresh extraction). Every other folder/row is - always left alone — the Parameters sheet is merged, never wholesale-overwritten. +### 7. Assemble the part folder -Feed the user's answers back as a small JSON map (`tag → replace|discard`): +The staging folder `/` should now hold the four files: the per-part `.xlsx`, the +datasheet (name it `_data.`), the symbol, and the footprint. + +### 8. Push to the components repo, under the part's Class ```bash -# decisions.json e.g. {"BAT46WJ_Nexperia_SCH": "replace", "1N4148_onsemi_SCH": "discard"} -python scripts/gitea_reconcile.py --parts parts.json --dfs-src \ - --template assets/template/template.xlsx --decisions decisions.json --push +python scripts/gitea_components.py push-part --folder / --typeid ``` -**For fully unattended runs**, skip the prompt entirely with a standing policy — every -conflict resolved the same way, no questions: +This places the folder at `components///` — creating the Class folder if it +doesn't exist yet, or pushing into it if it does — and commits and pushes. Confirm to the +user where it landed. + +## Per-typeid versioning + +Versioning is **per typeid**, not global. Each typeid carries its own `template_version` and +`skill_version` in `assets/template/versions.json` (both start at 1). When a parameter is +added to a typeid, that typeid gets a new template, so its `template_version` bumps — and on +the back of that its `skill_version` bumps too (v1→v2). **Only that typeid moves**; every +other typeid keeps its versions. Those two numbers are exactly what `fill_templates` stamps +into that typeid's rows (cols B and C), so a row always records the template/skill version it +was built against. `append_parameter.py` does the bump; `common.py` is the single source for +reading and writing these numbers. + +## The changelog + +`append_parameter.py` maintains one **global** changelog at `assets/template/CHANGELOG.md` +(newest entry on top), recording for each change: date, typeid + class, the template and +skill version transitions, the parameter(s) added, and the description. Push it to the skill +repo alongside `template.xlsx` and `versions.json` whenever it changes. + +## Backfilling existing parts + +When the user wants a newly-added parameter applied to parts of that typeid already in Gitea: ```bash -python scripts/gitea_reconcile.py --parts parts.json --dfs-src \ - --template assets/template/template.xlsx --on-conflict replace --push +python scripts/gitea_components.py checkout --dest work/ +python scripts/gitea_components.py list-type --typeid --root work/ --json ``` -`--on-conflict replace` (or `discard`) applies to all conflicts; an explicit per-MPN entry -in `--decisions` still wins over the blanket policy. Use `--report conflicts.json` (without -`--push`) if you ever want to preview conflicts first. Host unreachable → it fails clearly, -same as the push script. - -## Updating a template (new parameter) - -When the user wants a new parameter on a type, **append it at the end** of that type's -sheet and bump the version: +`list-type` lists every existing part of that typeid with the files in its folder — including +its datasheet, which is co-located. For each one: read that datasheet, re-extract the values +(including the new parameter), and rebuild its per-part sheet in place: ```bash -python scripts/append_parameter.py --type Diode --param "Reverse Recovery Time(ns)" \ - --template assets/template/template.xlsx +python scripts/fill_templates.py .json \ + --template assets/template/template.xlsx --dest work/// ``` -This adds the column at the end and increments `assets/template/VERSION` (v1 → v2 → …). -Then **push the updated `assets/template/template.xlsx` + `VERSION` to the Skill_Assets -repo** so the new template version is archived. New masters record the new version in the -Meta sheet automatically. +Because `fill_templates` uses the current template and current versions, each rebuilt sheet +picks up the new column and the bumped version automatically. When all are done, push once +and tell the user the previous sheets were updated: + +```bash +python scripts/gitea_components.py commit-push --root work/ --message "backfill into " +``` + +## Pushing the skill repo + +When skill files change (a new typeid template, a parameter add, a version/changelog bump), +push the skill's own files — but **never push the real token**. Blank the `GIT_TOKEN` line in +the copy you push, or exclude `config/gitea.env`: + +```bash +bash scripts/push_to_gitea.sh --repo "$SKILL_REPO" --src --message "Sync skill files" +``` ## Resources -- `assets/template/template.xlsx` — master template, one sheet per type (source of headers, - styling, and sheet order the master workbook mirrors). +- `assets/template/template.xlsx` — the master template: one sheet per **typeid** (125), + source of every sheet's headers, styling and order. Columns A/B/C are always + `MPN_make_type` / `Skill Version` / `Template Version`; `Library Ref/Path`, + `Footprint Ref/Path` and `Manufacturer` sit near the end. - `assets/template/Type_ID.xlsx` + `references/taxonomy.md` — Class → Subclass → Type ID. -- `assets/template/VERSION` — current template version (integer; the Meta sheet shows `vN`). -- `scripts/fill_templates.py` — build the single master workbook `Components_Master.xlsx` - (only sheets for the extracted types + a trailing Meta sheet); also exposes the shared - helpers (`part_to_row`, `template_headers`, `sheet_rows`, `read_all_rows`, `build_master`, - `MASTER_NAME`) the reconcile step reuses so the Gitea master is built and merged identically. -- `scripts/gitea_reconcile.py` — add new MPNs and push automatically; only stops on an MPN - that already exists (discard/replace), or run unattended with `--on-conflict replace`. - Merge-safe: merges into the repo's master workbook, other rows/sheets/folders untouched. -- `scripts/push_to_gitea.sh` — push a folder's contents to a Gitea repo (flat). Used for - Skill_Assets (not MPN-indexed). -- `scripts/append_parameter.py` — append a parameter to a template + bump version. -- `scripts/fetch_cad.py` — download Altium footprint/symbol by attaching to the user's - pre-logged-in Chrome (remote-debugging port); tries sites in order, defers on - CAPTCHA/login (never bypasses), and names files `MPN_fp`/`MPN_sym`. Runs on the user's - machine; needs `selenium`. -- `config/gitea.env` — host, user, token, and the DFS / Parameters / Skill_Assets repos (**secret**). +- `assets/template/versions.json` — per-typeid `template_version` + `skill_version`. +- `assets/template/CHANGELOG.md` — global version/parameter changelog (created on first add). +- `scripts/common.py` — taxonomy loader (`load_taxonomy`, `class_folder`), version store + (`get_versions`, `version_labels`, `bump_versions`), and the tag helper (`part_tag`). +- `scripts/fill_templates.py` — build one per-part `.xlsx` (version-stamped); reused for + backfill. +- `scripts/append_parameter.py` — append parameter(s) to a typeid, bump its versions, write + the changelog. +- `scripts/altium_refs.py` — read Library/Footprint Ref from `.SchLib`/`.PcbLib`. +- `scripts/gitea_components.py` — `check-mpn`, `checkout`, `list-type`, `place-part`, + `commit-push`, `push-part` against the components repo. +- `scripts/push_to_gitea.sh` — push a folder's contents to a Gitea repo (used for the skill + repo). +- `config/gitea.env` — host, user, token, and the `SKILL_REPO` / `COMPONENTS_REPO` names + (**secret** — do not push the token). diff --git a/assets/template/CHANGELOG.md b/assets/template/CHANGELOG.md new file mode 100644 index 0000000..cb7b4d8 --- /dev/null +++ b/assets/template/CHANGELOG.md @@ -0,0 +1,7 @@ +# library-manager - template/version changelog + +## 2026-07-10 07:56 - ESD (Diode) +- Template v1 -> v2, Skill v1 -> v2 +- Added parameter(s): "Peak Power(W)" +- Description: Add Peak Power (Ppk, 8x20us) capture for ESD protection diodes + diff --git a/assets/template/template.xlsx b/assets/template/template.xlsx index 1c999b1a8dad7f73ec04f645b170392171e1b256..07164c3b95b2d2cbcb7bf5cdee875cdfb18dec98 100644 GIT binary patch literal 110076 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%0Dmtp{vNvpgP32VK3Y*- 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
S@Bfru6 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∓Ect*2_`Ld#Y%xlib~>{?5@ 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 XPfi*c1@<6c@OPII4sO#Q9{luws$HB~ literal 25393 zcmaI8Wn7%wmOPBRySuvucW+#Sy9f6WB)Ge~ySqd1V8PubxVwk9Ip@xQ=G=Gg^auL+ z!R{}cs=caKt=&qp;1F;iAFmLF9pR7n|9Zg!A585{l$`7xoSBt?%P@eyfca|~l$pcj z6(|Tu7z79i%KuzuQ4&hTX ztP+JtG)9HRQE>!VmxxA}Jp3&Vp_T+0wi@<2;+^?M=M!z4AXd2gRwu_dv?!?lP(LiH zly5>|8}|0h)nxl|CzY5ioM;%{kk>vzR7u9&*1mVjUs0B~Pspn)@mm~e+7*Rc z((1*sw3%1tA*bugzQ*jTnxaG)wW(FoO;s`BU3igD_VKOwscWG2cn$kAPaI_qx_-jD zsVT%|pHfggw}{dc6H0d!oT3uj9j%F}b+lrb-k#3JVlHxgy#J&R1h#A{m@hV2k1ZY@ z9{(0uXtU^_f+OA1-T9mk_G6{0t>1SC74bpq8X!U6irEOtEHJ+~|5)fY#E4ywtasRH zqn*I`qz^8$9YEo1$r$TZGPFt`!lP>zdQEGhMkaC9s=!~UHT`JZ)#j4bKXgh?EeXm zpYax!GC+7(fq{Ua{!e(A*gKhh;3Fzt**bs)BWM%)1v%bbe$H4{34Zl(mKW-5X-P-g zPN!@rwy@#Pi-c{Kndg$pInTpxrtHjK8(kOkRB@?UaGx&;GDVF9=>zV!)#_Ks>qs+| z+WO?!JS+t5{Rekijl@Mfeb*}CRj|kd=^3SJHkaDZ%8`gd!h>>K1T$eB+8X2SB^xG# zk8x+9Qh^k$U}F#s2=2lLQQ|HlaDrs>fD{2?2LwtrlB!-KlpW$4NWC;6>~Cf}m2j(c zi$`egqgY-jQV+`*1$9>GxnVCjOy5I=7A7;g%MFIlKNBcr!zr{unOkSMzo~mNsq*8x zK{KS)S-YR;f$rq};*v`zpWT4xTh2F|cwgOZ^^O8X>T~WrRN5`?{28OTiaAP-Mk$!~ zLa`jon#D*%nxhw(@y2w>QpzOqM=aAGAZo!HwF7$vH~g~Jl0*Iu_0NshP*znF1#WyG zaO2VcvGK0XF7~z`Ti+BvrVz-2A^bq~2mFZW8!fUCm>dpKm}B0wV{>pKn-~~lo|%jB ztGld&m(3@;bk2wU+cl|1pCi8AVaa7UBJx!*Qx*k@^=t1)qLw0D60!2|MRjWKi0Lpu zqXH9ieupxGjT&P@u%rWSL4Zm>oCyHAvZk=8Usno`^*(84(lJJ2wlQ(fUqN6O4jqJe z7C<+h(NB%U*g3EKhNT71yE?Ld&tbVv@$8!uS*&48Sg^P@cD)8ZNgo?Vs4416o*i^6dr`<*Ni-nQHO! ze8QRdBfUVMMU)U6k40Wah>o6xfO1pXO7%wFRY(qEw8_fAj961BF~lV^=lL>K1uw5_ z(oBSlkgJWd)w=1^XM=7okV$au1!@oIk1hY-PfT#&mVdSR>#Y3W7a;w;z}?=-+S$^~ z%;n$DRaL0e<7nXj4gduLg7g1d``1TU|KgAOs$BsKinoE)d+)U;Y2l){@Vq>yt%hFN z%1HybSZ%oNf}3c&!4DH~E6wt9X4c5bOdeBOJvaurRfyoOCH!K`lu>NVn^CVZrO4=+DHQ&(F zwZX@_5=#4yOr22ShA3@u_n!p_%4xnr$tKxCO0ULSmZnO)75{EJTE2hkcy#)OH{P`9 zOyRh^LGYO05GUBnUKL4a6HANQJTs&<(YH*Z7#9_)Pn&G`Y^9S!;utkjnpA=!S<3wF}^_{p_+*F3;AIf~ohfo6o zJm*_Exw3SfUpLT9p!m@RJHduka|r<(2NQ`ypAv+#e=O4AK!>AM97EHbf|bLO%h4L8 z?eTpT*A!hG&1Z7I_&J_mHg(J_T?0lZTp6#R{4PiEK2=9~3T7yNQ!+Lb7Z{=&o%7Q9 z*@vI_(@37->L`=iUB*GP++pN>vwe9RcsH#c??E)1D(3SpGwK>6BBlS?+j3(!DwF&^ z{YKnVJ`q7J)CTC}#5RWYQ*gi4fp4QX1V`7l?~XSFzh_7br36i`e4lI6*v>qTQASry zc}5AxLOE%xwI-BCeu^5BM1Qp5tb=hr&rt6u&tsHz;)5u4z=9rcSU({r?FmZGP^e|7 zW+dt?sWFdaCK77S&(H=^<4=;A$fzwQ1A{1zT#~v-s1GIs^C*55Pu~a4p~XY>4xu}3 zWq)yACLJNB1go_gwr2J2@lduC?%hMC%s%9<_cw<#ts$b{6RJ!)Fm{1|`3e8T8b7Lg1la;cFKod_8DU8wM(3Cn_ZgTAEW`_~9h+SOapR zWxx0+94$#cbMBGr>mNN$*}F<-5HTf@!_fU9HTKO#021YFcBSx^0+sp*f=_u;4Lu}- zYhnH6&w}YB12!hh0v1*K=~%5Hbj@K?J%5&{P=9~i+Zt#6BL&-xZUD1-<&Z9)kS7-Q zuL%X_R-8wAkNm>qv@0~N=cp#5WYzC0V-0dpqEg}NY+WToVS{L4YCoY0Dond&7qNM8%MDdrd z2L%_V6P>|XXeJOKij70I0HYT0U5aNa1fS(VJf8IPj0k9CA}fhV*qv$MB3X(9DTD2| zP>oQ%R_8QMQrka@(?d+)On#g;oGWMD|WV`MAsrA{tg&9%gZY?)p@AIxAP65a>TSB z)KzwesPrS>Y9P?>x3Mzr{z7&af+#ss>REQUz=)_Ct9*X0%)EW|MZ!2;e9~e)l^LGd*l*1bor}>y0 zv(VIA)UOc)$v*93k4qEh44iLOOK{z6O*^6qU>9JW(w(r;go?eE19|VbRgwM8o-FYE5Y_qG1aaW{i;kq$$yM;he2;~ zY^$D0>gYGpbsd@Eo??fNl1iwkm8`F2$&BWT;Ry*BESS11gP%5;X5_-_NcdIxj|-Y} zWxlO|a%|u&6V7*dnryxSd!(X`RrIXUYyGRoF!-L}0<}sQ{UWc1sh>+k^3;&%!W-W) za9H5N(a0mHrNXW?dnr={2UfAMCoB^aWpEUduVi|s?15jJeVJ{mPmCL% zBKi)PI4j7bu)uFgPO|llp>o2k8PQd4fzkxG@U=E^aGV3N&jPJ(>V?F54-FYx0a+aC zzlyE?5^$2$dlehp1e$!rfeb3Cn{@QMF@VaxgJ)Vc!J{_in?04!yhI7o3sW&VrK~?Y z_^OpoSH)%XQz-)mF`W<&xd2^H-$mtCCMKR0zn&sA`u8Z~h%W;rOk~`?#gKpZOq(E^E}1{gfVWwRb-R&5PYb%DLfd6`gc{Dd z1(09V2Ji*Olv_ecQ6~5^Zl{$|VyByOw>lhqhIl28e*jMzX@ZADo?*{W9VbAdw>_0E7tO5`9zhLiv$_*;Y z2Ea+@V1P^)O|dWn1&fYN)N|Ca_p_TtCuF`vi@k1qO(jrGl9*#6eU9?Oxw`U%MTKm9cQ^QDX8jgCQrPwOUbkwzA9^Pn0Wqryf0Zvs*a5;hwBLy z<{)+rT{g|9>F6`4@>{hkMv%tuQFssBk8?dw@y@HDaTrS+oj+p5gahZZTGk@_XK|I*3QK5v7tcgGgZ7kC>PMlH$qaLVDcurvO(y#8O{NN(ZaIR6? zLXIL|%r$YnF7g5cYTn*!5N_o+Ms`lJKTMl%4|ESjO_wtHp+lw#$8FN@wbIn8{C)E) z0W{S#I@DBI=;XNJ7>}3OX5+dHk!;t-IYGNn4t1idNh;%5K2pATnqd6&eOqE+r`llr zvV9+McOn>>1L^BsdIHinAvVXcz6s3t$hucQ8Y}FqK4jk3o)^W}txHW4DEjM;!+p zO|tGrR1t8t3*R{jEL-s(q}NU0Xmjexgt0z^PRpRUbEF!CvAev#+rgbwLe^Y_6;5L9 z31f{2omN44D#XrtNeXy>cO&9*l)rR=&y=2*BU(}17DQS8zL}DQh%z*a<|2HEzU&>G zFLZ967)jqiZ8O)+!=Pe}w6cduQEpG$7c`X7LGJK7WdeQ8Rb}})3GToUSS@G>LFHy| zh5ajb{p}~q%U^!rzgr_+1baoDK&Ht4=K}HR?*gF%ls`ECC4bWOlVM8RO|4p*RGLm2 z;E8+bmd4#gI}CnI?*Ao!SW}%R-1*%4Dh}#NbUq5ibWaAbK*Z5vpM?H-t>b+X4&#(7 ztc(RCdj?C_AH3KMYEo0=g=T@k42-t4ll%1Go%LIZ?Q8@N!e^wAF0EIi>E5--wkm-FZ>L1Uuh5Lo>u{JRUqK|ZEhm!5k>}?cLftI#)VP$~XZ|gJv^%`qRCGBFzIAQc zBpQsMQ#Or9rVP*6tKe(!+*kzgj+s)pDrOu*1kp2`$6>E^wmNkL_H-W4xt`4@ z-J?yL*Ujc`%|JISS(1-YvPTZ(W%LAG@3QPIN$`EkCy6+8W%+?jbIv!wjMTK@LB*SL z7qxRli0un5RiTN_FY{!Ym!sN&8G?x}tlzDX%7Oa#(7QDA2ntVZ57sgr%$;+{j-W; zPWre0v1nWc>K~o0leJ`LyMO4PNxQ1Q^$#IX|EPL`-S?n6*utuw?xFTIQfA%-zKe6) z!?wE7<_*QcU^9ZEU%>pd?zwNpb2x+fNe-cDJWF+w!F5M_$QLm~xMzs*12+u6>5~Tm zofdj0h*IywS<{a7lnVMB3)k_dL5V1FO7hMMbgpXv)iz`KgCUFz4r zBoTb}+nTpdo4jYo?e~Cxmqc7*iPBj>#OMBJ!O!`>2>$pcE3(|<_@c7~z`S6NUMoa} zA^R%qk04e5sz;jwaLdK2Oz<@{!^&GqRNuHYZ3@#iUqX%dJ^Qy<8i&zbqqA%G>@gt zwiT%p*ngPnPDvV-ZRC?SL{ir#k5oe#;h{o8vg7=i@F?PI5__iVQme!3a=Ri3YPC}U zBEFxdJDytROByb7T+wDAr(4@4Hh7L18`?KgAmWibJ`m60J_&C}l(o)8!lKCQs^{ri z)Fs}iOh8GK`eOK*&(^ffJb0Sh1+!JvhoG3C!BSQc!xXg(A=v;d`~~SMLaZt#J8Z(t zyXfn;F-XJSI&~ZLHQ6V_Jg&0!@cwXEl9)|;1RVSi#On-?!-a#{r+y&*!6BqJbVC*> ziJTYz3-QW-Azt#o5Pt^wwcyWedmWk2#olAvO_Sr9N681`G0iFeMm+Xkh_~KaU0dO4 ziVtImjrmdd+s%@OcDc98wI9*J5-sKG^EhP_wILD#=wdQUzizi^YE$5B_GJ$Kgm&L+ zm@i(lj zCFQUngOxsBtay&|^IgE za);|*kah;Dd@rEf36Q+#rHNI-k)sNVFO=7Qeydz!TNp^z1sKj4Y$im4m>};#j?HXr zY*=|m+H-mYFUVOjNrVDQB2dxeBHJ{B9}i-l2Kgcz^T>+RFc=1EVM79dg@jakm=+{9~{RUB1Q!p zvm_^3kqN6BN0Y1!{_Hvmf40ndrR7zZPdRsdMx<03@+4YI+)OiWC6ZuM;|i3 zH18{$8&|DTuPK4^VSd-QLQS+C9kyo~mQ&yE6JO*m$h?6j*Y-XP-!fG$*>m@ zBskz^&Bjz}czEXt>fft`gR8|~KOm=-ED|gmX=(bH&Ip3FV__wnBH+%+UYkVolmc7g zZ-!8-qAB4M#qeFAmTZm6H}ZM18BZ4N^b3#fOZ)yrHhhqA@|3!HG26>8lZ4=}p>G>p zS{v-$a<)7g$!zhs1O3dSZEuz=qenvmV|2+qQs`~b_9MEg?x0iP*dU6;Q+&7}B}^dM ztG+}W@8ZGTDW(^P4DzNLkPKeKR_X(bBKPfoRC$(RJ9zoVnra}i^p}qQ#QP!p1tD8i zQI=NEXbt!fB-F)I`uBp$*FJRF-%NlSGCPng%oDV*|0y;GP=4f z6UV5bLSO6|!q$oMym-03;SleG`fg)A^XoiYt$7RhmblEWfuV2Fxy?D}S?JnZisB#8 zf3iWsyz1^4$cDZDENQs^X2Y`6M@Q!`@jqhSL$LupTj;99yrIuR&QK z_V$6I5AE*;8QOUmj8-^@a#vPYJQPk4J%@OhG<3uor@eAf6*L`{>^6(8jj3O- zyR9mzqcN7D6h^375tdo1)%2(U_Iy&GqkuLAHb;5&g;* z=z6aundTdsX2^2TOGoTPQ+<l5|A_R+Am*H$Jfv6LPVV}4vZ6ygx-x}8LBPsa{RO87F-DU1@5tZx%lCNX7C8D&p9Wu{n7^xD)Wn&I0L z;*_-~ID|fW^a03T9fYQJ-xUFW=n`9JS8nts==_FC@O#@}mA0{tZFksIO1?M?b1ql0 z{l(sjD;fcKG1#J~syz%+G{Wl)&Pcs)p`knay_Y-qL|Ahuvu&U|NSdo~I@t}d?AwWU zy9rat59cG|5(GNUgI=Z6EfU5yQ2j0jciISCa}$1+A8tV!&V_BW7V8YqJm({UuH$bc z4KTXNzpx!H4XY(DItqNa?%1mSmagVTKODY+-e#l^_#572~v!2ih)@id~CO&~uS{xd&#{^rNn2S3)` zi{EpnP4QWxHv_T&XS{%n!tCOhCa^wBFtjAogx@Egls%lmj`elIya$JC(2IZeWYcpQ zlfq#I2eW5;L$8GcBWp??A8PiVV91MLkpX1fSvwl#mwTr-)I!+TT7!vSAmDW69V=Zq z9rZ8=I4DO^arYe1VAIfm1UW00Q$f>KNpD?Ou-GaNbkwDh`z^&Gj#1~!Bwh=kT?`5> zgq_|p7-UdQT9JJu?NW*-Fs^zf(A34C<`bw{yiBPVhNC1qgCKK-xBf-*^Sl+y0*@n5 zJ3a~RsdHpPggam>TeI)B0G(wTZP5&d#zP{hA8PxiwbOJf7Q@r^Cy=b5Z0 z(g23^8FD}YZTB@5J#v4y#H$g92K1!#AlZ*(hE9`A(cV1ujX-H)@OWX9$Y1~9UpIHd=jSQ@QyVgH}{BThODC?E_)V2T4NnPQaX1zxf+CPP*F6%1H87Y z)QCQQC(6k>i3+3EAAfb8oeg^~EXdFi93pH^2><#R!C3Hyw@djuH_Nwkru+T_I%L_^ z3HmTmyg2#%1G1+vQ9{Kl91Tw)2_La#Ue3I`C{q%&t$t>iw5MSfnUM&+8~>ADDXb;9 zB;#fnKG7D69&J<-F=`lxFZRAo6uQE73Mk(wLBD9OJFLv)e*95E2QL}I{^BUBv}-i+ zVS~~x&QOPYc8A>{oS^prIgL|&3HnC`{2Z}1MEX*OFF(f5o*FqFO{`dqQ#%X7&2>>? z2+AE<^ld_sea6`K;ry^eUKIL>%6~p;D6^M zJ#By;&6Bz_ei-M?z^_vJMCq|2dcUR69TR={h>xRc6|3NXsYeCpud>w_KZsZ3YS8%6 zLb6Zb_LWdXkScZdgf@f^vNXi7zT&GKc0_}3NyF%M^^DU@B)iSZ6|HZco&Hrm+)~5*1 zpfE>hFiLH*y>O0135B<}i1m&F!S%C>f(wZ?onc9AI6Am$6O3yW%?M$b#`cvV3j&W3 zm}gKJuecZ%_1Z08n4d(xwUHqXtUNE*bKmgccO5n!#5BkO z7~#=P<3l#Ig+?8@&j&^tvZu1gcEkmkPsmZKNn5Euv)&x{*&-_6RL^qAPtEZT53CF> zKuQ$ErXQ)`k5~+#WDYTbBh$(4X7)0!%#lk(Kp03SyukFgi@lmRPV1Ny$}a2DCU3Hd zkZvo{*{fXtj8s#?Ik3=~^~|^%WnK~Wh`(MQUH_e&JoE__qf2lB1p|XnQgWfCkwsM- z^ynIgJRY|>9~ngUGJ#`j{k%hkqgIXPNixV;h8|9nw(`U+veGBz5b_Q51}1v$yWX%H z!kh#8)qQJ}&w2QxNM0tEsa9H9<)7NML|CF-x3dtNYFeE{&Rll-v=O++%LwiUMR;-r z{k4{>HJI$r``7hY&I+6mt&mc__Ojehu^=7MAe*1IAyj}ZqY~>rOR>ioN|sXv-wUJU zt#4jahJ|Kc1Woc$B?JWSb5M5B(AhJ=M7_C1ARXVe@)Qf`kzc%KG)JIBtU+@)7cVqA zM+E1v=7{-YEy1ppz_5I*WGCu;3yoZpkaip(=aZ{Y4W2_Sq1o2KkHW{FL$_<;pH@OL zBRgU}h}j5SNi{r4e(DY15e<7_JI#iO^U-ZS$8JZjUwD$NJKMd4W_>+>4xJ^0X&hR| z=*EZ5V^~?(J}IPv|0Xr(4;w{isXz7PkIDhq%LvR7-gTDP_ROrIdyFYmVhsT42}&%^ z5>}*L-&kW@scw2gS9t$79YucZpjHi}!~oDJ|0hcPE8hL@XiUqSF|Zg>9zsp8-eq08 zY~`Qu=FcZ|&?x$PZaTp9k-i%sWaQqzn!zpKo z6(BE&W7oGS^LOlm)ov}(HNz4WdDTy?j@wZx|AL53A^rq66r4XW{EXzp9#SukY>`+y zcZtYP!cZJDlgs3JzB0b`x%#&diUMS!FA)FvcJd70H)|kjMQwDR7Mo`pgpGtAPxTjZq`07($ zq4DJTdSf%_gyM1h4@&2!bzDQ+6<1}8lGN9LAwX6solXhYV4Rycldqj$Tg>LwfHg^L`6BlsgI!lRV3_-RLNV9 zXD@5BhOZ9RqbJB|NLSnJ$HP0|-^EaNDIF|4a1dexXr2EP$p6lN zKkKOO02`p2X>Z6`ow8y6x=;?fnsa)tTI*xZHmd1$w(Wr2~Qem1t>CpK8X|`}{M{6jN z4jF@uN1{&UY3acXWj1jbY0?Uc9u=EbnFYhzVBbG}Dt2Z-=*(^MEwL6sE2xfJGC&Fy z{?H#fBhh>`93~wF@@Xt}Z>Fp(Q0geiA492P%z}9^NN@RQTc7+#BBZ}8QJF%I>Spmm zrPJ>iI@e+mvC<+fTGYvmSBOAon)5J9xvq34JyEr;6AX!ITBl{$dbWrzsBJ&Rc`tHI zMQm|Yj*L`7P1x_8u(&SD_FdGL91{vwGli_%GL&Y`6EaQbJiW*BXjllZF(&P;3imlcXZC2XrE`~n zdWcg(QCH;r+q$N5uNuW8{x7*=jy&JJw2752PwgKvjs9jJp)~JFnAWJ@H7@>iRCrr4 zbfW1?lh*_0GO537mG02uk#arsL|Bh5)G?LRP=aTJ5c_l`9t8w-CU&M;7`5RbeKvF} z!eEcuM9qZ`22qsa=9BhS7xT3Ma0ejrqU33y8E)zZ_S0siK$NbaB9#P^g6e!qyr2m& zG1TA+z9rZTpi%OTa&nAd(bl_I{XgMtI!u_EIx{CF=82WUD$d;#a%bKUXcWqsVa#f9UeX+ZEr505e zgq_PHfeVy_T~HU_8AvzO(7^ppO9{vyZY77WK)W64ya(klKV)ueMRjM^{bIrP@^8&2 zH*S5O9blP&{684<|1xHvpZMrLi4*|+w#?26j)h8x!VFba$)W{h;4}mU-3Zmu;)67f zO;5t}pzo5NTTFCx$>K-Y_R)Uw4O{FF4O5cTtptC)`FZUX7{{q5&XME~`IuzoCE)pP zr!9R~i9Un?2AKz*iIbq{We@^8R(fwb0nr&&G+OE>1q>0YaiR!lW#1z7?YArv;dSZc z=4kQQqCJMO*w#$tQdPw367eo;(6diu&M0{S%qoq(l}S0^5fRt&RZ`Wwf7poV%FJv! zmCSvtsxq8TWAJ$zYRO#+HF4E}{2>T*hItrJwbR#I8%1IF%*ve==P6F-0 z;a9&w^NAUb9%2~DX8P5=hM+$2P?6fo zi|$a##+2`D+ctHHpr7Ha>)NG0{{S#R%4(p%90@UP93xz3#cj-&3K3`u0Gr2O4b1f0 z7xZW`>Qe1g*i(+|iP9)~OmwTf69x%)PX>U=!iZAwhr@RC%w-upjOf6kOzH81TemwM z(CxJbOYBG8(ne+4r0CI+@;!-mfW^NV!mf4;G?M>IHFbk5&N7I2i~?1Yac5TgoGUL6 zSDF862Ko;b3F-g=$fb4Tr#;U0``Qq5PW5_W7Nwq&^%L)~ll2g$0LefT5h@a7M#F8c z<|hJpX}r%Z4I6WtqAfvufdU+i5LP0hwcVJP|GYZ%j|z%?Sj$n@9_ z@>oZ7r_G>9oirg=(iBGKoQ_ylE1$z{nBV!Z9;c$x@YhA~R_f>0*wbt$#YzpR_Yzlm zZRMb9ZaNB!r*Y+oBa1al>4=V3M#O)Z2cd^_*V+O`agy*4C((GzKGSyC~Q31(;5c0Wt(*Y zTNDPHNs%A{A(#H9Pnq-C>*Q&@d)C=`kQ&;&2Tprp@a25H?ti3B&Al7PRg?qrOH)pL`>t`a zwul8MMFQ^SHuhd0(WgkK=i{OB{S}7PlUcS@>%njkbq5F>fo)S}&<$R&NZCHvMw>~b z5?QdwkTVcQH@==j*q2Y4%&;uq`g{=ADgA`o_)Xri1{T9AU008Y)(7BQ62RTBX7XC- zN1;2uUYUx$L2G}$HrheAUZ*Tw-_x~Ld~(P&4cdYVY}bOX=KH}$sYw(KnQ1aZt;ZcO z-d6)+pR#2srP+UMjDzG4NdVR??zJot%Q##LIfKBqA#tma9(#f{Y=!PL3&vwD3iROz z^22)Yg{y&UEy7IcA9h_I{n{2=w6AJLpwUR0eZmBPzctL`ORtSccZ+ae6_wlyi;kWz z^H8(dLLYc--VYILgJhQ>;Q;tJ$<$U#~V0J2Ha9_5R z8Nv1FB+;eluD93_UPMINdYXdbcz*`VsS-7$MQT8z7jx!oDj*Kju8A|!+~?u4 zSExn~P0PwEFXvS+!s(V8OwC!^2L!D(q$+ zNJLDm`E}dY_Yk$LKFLr!^$*p*5D1E7{+RSh^g2Ol(%Ojo#8kdf)p2U_Y5GdNEtY9{ zk&r(It)Fb<+Mhw{XuWtcJD&620rCQiT#_=%#@{aoOilO){+xDHp5q#rf7Y5zk2FK_ zdX$1}xj>eL^mesYcZTg0s+0XQlsa~i407ZEp((I^OaW>~2)cl{4R`F&C+05Zd&iI5 zg$N4v5IBoRBEUoeT+#>yhPr6w@8vOSMLa+qej2!X2D?2#-;%-J_4Q0?jd8CuC6}qw zn&#!RJQ*cD1UfXv^7#^`*(&o?%2fO1Ml5P-NO9ez&T4fuBn4v02>=Ju`f&1Vz~ho+ zEcoy(Ore#j8wBcoda9!B_(~DHp5zPnH0x{XaPS?O`64CQ#7ZUUp*7(6P+6 zFl{!xPUr<%{@{QZTXJas?q<|=a2SMzewhnn#9Yr;x$@PrLfL!i`FQv!C!81-9X{|w z8Fj^+XHdxs_|EKnyNq7!WD%1fLXZ*_ymyfP^6rcdF+QZ0oXe5#|D*>!m4$5skRCh# zhfV)ipsl00`mYfVtkvb36!IcY;FBR`Rq$>$T8aM)om z0;3&`j4U&s1FvXwywTI#(Yy{j~24f%PVTmg84x*&PjLRYVs=9H3|1t17AzYfdRsyc!Id zPC7qQ49oT?&e$WeX81JPa0xiVn)&J51@4h3=VG|UgDwjVcVoD%zZ7i_!!2in4nR${ zck1Q=4?#@K$kfz((uv)|43;a?P+Ya2>lD#sz++37=i>DI;rrPoQRr{w1! zinsoMz%FBMiLrmJqXs&Q_svE$jI_}|yU)$T4_kk3^hX_Cv z*9lBnayRq477G#fjK?LNiY^N>Sgv+pAhB9nr!c~>$wi#Oy0Qu-sUf)mkL@1b0U^u0 zXU#!2)Zc6*FlHL^PCFqrgqPL&3ZgiH=uf}NGU8a`n^e&LpQ_j8DWeDz?VG3N$zU@AJ&H$-|{p0Qm4Y`*bP zzJvS|>35P-PrrdkSNlH-!QVOe=Z`}0FQ@Tfh(Uq@l>o@2uu4ciy3?vNs|;goi|;8k z*d%{KvtbABK=OU%edS`vc=dyozZ=kL1jPVqVo=fj5Zaa=9~UR*2cbWL?!qvP@t@3c zyC+XC?hU%-7KH2{LHF72QmW0d2XCmBjH{x#T#o{Ux(p(Jc~lAepygrs%s|KKLrrI* zlOKF=88GOUL`7awfm%(3RgI-dRssgyIpYm*Y6&xET9k7kQRS;5_Sulea^R3&V}hU_Uey zPNzd?IAU)?;w08L;+3K&h64Y*6k=SMU|}HE?|aA^3;|%&y2j$*{q8o1EW|5=bp+?WhTzDa#_<>zsG_E=bfd!P>ae{|eT0G;hA{V(M;Nf8Iyd9e~M8L2^X ze=es61hcokoOO&};A{>wXT=pEn1bD1Vi&FXaNrcVTvU4~%Wf6GX6{m^u{SY+AnSta zR`~ahfGxQ~IxxaR8U6`F4>s#9W%?>Mqpro`!^q7D%j*)~Q(PW0E03j23Q z6vxNzVT?mk3oIWKle6+%>HcH8jT{fYP!}aKK{wPwNG$?C4nVRL%J2HHCl>^EOXJT! zS^BB=?{UoM2G(3z1O74_z;W&YFTr4|E3YGCNyb5BJ?YiVpG<{Cdq9_=a$OaH(@err zwCb2T$LNVt_hkVyZR{{uK&5G0v;e}K5#e|PXQn#5>@$B)rW+`|q*I*IW1D$ww-0hp zTw?JzUs9Zot|XmCljc9)4gV1TlNyy3T6W_=YLxyT@x{MFZ#~rlVCenP#!8E<5ml{d zBJ6Qot!l5RXvmpF6&-;@ynPZ;SU4~wC6ikiKEWp+ zIP~L?y*cXFzPt4e8adiP4$hlG%GcX1y1NkKMzX481`IVr~r%_W!_~ z62wjaes%d~e)qbt@Ta}5)d|?HRq9h|ANeJ^|NX0RJ<6m*!-L-E#mE}=lhZkwAvS2i z*L?rg3+MuW!430 z@4^EKdSR1yE#k~$Z3MWzRi^O14qWZa##=jG^-A+zf$#`iFSjqZ?U6qF0v38Y)H7Uq z`nU)Y-Iwi@=9CZH?bb3)bJ;S*e}!~a7DHlB5&Ak!uo=PhxLcW6^7c9|?;b|G)VvZGYj^YREp!9vnVt{wE zQ-+&<(|g_YUpnZ2zqs~s7Z`Z6&BfBp*6gqQZT~&+dV34EX$O=_0{DO5IsAXi{#yK3 zo^ht7Xuryd@dJ6uodkFdk-8Zl+BJNsE~82wf50w;$SN&aQ#D_B2H=gqvg-@l-)@uw z+HuBWLq7%9;gUn86H*2KjCn6DQ;#zyI|GHkFdrgL_TB4!bC=xpZY3ZzPmGLW&vCUv zulif)fFSWKx230h@S7|?Q9RsFlku-|D;t^87b^=d7kYykbQGu}wSvhRt!F_plo?6k zw?;T)WBjD7+Ae zGRL4fVSZHdnV3*~*$W(^6J{x}E}>eh;DRPT92CfjAvno!R4e-Pubra{^Kct-*sM*) zwnloJuQRSk{#P!)E-`v(EVNG+bu`?Zo}qf?*s2##Ts@mIvq23vq2;R>B+~ExNLD3G zGx(nFvYpvADOYtog1y~Qrs-rUluXN*)rlhx3ofKw;?J9Vd;aRhj~(PnzPy`oYgu)< zgfhMQMd|e}Q=#R41%<(er6Ty~-G558_pj-iJ*S8qD9$5dj-BU#=N( z>CN;EG9?_085NgKZR+nGy2BJL>2LfC#2!sIwN9*C7jLFB_7+lJZS{qvHx?ECTTS-& z_v0tk+vBH%-H^#>;y*60htpU6#Egs<+f2~`JAEmeKKBQU#05gxJ~!7_*n3~K2Hv0h zsein_dhnpIS&~jiFz3A9pN>S-2)?~_{G`0J^^=l#jFlA6*ZB=YcnRae9_&a-P43u@ z*^i=X0g2@2278nZN4QnRV{X=ppU0Pu3?C>%_@7H6wjnCN$=m}y|X$lV>6JycN z&XtdsTw|X7Wd)TFi6jW+WaZfk$*E(pBHXMXe9}^m5USm58r-p6x#?%Wg%Gq$OT&!I z1QMg|r(W@9(-I`b5$uFwBKn|L)XEL_>OM;7D$-rL2;)d~vBuTg{IUgo#>(^o7we*P zeId$_Vp5^uWi0SOch(m_zqH~SP*w>hoZ@CM7K&AcQI5QCqYJojFUOqOO&;VsJY)qOBkxpasVY6Ek(xdl;2RZHeHo2IwI;k~!7 zBf#QH&`;A~@9tJ|&unS_W=Z02LI<4^1+jn%N3Nz2bki8Q5eyyIWAOWB<( zaHr+UoLl$k0+3VG1A5oer-Dch^?F9-x$~_T95{O;Bb(OGpfcMAziyN1QHb$VmBln} zj0u+GQk&HoGl7%1$#@k`dX#8Z^;8v&bc41uaF-2Ncl+z@cX3^*VD-W?A9V`EMKdr_ zQAbQGuUdavz$3bLoVllRs0{i^16m`y{%C~NvmRR$i1?zKux$F&mr*6h$_4wyLDlH2 zyRcRj8jW-wDLD@jkv%CGB;pe_{kt~!_%~&^CF$>ov;ki6A#|$^%cKqy$>hbp`YQh0e@G3<(L+|YBw?1HtkS8W*pu~1gkdnLx zt3=pzzm_z&n+`uVyxI$Z7YNDH2YBz{0)6!9kL~ zC?(6?W{FlNpX()4$ik+D(uLBa)8B^@pmEN9sx5nPLk>bYE>15r>)BEm%6Z8)pQ4$P zS76=D#EZY^;)MQlH1U&)on~*rXm-lLWy8>m$=f~l58hPa2k=IMOC?i9Ri9TK+g~C^Pa!B^3$ zNmhNZWZ7&7l%X&lRI9L-(h~!HzWrJ?IH3ZupKNd=A}7Cj-TCut-e)ojwkvXcxCNf( z^=TY3&_ID_;SW+<)#wlJLVG4A;00Gr0aE#sX}4QQOXO*x`=xd* zLc+de@3RVIRF_UfiR1&Fc5ck`eEH3BMASU7jsApPYu#E!}mxOeJl>dUDqb*5fs zFp79u%{|A-%}LH+XO_U77#qNK`Q|gp_3ks-P@xj(;*?RrK&^iHqT7Zf`!b}8b|-nG zc*VfzsfP;SXKip#cN2OWSPNsTYz_SKI7hLg@bqwHf=?S!pRYaqVklM79d2!JOsWG!J_zgXHv$@H`N8MD5aJc>At zwGBMoG3_-*{3mpNBiyz<*<3)9>HpWqS%+1%ba9xFPU-FtN$Hes4$=+MD3XWnPNf?} zx&$Nzq)Vh5Bm_~qyX!l^y_f6xc<%mVKk%Gc@0yu)_MF)>YkqS_6bn!3zO)o1N%vU} z5EWRNR@NbU{7O5QnqPgASQlE$7faz$H~BdnH99vDRc9+MZXXsn#G>I1{Cw#{Po`ef z&hh%hX3@V?fx$MSs1$44#>L`=PB2`$%2;r!EBc$J$sMfer3$?ag24k~r*PeNG{y6L zgivKIq(HbQn8p~YZzqaJ35`+{Pt5q8+FysQ0Xn&d?`fhzcky-k$HQ7LUO4f&#vMqae|A zF)x1=f4cOc&!eWCz}p2b4!B10TN~=DnCj-o=j!=ERLO5u>}wobwNg>O_kMB^fRwSL zO6Vbp;6)A}c!qE5el)sUYj!p_XqBhsXt|E&JpRox%y$^>*eZiMCpm43qYZ_P)`&`e zwVrk<-Mszv`_cYmnGxfcXibZ;KV}wf@iFO==mrUn+ZYwJJ)U%n;;2`>)GRozr91GQpU11u!=q>u=li9q1(WtEb zG?8EF#wn}f|{&{j4u5Lh5_LMH_aSOdA2E_`By?^WEE9kWt?LMo|(AxUVU%7nYH>aLUM!kYBP+NWoH-~ChN}1F_)lNSr zpXY9$Y7e(CGf+MJ$%hchk@3-4#*~%KjLD3~FUW|9XB)Io^cDS=ISP-Q!m3>vX(lOQ z$*_1hr`QQqv@gmK+{lW-!C}uF+1RvvHy0cfyntB7GwGiW94MM{_Np{;%?Uyu6Y?E6u?V|!1(e%;<8$Ny(*@)_r$vkwSi>lJj zU#fc;x$+d7spn#CLLeo5h-~h$8_!`gx%8fxVg3OgIW5IhuZLBlpco4JqL0T@txm<) ztkN!zUb=;85r4_bWE?>BFA`*aPkfN~95WRfw~@adlw>KYvVD+@zLxD~>`qF6Cr0dK zfEBo*rfG@ZG%Y6I9LKp{o-m0X-^ppk<-tjGr&&zDq@`07jzTW3C7Hczp$1Rg@mDz^7(bA?K*M> z7>x3(Sghjrr=Cz)J?-kSEyz|q(vT~juqMCnuO!CQc-d9neubQmGd$fC+Pk9WWWG$J#JlhRDi;7&tA6 zIBdW1pZH^#fp;aeH8qpQIvK*p`@Coo`7+nh{z#ht=`Lx1%Mi z`A;csY=%lOO@2djSjB0t8!D<4U}?XeLg_*ts8?8onr&a>f66c_RXoObV|F5tPm51k zOs)F4l(JdqRI=;LlN7dHOuFD#$n|{I_A;W%V>6?}X9M38 zlH>bGFFSjfqCbErkdUx}?N#l7-8b;PDE;6f4}vs{biX!}+1F-!jgjb?c-bNW+8O(wrD*K(pp z={Z*k-p=0?OG@Vlic$ziMKz-AOTR7h4J zs^t^KrT*6EroV(YF%zFrs3M6~CL^#VV?4`8n;WF|>NDoF&(qhxUV%mYoqrs@_ClD* z99>)xxUw^5GXiA9JzJ8{?Wy)h#aFL{r5NI+(EK24Zyi{DW}tm83t6O9k93W?HkOR{ zVX(18)*b~x*@)mBLKt+=Cy8XXfO?yW4h4G4K|vo&@lMnpuOv;lC0Jqv6P7i^)=~mP z(N;tCPbK;_Y3HvuS}k+98@;sJo-hO^A-r}a5Z+>JkZGW8%f`>eCzEZ}7p~L*tWK1? z+KXw~+zB-Ot!YacjC(a6MAz(%dd!|}qr9jWDbAa)8av^Ps5w$F|6VG7we{y~aBhv~{q>Uha+GHqup_!u@T@V5oSI(_XAr-7uD)EXL*1aD}_S6Yre_<+rMLBR$)G z?%xP_pfN`WBLHK|N7`hb6WbAIfksmuWp&;nS0TL{g`0u+UDnjSQl9h%xqe0S)uzrX zqvmfU_Oub$qJPRH21)k2i045@Txp@){vUOr*6O}tVEx!s!|fHvt=Uc4{g6P2Qn?ZH z#GB9GEnN4rj36+|Wtd6VdUFWmXymclCpMP9`L3SP)N4SI1Y0P%mqPo7wODSCNUh~l z364*-j3KkXXt7vq(<6kpKBZe+YWtEJ)%2ty32cT=P+nBfud-i}XOa7^iH^O7hxjVn zKuD1o)ifeu2j%tBgi?~dG736*s`D|x(x0pgPr;RS54VNpBa}`bHXb9q`cd63p}2~N z4T;8{y;hj4=4! z&zwxrC`GI4?(S9PWEDW>r7x*&Ex6HQ*Y;?GMllZp;NfhdBXRE~kO zE4S81g-oM3hN zdo!r$$4&)iB~$jZ*JcivLdWb9VaPxF`1ot&&p+a_74DZp7?lOM986L=DkWdIeqapeJBr`PK)d>4V3ba17*`lfq z)wWUwWuf9}jWN4|AeyU9PI`&?3%b}jm>jJ`tnOcPL|b$#l+*iI&SFzZLR=bIv2Lwf z{myjj!h#dn*ow)1q9jvi{l>rx1OkFByeAbc29j}NT_~t^1=%6Pgk6-xV;DI|(Y*aa zvYphRza@1N`gaOd?el5K@s7APc)O)*@v0Ri@z=d)u@CUvpQZItYYap-h4 zdYO|{k;NTtCcM@n-F{ zW&Ny*C+$F96o7>;rt{P!#wb_{A(32~Y=ooWGztulck!lvv=ZGnyYm(?u6 z>moVwJ#4w`K)NvO48Bs_`IU?4ZxgXqzs{+aXAo1@$^B!Mya3VS1qAMSVx>|t!6Dyz z-m0+Vs5oz1?_trww0zZz!T4n=@0uB!Zx8fFj;hj9zK$7Ic8sDR)_+eGQp_kGTI5~! z_7P7cg}9!Ek{*jdp@d0jc4>BYGme@R@4t)UPVf?%47mOWN%nvet4`5k!dmz`Yut_CNJ- zuq)b|ZyK68Yc9+j4VsgU9(mgFRM1m$_Qcy5DceVJpblAY(EFuH46M;%lT%EFo!fG9 zL)3HpLyMm6+E(?`UJQH(688y9DrmQSF!e+~NV z=A0qYKV_J1N)|^C$h5PdcWnD^qc1fAoUk8H+A~vZ0E|7n6XmW4X)vQTHyay-6&Xfr z;Ubbwmr{PZ8`lxekm)_k4X^igQ1lhD?sRNa+;Ml-nDLb=Y8ZodQL$-#RJXQgc<%4F z#2-7wL=~m$t~AIR#77tNdT3QeK`4!nLp+a1(1_BHv%yNis(VkOBaK7>!2fmjX|P)z ztSxsy{i(I}$Mya>futu&_J>K!@i&+fFh;Ma8C! zDB?N+iOQir7Im6z8Ir!~$diUrr}v!-jf^a}wnPnSSN2Hp`cr4FUsJJuYhQ?WdCu`t z3b@Bu=ybE(_i$hSwQ2QGcLJXcAX|ZiG8vE|APDXz;CIDtq#6Hyo4xJbhWI}7HWp;> zZNCQ59S?`tt0H268@``j^%kUDCQU{NvBh?twcv<@6_R+}BVDNIvIB$p7n+}%>Nrou z)(NugAhT4c@{yD|YrDUmA1xX%VJycnslH)f`t2=Wq<-wwqizw0mZCFrz=WV$*(1K` zaZQy&Z@v*rm_*{BF^NA-M=GM-rHAdNn$bIKPqS%)Qdavh$)a0l0`LXB>u4~j|N1*; zov!j+1>JMr(JKk0Qq^+MO~6c@{_SE)wQynyPpZ8tbN2TrYVW9oYzX#cRn?)NviFipyUgLn2ZSA97BSe5(6Ni*-=c8RKq=j4a z87bSzN4s7q8JE>`62dY?7)}^#W|I4v1CVrZ7T=z!efE*OjCthwrz`!1F~O@fFfAQ! z2XDh%P?ShOx|ug}rrM&`R`w=V_AgajY)tHQZ&n=o5_Dg*vS0;n11`{Htx@RHDF|S3 zJ$0UjoIsnq=_6@RA7J@=ZBIgA!taqM9+-BGe>nE&pH8J-KQI+10kjyH+P4zeri!QM zC?=6mZy4<;y{Uk27?fF%5N%!ZF8bb3zk;bB?H7+!%=*+go-2mJ_ug;G&bC$qb?RJS&XiOYZBZh^=$oosDcbJdj`ORIBup zf3d^}5ybdX1ZP-i`h3)kezeSXVlcbP80I(Mr;Z;tCdns1nDX0sAS}>?+AjT+VL1^I zZf%Com2%hEsS?${6{{K7r9QX8RkiA?9`M8k)eJ6Q?3mk);OX4hXImgdTrWu*^L$5I z$GaDt({d?F5lB(1&=-Z2BD@|Sc^cLoBFmd(PBg;Lk}yx%q*y}OMWq*bu_99xgsem% zUk5+|aS^5X&?6Ocxnh5zm^g~!&nVDv#kQcBSZ!aQpSKh?)opPp$U;J4-7df1=|_VH z78F|uNC+~}JzV3#Fy8?le#~!%%)9GGirPW-egOT`Dfke2V{_*;=^vZhNB+N@-Ha)B z*NwBA>t7PtfBFL#$-XIk=a2T^W`9X$gLe_QhVs3&1n4K=AE$pSDubJYD*@k|H-VP# z?#=(v1^%x_gDn2KZW_%B+D*AtxxH!b!y);zms-?r4gMa7|Li63k@&{ZT`#Tt+wA|w zW$*@r@AKSqhD9FU^#LbcqWAwtt)5 zCsKe{8GJeR9zZ5_>-XQ?_Ft905hlL7ZX&FF7vVo-ivO%RxKQzp?OoKo{?GPawivwn z;Ah0|DKcdLRR1rbD8R6TGExKi;wMii6~3mW47^#I&{{T_@rxFYO5 zg&Z_de}M8}xWNTB?TAH1iCfOPmDpgb6Ca6z|w3Oi^A{Q=5@u?E-B zx~IV5K1g{m)Zp4f_Y`c1^8VWM?o F_doD~gRcMp diff --git a/assets/template/versions.json b/assets/template/versions.json new file mode 100644 index 0000000..98e2211 --- /dev/null +++ b/assets/template/versions.json @@ -0,0 +1,502 @@ +{ + "ADC": { + "skill_version": 1, + "template_version": 1 + }, + "AFE": { + "skill_version": 1, + "template_version": 1 + }, + "AMP": { + "skill_version": 1, + "template_version": 1 + }, + "ANC": { + "skill_version": 1, + "template_version": 1 + }, + "ANE": { + "skill_version": 1, + "template_version": 1 + }, + "ANP": { + "skill_version": 1, + "template_version": 1 + }, + "ARR": { + "skill_version": 1, + "template_version": 1 + }, + "BJT": { + "skill_version": 1, + "template_version": 1 + }, + "BMS": { + "skill_version": 1, + "template_version": 1 + }, + "BPK": { + "skill_version": 1, + "template_version": 1 + }, + "BRG": { + "skill_version": 1, + "template_version": 1 + }, + "BUZ": { + "skill_version": 1, + "template_version": 1 + }, + "CBB": { + "skill_version": 1, + "template_version": 1 + }, + "CBK": { + "skill_version": 1, + "template_version": 1 + }, + "CCO": { + "skill_version": 1, + "template_version": 1 + }, + "CER": { + "skill_version": 1, + "template_version": 1 + }, + "CFR": { + "skill_version": 1, + "template_version": 1 + }, + "CHG": { + "skill_version": 1, + "template_version": 1 + }, + "CHL": { + "skill_version": 1, + "template_version": 1 + }, + "CLF": { + "skill_version": 1, + "template_version": 1 + }, + "CLI": { + "skill_version": 1, + "template_version": 1 + }, + "CLK": { + "skill_version": 1, + "template_version": 1 + }, + "CMC": { + "skill_version": 1, + "template_version": 1 + }, + "CMP": { + "skill_version": 1, + "template_version": 1 + }, + "CNI": { + "skill_version": 1, + "template_version": 1 + }, + "CPL": { + "skill_version": 1, + "template_version": 1 + }, + "CTC": { + "skill_version": 1, + "template_version": 1 + }, + "CTX": { + "skill_version": 1, + "template_version": 1 + }, + "CWB": { + "skill_version": 1, + "template_version": 1 + }, + "DAC": { + "skill_version": 1, + "template_version": 1 + }, + "DCD": { + "skill_version": 1, + "template_version": 1 + }, + "DCM": { + "skill_version": 1, + "template_version": 1 + }, + "DIG": { + "skill_version": 1, + "template_version": 1 + }, + "DRV": { + "skill_version": 1, + "template_version": 1 + }, + "DSG": { + "skill_version": 1, + "template_version": 1 + }, + "DSW": { + "skill_version": 1, + "template_version": 1 + }, + "ELE": { + "skill_version": 1, + "template_version": 1 + }, + "ESD": { + "skill_version": 2, + "template_version": 2 + }, + "FAN": { + "skill_version": 1, + "template_version": 1 + }, + "FBD": { + "skill_version": 1, + "template_version": 1 + }, + "FFC": { + "skill_version": 1, + "template_version": 1 + }, + "FIX": { + "skill_version": 1, + "template_version": 1 + }, + "FLM": { + "skill_version": 1, + "template_version": 1 + }, + "FRD": { + "skill_version": 1, + "template_version": 1 + }, + "FSR": { + "skill_version": 1, + "template_version": 1 + }, + "FUS": { + "skill_version": 1, + "template_version": 1 + }, + "GAN": { + "skill_version": 1, + "template_version": 1 + }, + "GDT": { + "skill_version": 1, + "template_version": 1 + }, + "HDR": { + "skill_version": 1, + "template_version": 1 + }, + "HSK": { + "skill_version": 1, + "template_version": 1 + }, + "IDC": { + "skill_version": 1, + "template_version": 1 + }, + "IFC": { + "skill_version": 1, + "template_version": 1 + }, + "IGBT": { + "skill_version": 1, + "template_version": 1 + }, + "IND": { + "skill_version": 1, + "template_version": 1 + }, + "INV": { + "skill_version": 1, + "template_version": 1 + }, + "ISO": { + "skill_version": 1, + "template_version": 1 + }, + "JFET": { + "skill_version": 1, + "template_version": 1 + }, + "LCD": { + "skill_version": 1, + "template_version": 1 + }, + "LDO": { + "skill_version": 1, + "template_version": 1 + }, + "LED": { + "skill_version": 1, + "template_version": 1 + }, + "LOG": { + "skill_version": 1, + "template_version": 1 + }, + "MCU": { + "skill_version": 1, + "template_version": 1 + }, + "MEM": { + "skill_version": 1, + "template_version": 1 + }, + "MFR": { + "skill_version": 1, + "template_version": 1 + }, + "MMO": { + "skill_version": 1, + "template_version": 1 + }, + "MOR": { + "skill_version": 1, + "template_version": 1 + }, + "MOS": { + "skill_version": 1, + "template_version": 1 + }, + "MTR": { + "skill_version": 1, + "template_version": 1 + }, + "NTC": { + "skill_version": 1, + "template_version": 1 + }, + "OBC": { + "skill_version": 1, + "template_version": 1 + }, + "OLE": { + "skill_version": 1, + "template_version": 1 + }, + "OSC": { + "skill_version": 1, + "template_version": 1 + }, + "PBT": { + "skill_version": 1, + "template_version": 1 + }, + "PBZ": { + "skill_version": 1, + "template_version": 1 + }, + "PLY": { + "skill_version": 1, + "template_version": 1 + }, + "PMU": { + "skill_version": 1, + "template_version": 1 + }, + "POT": { + "skill_version": 1, + "template_version": 1 + }, + "PSU": { + "skill_version": 1, + "template_version": 1 + }, + "PTC": { + "skill_version": 1, + "template_version": 1 + }, + "PWC": { + "skill_version": 1, + "template_version": 1 + }, + "PWR": { + "skill_version": 1, + "template_version": 1 + }, + "RCM": { + "skill_version": 1, + "template_version": 1 + }, + "REC": { + "skill_version": 1, + "template_version": 1 + }, + "RFI": { + "skill_version": 1, + "template_version": 1 + }, + "RFM": { + "skill_version": 1, + "template_version": 1 + }, + "RLP": { + "skill_version": 1, + "template_version": 1 + }, + "RLR": { + "skill_version": 1, + "template_version": 1 + }, + "RLS": { + "skill_version": 1, + "template_version": 1 + }, + "RSF": { + "skill_version": 1, + "template_version": 1 + }, + "RSN": { + "skill_version": 1, + "template_version": 1 + }, + "RSW": { + "skill_version": 1, + "template_version": 1 + }, + "RSY": { + "skill_version": 1, + "template_version": 1 + }, + "SAW": { + "skill_version": 1, + "template_version": 1 + }, + "SCH": { + "skill_version": 1, + "template_version": 1 + }, + "SCM": { + "skill_version": 1, + "template_version": 1 + }, + "SCU": { + "skill_version": 1, + "template_version": 1 + }, + "SEN": { + "skill_version": 1, + "template_version": 1 + }, + "SFY": { + "skill_version": 1, + "template_version": 1 + }, + "SHA": { + "skill_version": 1, + "template_version": 1 + }, + "SHT": { + "skill_version": 1, + "template_version": 1 + }, + "SIC": { + "skill_version": 1, + "template_version": 1 + }, + "SIM": { + "skill_version": 1, + "template_version": 1 + }, + "SPK": { + "skill_version": 1, + "template_version": 1 + }, + "SPR": { + "skill_version": 1, + "template_version": 1 + }, + "SSR": { + "skill_version": 1, + "template_version": 1 + }, + "STE": { + "skill_version": 1, + "template_version": 1 + }, + "SUP": { + "skill_version": 1, + "template_version": 1 + }, + "SVO": { + "skill_version": 1, + "template_version": 1 + }, + "SVR": { + "skill_version": 1, + "template_version": 1 + }, + "SWI": { + "skill_version": 1, + "template_version": 1 + }, + "SWT": { + "skill_version": 1, + "template_version": 1 + }, + "TAN": { + "skill_version": 1, + "template_version": 1 + }, + "TBK": { + "skill_version": 1, + "template_version": 1 + }, + "TFR": { + "skill_version": 1, + "template_version": 1 + }, + "TFT": { + "skill_version": 1, + "template_version": 1 + }, + "TPD": { + "skill_version": 1, + "template_version": 1 + }, + "TVS": { + "skill_version": 1, + "template_version": 1 + }, + "USB": { + "skill_version": 1, + "template_version": 1 + }, + "VAR": { + "skill_version": 1, + "template_version": 1 + }, + "VRF": { + "skill_version": 1, + "template_version": 1 + }, + "WWR": { + "skill_version": 1, + "template_version": 1 + }, + "XCV": { + "skill_version": 1, + "template_version": 1 + }, + "XFM": { + "skill_version": 1, + "template_version": 1 + }, + "XTL": { + "skill_version": 1, + "template_version": 1 + }, + "ZEN": { + "skill_version": 1, + "template_version": 1 + } +} \ No newline at end of file diff --git a/config/gitea.env b/config/gitea.env index 7ce9892..eb9e970 100644 --- a/config/gitea.env +++ b/config/gitea.env @@ -1,10 +1,13 @@ -# Gitea connection for the datasheet-extractor skill. +# Gitea connection for the library-manager skill. # WARNING: contains a secret (GIT_TOKEN) in plaintext - keep this skill PRIVATE. # Use a token scoped to these repos (repository: write) and rotate periodically. GIT_HOST=gitea.vecmocon.com GIT_USER=nitishKumar -GIT_TOKEN= -# Target repos (flat layout, no project/version grouping): -DFS_REPO=nitishKumar/DFS -PARAMS_REPO=nitishKumar/Parameters -SKILL_ASSETS_REPO=nitishKumar/Skill_Assets +GIT_TOKEN=451bff1dc32202cbc0a371f8e5645079466d2120 +# Target repos — TWO now (set these to the exact repo names on your Gitea): +# SKILL_REPO : holds this skill's own files (SKILL.md, scripts, assets, ...). +# COMPONENTS_REPO : holds components, one folder per Class (Diode, IC, ...); inside each, +# one MPN_make_typeid/ folder per part with { xlsx, datasheet, symbol, +# footprint }. (Replaces the old DFS + Parameters repos.) +SKILL_REPO=nitishKumar/Skill_Assets +COMPONENTS_REPO=nitishKumar/Components diff --git a/references/taxonomy.md b/references/taxonomy.md index b62805c..6399891 100644 --- a/references/taxonomy.md +++ b/references/taxonomy.md @@ -1,7 +1,10 @@ # Component taxonomy — Class → Subclass (Type) → Type ID Use the **Type ID** as the `typeid` in `MPN_make_typeid`. Match each part to the closest -subclass. The output sheet name is the **Class** (template.xlsx sheet). Full source: +subclass. In the current template **each Type ID is its own sheet** (125 sheets), so the +template sheet name **is the Type ID** (e.g. `SCH`, `MOS`, `LDO`). The **Class** is used only +to organise the components repo into top-level folders (e.g. `SCH` lives under `Diode/`); +`scripts/common.py:class_folder()` maps a typeid to its folder name. Full source: `assets/template/Type_ID.xlsx`. diff --git a/scripts/__pycache__/common.cpython-311.pyc b/scripts/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4baa9cb4e256632024c76867543981ae0d332ebb GIT binary patch literal 10610 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 ``CAP_CGA3_TDK``. + +The four values that get written to the sheet: + Library Ref = name inside the .SchLib (read here) + Library Path = the .SchLib file name (just the basename) + Footprint Ref = base pattern inside the .PcbLib (read here) + Footprint Path = the .PcbLib file name (just the basename) + +Usage: + # one file -> its reference name + python altium_refs.py CGA3E3X7R1H474K080AE.SchLib + python altium_refs.py CGA3E3X7R1H474K080AE.PcbLib --json + + # both files -> ready-to-use design map for fill_templates --design + python altium_refs.py design --symbol X.SchLib --footprint Y.PcbLib > design.json + +Programmatic: from altium_refs import extract_ref, build_design + +If a ref comes back None or a footprint has several unrelated candidates, ask the engineer to +confirm the name shown in Altium's properties. +""" +import argparse, json, os, re +import olefile + +# Entry names that are container metadata, never a component/footprint reference. +META = { + "fileheader", "additionalfileheader", "storage", "library", "root entry", + "componentparamstoc", "fileversioninfo", "sectionkeys", "data", "header", + "uniqueidprimitiveinformation", "pintextdata", "textframe", "models", + "modelsnoembed", "arcs6", "pads6", "vias6", "tracks6", "texts6", "fills6", + "regions6", "componentbodies6", "designatoroverride", "parameters", + "primitiveguids", "widestrings", "layerkindmapping", "padvialibrary", + "embeddedfonts", "textures", "fileversion", +} + + +def _decode(raw): + for enc in ("utf-16-le", "latin-1", "utf-8"): + try: + return raw.decode(enc, errors="ignore") + except Exception: + continue + return "" + + +def _storage_names(ole): + """Distinct top-level storage names that aren't container metadata.""" + names = [] + for entry in ole.listdir(streams=True, storages=True): + top = entry[0] + if top.lower() in META or top in names: + continue + names.append(top) + return names + + +def _from_header_records(ole): + """Backstop: scan header streams for LibReference / PatternName records.""" + hits = [] + for entry in ole.listdir(streams=True): + if entry[-1].lower() not in ("fileheader", "header", "data"): + continue + try: + txt = _decode(ole.openstream(entry).read()) + except Exception: + continue + for key in ("LIBREFERENCE", "PATTERNNAME", "PATTERN"): + for m in re.finditer(rf"{key}=([^\x00|]+)", txt, re.IGNORECASE): + val = m.group(1).strip() + if val and val not in hits: + hits.append(val) + return hits + + +def _pick_base(candidates): + """Choose the primary reference. When several names share a base and differ only by a + trailing density suffix (Altium IPC variants: -L / -M / -N / -Least / -Most / -Nominal), + the base (a prefix of the others) is the real one. Otherwise take the first.""" + if not candidates: + return None + bases = [c for c in candidates + if any(o != c and o.startswith(c + "-") for o in candidates)] + if bases: + return sorted(bases, key=len)[0] + # no prefix relationship: drop obvious -L/-M/-N variants if a suffix-less peer exists + plain = [c for c in candidates if not re.search(r"-(L|M|N|Least|Most|Nominal)$", c)] + return (plain or candidates)[0] + + +def extract_ref(path): + """Return (ref, candidates): ref = best single reference name (base pattern) or None.""" + if not olefile.isOleFile(path): + return None, [] + ole = olefile.OleFileIO(path) + try: + candidates = _storage_names(ole) or _from_header_records(ole) + finally: + ole.close() + return _pick_base(candidates), candidates + + +def kind_of(path): + ext = os.path.splitext(path)[1].lower() + return {".schlib": "symbol", ".pcblib": "footprint"}.get(ext, "unknown") + + +def build_design(symbol_path, footprint_path): + """Assemble the four design-column values from the two uploaded files.""" + lib_ref, lib_cands = extract_ref(symbol_path) + fp_ref, fp_cands = extract_ref(footprint_path) + return { + "Library Ref": lib_ref, + "Library Path": os.path.basename(symbol_path), + "Footprint Ref": fp_ref, + "Footprint Path": os.path.basename(footprint_path), + "_candidates": {"symbol": lib_cands, "footprint": fp_cands}, + } + + +def main(): + ap = argparse.ArgumentParser() + sub = ap.add_subparsers(dest="cmd") + d = sub.add_parser("design", help="emit the 4-field design map from both files") + d.add_argument("--symbol", required=True) + d.add_argument("--footprint", required=True) + ap.add_argument("file", nargs="?", help=".SchLib or .PcbLib (single-file mode)") + ap.add_argument("--json", action="store_true") + a = ap.parse_args() + + if a.cmd == "design": + design = build_design(a.symbol, a.footprint) + warn = [k for k in ("Library Ref", "Footprint Ref") if not design[k]] + print(json.dumps(design, ensure_ascii=False, indent=2)) + if warn: + print(f"# WARNING: could not read {', '.join(warn)} - confirm from Altium", flush=True) + return + + if not a.file: + ap.error("give a file, or use: design --symbol X.SchLib --footprint Y.PcbLib") + ref, cands = extract_ref(a.file) + if a.json: + print(json.dumps({"kind": kind_of(a.file), "ref": ref, "candidates": cands}, ensure_ascii=False)) + return + if ref is None: + print(f"Could not read a reference from {a.file}. Open it in Altium and use the " + "name shown in properties.") + else: + print(ref) + if len(cands) > 1: + print(f"(variants present: {cands}; using base '{ref}')") + + +if __name__ == "__main__": + main() diff --git a/scripts/append_parameter.py b/scripts/append_parameter.py index 568ca94..c68383d 100644 --- a/scripts/append_parameter.py +++ b/scripts/append_parameter.py @@ -1,27 +1,99 @@ #!/usr/bin/env python3 -"""Append a new parameter (column) to the end of a type's sheet in the master -template, and bump the template VERSION. After running, push the updated -assets/template/template.xlsx + VERSION to the Skill_Assets repo. +"""Add one or more new parameters to a single typeid's template sheet, bump that typeid's +versions, and record the change in the global changelog. -Usage: python append_parameter.py --type Diode --param "Reverse Recovery Time(ns)" \ - --template assets/template/template.xlsx +Versioning is **per typeid**: adding a parameter to (say) SCH means there's a new SCH +template, so SCH's template_version bumps v1->v2, and on the back of that its skill_version +bumps v1->v2 too. Only SCH moves; every other typeid keeps its versions. Those two numbers +are what fill_templates later stamps into the SCH sheet's rows (columns B and C). + +The new column is appended at the END of the sheet (as requested), styled like the other +headers. Then versions.json is bumped and one entry is written to CHANGELOG.md. + + python append_parameter.py --typeid SCH \ + --param "Reverse Recovery Time(ns)" --param "Diode Capacitance(pF)" \ + --desc "Added Trr and Cd for SMPS rectifier derating" + +After running, push the updated template.xlsx + versions.json + CHANGELOG.md to the SKILL +repo (and, per the workflow, the same change flows to the components repo copies). """ -import argparse, os, openpyxl -ap=argparse.ArgumentParser() -ap.add_argument("--type", required=True); ap.add_argument("--param", required=True) -ap.add_argument("--template", required=True) -a=ap.parse_args() -wb=openpyxl.load_workbook(a.template) -if a.type not in wb.sheetnames: - raise SystemExit(f"no sheet '{a.type}' in template; sheets: {wb.sheetnames}") -ws=wb[a.type] -headers=[ws.cell(1,c).value for c in range(1, ws.max_column+1)] -if a.param in headers: - raise SystemExit(f"'{a.param}' already exists in {a.type}") -ws.cell(1, ws.max_column+1, a.param) # append at the end -wb.save(a.template) -vf=os.path.join(os.path.dirname(a.template), "VERSION") -v=int(open(vf).read().strip()) if os.path.exists(vf) else 1 -v+=1; open(vf,"w").write(str(v)) -print(f"Appended '{a.param}' to {a.type}. Template is now v{v}. " - f"Push {os.path.basename(a.template)} + VERSION to the Skill_Assets repo.") +import argparse, datetime, os +import openpyxl +from copy import copy +from common import (TEMPLATE_XLSX, CHANGELOG_MD, bump_versions, load_taxonomy) + + +def _style_header(new_cell, ref_cell): + """Make an appended header look like the sheet's existing headers.""" + for attr in ("font", "fill", "border", "alignment", "number_format"): + try: + setattr(new_cell, attr, copy(getattr(ref_cell, attr))) + except Exception: + pass + + +def append_params(template_path, typeid, params): + wb = openpyxl.load_workbook(template_path) + if typeid not in wb.sheetnames: + raise SystemExit(f"no sheet '{typeid}' in template; is it a valid typeid?") + ws = wb[typeid] + headers = [ws.cell(1, c).value for c in range(1, ws.max_column + 1)] + added = [] + for p in params: + if p in headers: + print(f" ! '{p}' already exists in {typeid} - skipping") + continue + col = ws.max_column + 1 + cell = ws.cell(1, col, p) + _style_header(cell, ws.cell(1, 1)) + headers.append(p) + added.append(p) + if not added: + raise SystemExit("nothing to add (all parameters already present)") + wb.save(template_path) + return added + + +def write_changelog(typeid, added, old, new, desc, when=None): + when = when or datetime.datetime.now() + folder = (load_taxonomy().get(typeid) or {}).get("folder", "?") + lines = [ + f"## {when.strftime('%Y-%m-%d %H:%M')} - {typeid} ({folder})", + f"- Template v{old['template_version']} -> v{new['template_version']}, " + f"Skill v{old['skill_version']} -> v{new['skill_version']}", + "- Added parameter(s): " + ", ".join(f'\"{p}\"' for p in added), + ] + if desc: + lines.append(f"- Description: {desc}") + entry = "\n".join(lines) + "\n\n" + header = "# library-manager - template/version changelog\n\n" + prior = "" + if os.path.exists(CHANGELOG_MD): + prior = open(CHANGELOG_MD, encoding="utf-8").read() + if prior.startswith(header): + prior = prior[len(header):] + open(CHANGELOG_MD, "w", encoding="utf-8").write(header + entry + prior) # newest on top + return CHANGELOG_MD + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--typeid", required=True, help="e.g. SCH, MOS, LDO") + ap.add_argument("--param", required=True, action="append", + help="new column header; repeat for several") + ap.add_argument("--desc", default="", help="what/why - goes in the changelog") + ap.add_argument("--template", default=TEMPLATE_XLSX) + a = ap.parse_args() + + added = append_params(a.template, a.typeid, a.param) + old, new = bump_versions(a.typeid) + cl = write_changelog(a.typeid, added, old, new, a.desc) + print(f"Added to {a.typeid}: {', '.join(added)}") + print(f"{a.typeid}: template v{old['template_version']}->v{new['template_version']}, " + f"skill v{old['skill_version']}->v{new['skill_version']}") + print(f"Changelog updated: {cl}") + print("Now push template.xlsx + versions.json + CHANGELOG.md to the SKILL repo.") + + +if __name__ == "__main__": + main() diff --git a/scripts/common.py b/scripts/common.py new file mode 100644 index 0000000..dffc88b --- /dev/null +++ b/scripts/common.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Shared helpers for the library-manager skill. + +One place for the three things every script needs to agree on: + +- the taxonomy (typeid -> Class -> components-repo folder name), read from + ``assets/template/Type_ID.xlsx`` so there is a single source of truth; +- the **per-typeid version store** (``assets/template/versions.json``) that holds each + typeid's own ``template_version`` and ``skill_version`` - both start at 1 and both bump + together the moment a parameter is added to that typeid (and nothing else moves); +- the part **tag** ``__`` that names every folder and column-A cell. + +Keeping these here means fill_templates / append_parameter / gitea_components can't drift +apart on how a version is read or a folder is named. +""" +import json, os, re +import openpyxl + +SKILL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TEMPLATE_DIR = os.path.join(SKILL_ROOT, "assets", "template") +TEMPLATE_XLSX= os.path.join(TEMPLATE_DIR, "template.xlsx") +TYPE_ID_XLSX = os.path.join(TEMPLATE_DIR, "Type_ID.xlsx") +VERSIONS_JSON= os.path.join(TEMPLATE_DIR, "versions.json") +CHANGELOG_MD = os.path.join(TEMPLATE_DIR, "CHANGELOG.md") + +# Fixed template columns that live on every sheet and are NOT extracted from a datasheet. +COL_TAG = "MPN_make_type" # column A header (the part tag lives here) +COL_SKILL_VER = "Skill Version" # column B (per-typeid skill version, stamped by us) +COL_TEMPLATE_VER = "Template Version" # column C (per-typeid template version, stamped by us) +DESIGN_COLS = ("Library Ref", "Library Path", "Footprint Ref", "Footprint Path") + +# Raw taxonomy Class name -> clean, filesystem-safe folder name for the components repo. +# These match Vecmocon's established sheet-name convention (the old 18-class template). +CLASS_FOLDER = { + "Resistor": "Resistor", + "Capacitor": "Capacitor", + "Inductor / Magnetics": "Inductor_Magnetics", + "Diode": "Diode", + "Transistor": "Transistor", + "Integrated Circuit (IC)": "IC", + "Protection Device": "Protection", + "Power Conversion Module": "Power Conversion Module", + "Relay / Contactor": "Relay_Contactor", + "Switch / Button": "Switch_Button", + "Connector": "Connector", + "Antenna / RF": "Antenna_RF", + "Crystal / Oscillator / Timing": "Crystal_Oscillator", + "Battery / Cell": "Battery_Cell", + "Audible / Indicator": "Audible_Indicator", + "Display / HMI": "Display_HMI", + "Sensor (discrete / module)": "Sensor", + "Thermal / Cooling": "Thermal_Cooling", +} + + +def norm(s): + return re.sub(r"[^a-z0-9]", "", str(s).lower()) + + +# ---------------------------------------------------------------- taxonomy + +def load_taxonomy(type_id_xlsx=TYPE_ID_XLSX): + """Return {typeid: {"class": rawclass, "subclass": name, "folder": foldername}}. + + Reads the Type_ID.xlsx. A real mapping row has all three of Class / Subclass / Type ID; + the group-header rows (only a Class label like 'Resistor (12 types)') are skipped + because they have no Type ID. + """ + wb = openpyxl.load_workbook(type_id_xlsx, read_only=True, data_only=True) + ws = wb[wb.sheetnames[0]] + out = {} + for row in ws.iter_rows(min_row=1, values_only=True): + cells = (list(row) + [None, None, None])[:3] + cls, sub, tid = (str(c).strip() if c is not None else "" for c in cells) + if not tid or tid.lower() in ("type id",): + continue + folder = CLASS_FOLDER.get(cls, re.sub(r"\s*/\s*", "_", re.sub(r"\s*\(.*?\)", "", cls)).strip().replace(" ", "_")) + out[tid] = {"class": cls, "subclass": sub, "folder": folder} + return out + + +def class_folder(typeid, taxonomy=None): + """The components-repo top-level folder name for a typeid (e.g. SCH -> 'Diode').""" + tax = taxonomy or load_taxonomy() + info = tax.get(typeid) + return info["folder"] if info else None + + +# ---------------------------------------------------------------- part tag + +def make_tag(manufacturer): + """The 'make' token = first word of the manufacturer, stripped to alphanumerics.""" + w = re.split(r"\s+", str(manufacturer).strip()) + first = w[0] if w and w[0] else "NA" + return re.sub(r"[^A-Za-z0-9]", "", first) or "NA" + + +def part_tag(part): + """Identity of a part: __. Same tag = same part.""" + make = part.get("make") or make_tag(part.get("manufacturer", "")) + return f'{part.get("mpn","")}_{make}_{part.get("typeid","NA")}' + + +def mpn_make_prefix(mpn, make): + """The __ prefix used for the early duplicate check (typeid not yet known).""" + return f"{mpn}_{make}_" + + +# ---------------------------------------------------------------- version store + +def _load_versions(): + if os.path.exists(VERSIONS_JSON): + try: + return json.load(open(VERSIONS_JSON, encoding="utf-8")) + except Exception: + pass + return {} + + +def get_versions(typeid): + """Return {'template_version': int, 'skill_version': int} for a typeid (default 1/1).""" + v = _load_versions().get(typeid) or {} + return {"template_version": int(v.get("template_version", 1)), + "skill_version": int(v.get("skill_version", 1))} + + +def version_labels(typeid): + """Return ('vN', 'vN') = (skill_version, template_version) strings for stamping cells.""" + v = get_versions(typeid) + return f'v{v["skill_version"]}', f'v{v["template_version"]}' + + +def bump_versions(typeid): + """Increment BOTH template_version and skill_version for one typeid; persist and return + (old, new) each as {'template_version','skill_version'}. Only this typeid changes.""" + data = _load_versions() + cur = data.get(typeid) or {"template_version": 1, "skill_version": 1} + old = {"template_version": int(cur.get("template_version", 1)), + "skill_version": int(cur.get("skill_version", 1))} + new = {"template_version": old["template_version"] + 1, + "skill_version": old["skill_version"] + 1} + data[typeid] = new + json.dump(data, open(VERSIONS_JSON, "w", encoding="utf-8"), indent=2, sort_keys=True) + return old, new + + +def init_versions(force=False): + """Seed versions.json with 1/1 for every typeid in the taxonomy (idempotent).""" + data = {} if force else _load_versions() + for tid in load_taxonomy(): + data.setdefault(tid, {"template_version": 1, "skill_version": 1}) + json.dump(data, open(VERSIONS_JSON, "w", encoding="utf-8"), indent=2, sort_keys=True) + return data + + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1 and sys.argv[1] == "init": + d = init_versions(force="--force" in sys.argv) + print(f"versions.json seeded with {len(d)} typeids at {VERSIONS_JSON}") + else: + tax = load_taxonomy() + print(f"taxonomy: {len(tax)} typeids, {len(set(v['folder'] for v in tax.values()))} class folders") diff --git a/scripts/fill_templates.py b/scripts/fill_templates.py index 716d570..5790566 100644 --- a/scripts/fill_templates.py +++ b/scripts/fill_templates.py @@ -1,143 +1,105 @@ #!/usr/bin/env python3 -"""Fill the reference template into ONE master workbook (Components_Master.xlsx). +"""Build ONE per-part Excel sheet from the master template. -The master contains ONLY the type sheets that actually have parts (the components whose -datasheets were provided) - built from a copy of the template so each kept sheet keeps its -exact headers, styling, widths and freeze - plus a single **Meta** sheet appended at the -END with: Template Version, Total Components, Date, Time, and a per-sheet count breakdown. -Empty template sheets are dropped. Column A = __. +The new Vecmocon layout stores each component on its own: a folder ``MPN_make_typeid/`` +holding that part's own single-sheet workbook (plus its datasheet, symbol and footprint). +There is no accumulating master workbook any more - so this script takes one part and writes +``__.xlsx``, a copy of just that typeid's template sheet (headers, styling, +widths, freeze preserved) with a single filled data row. -Shared helpers (part_tag, part_to_row, template_headers, sheet_rows, read_all_rows, -build_master, resolve_version, MASTER_NAME) are imported by gitea_reconcile.py so the Gitea -master is built and merged the exact same way. +What gets stamped automatically (never read from a datasheet): +- column A ``MPN_make_type`` = the part tag ``__`` +- column B ``Skill Version`` = this typeid's skill version (vN, from versions.json) +- column C ``Template Version``= this typeid's template version (vN, from versions.json) - python fill_templates.py parts.json --template --dest [--version v1] +The four design columns (Library Ref/Path, Footprint Ref/Path) are filled from a --design +map when the symbol/footprint step provides them, and left blank otherwise. Everything else +comes from the extracted ``values``. + + # one part, values only (design columns left blank for now) + python fill_templates.py part.json --template assets/template/template.xlsx --dest out/ + # later, same part with the Altium-derived design fields merged in + python fill_templates.py part.json --template ... --dest out/ --design design.json + +``part.json`` is either a single part object or {"parts": [ ... ]} (each part built to its +own file). A part = {"mpn","manufacturer" (or "make"),"typeid","values":{header:value,...}}. """ -import argparse, json, os, re, datetime +import argparse, json, os import openpyxl -from openpyxl.styles import Font, PatternFill, Border, Side, Alignment -from openpyxl.utils import get_column_letter +from common import (norm, part_tag, version_labels, + COL_TAG, COL_SKILL_VER, COL_TEMPLATE_VER, DESIGN_COLS) -GREEN="B6D7A8"; GRAY="BFBFBF" -MASTER_NAME="Components_Master.xlsx"; META_SHEET="Meta" -def norm(s): return re.sub(r'[^a-z0-9]', '', str(s).lower()) -ALIAS={"induactanceuh":"inductanceuh"} # template header typo -> value key -def make_tag(manufacturer): - w=re.split(r'\s+', str(manufacturer).strip()); first=w[0] if w and w[0] else "NA" - return re.sub(r'[^A-Za-z0-9]', '', first) or "NA" -def part_tag(part): - """Identity of a part: __. Same tag = same part.""" - make=part.get("make") or make_tag(part.get("manufacturer","")) - return f'{part.get("mpn","")}_{make}_{part.get("typeid","NA")}' +def template_headers(template_path, typeid): + """Canonical headers for a typeid sheet, or None if the template has no such sheet.""" + wb = openpyxl.load_workbook(template_path, read_only=True) + if typeid not in wb.sheetnames: + return None + ws = wb[typeid] + return [ws.cell(1, c).value for c in range(1, ws.max_column + 1)] -def template_headers(template_path, ctype): - """Canonical data headers for a type sheet, or None if the template has no such sheet.""" - wb=openpyxl.load_workbook(template_path) - if ctype not in wb.sheetnames: return None - ws=wb[ctype]; return [ws.cell(1,c).value for c in range(1, ws.max_column+1)] -def template_types(template_path): - return [s for s in openpyxl.load_workbook(template_path).sheetnames if s!=META_SHEET] +def part_to_row(part, headers, design=None): + """Map one part to {header: value}, following the fixed-column rules above.""" + vals = {norm(k): v for k, v in part.get("values", {}).items()} + dmap = {norm(k): v for k, v in (design or {}).items()} + tag = part_tag(part) + sver, tver = version_labels(part.get("typeid", "NA")) + row = {} + for h in headers: + key = norm(h) + if key == norm(COL_TAG): row[h] = tag + elif key == norm(COL_SKILL_VER): row[h] = sver + elif key == norm(COL_TEMPLATE_VER): row[h] = tver + elif key == norm("Manufacturer"): row[h] = part.get("manufacturer", vals.get("manufacturer", "")) + elif h in DESIGN_COLS: row[h] = dmap.get(key, vals.get(key, "")) + else: row[h] = vals.get(key, "") + return tag, row -def part_to_row(part, headers): - """Return (tag, {header: value}) for one part, following the column rules.""" - vals={norm(k):v for k,v in part.get("values",{}).items()}; tag=part_tag(part); row={} - for c,h in enumerate(headers, start=1): - key=ALIAS.get(norm(h),norm(h)) - if c==1: row[h]=tag - elif key=="class": row[h]=part.get("subclass","") - elif key=="manufacturer":row[h]=part.get("manufacturer", vals.get("manufacturer","")) - else: row[h]=vals.get(key,"") - return tag,row -def sheet_rows(ws, width): - """Existing data rows in a sheet -> (headers, {tag: {header: value}}), bounded to `width`.""" - headers=[ws.cell(1,c).value for c in range(1,width+1)]; rows={} - for r in range(2, ws.max_row+1): - tag=ws.cell(r,1).value - if tag in (None,""): continue - rows[tag]={headers[c-1]: ws.cell(r,c).value for c in range(1,width+1)} - return headers, rows - -def read_all_rows(master_path, template_path): - """Read every type sheet of an existing master -> {ctype: {tag: rowdict}} (skips Meta).""" - wb=openpyxl.load_workbook(master_path); types=set(template_types(template_path)); out={} - for name in wb.sheetnames: - if name not in types: continue - th=template_headers(template_path,name); w=len(th) if th else 0 - if not w: continue - _,rows=sheet_rows(wb[name], w) - if rows: out[name]=rows - return out - -def _thin(): s=Side(style="thin",color=GRAY); return Border(left=s,right=s,top=s,bottom=s) -def _hdr(cell): - cell.font=Font(name="Calibri",bold=True); cell.fill=PatternFill("solid",fgColor=GREEN) - cell.border=_thin(); cell.alignment=Alignment(horizontal="center",vertical="center") - -def _write_rows(ws, headers, rows_by_tag): - for r in range(2, ws.max_row+1): - for c in range(1, len(headers)+1): ws.cell(r,c).value=None - r=2 - for tag,row in rows_by_tag.items(): - for c,h in enumerate(headers, start=1): ws.cell(r,c, row.get(h,"")) - r+=1 - -def _build_meta_sheet(wb, version, counts, when): - if META_SHEET in wb.sheetnames: del wb[META_SHEET] - ws=wb.create_sheet(META_SHEET) # appended at the end - for r,(k,v) in enumerate([("Template Version",version), - ("Total Components",sum(counts.values())), - ("Date",when.strftime("%Y-%m-%d")), - ("Time",when.strftime("%H:%M:%S"))], start=1): - a=ws.cell(r,1,k); a.font=Font(name="Calibri",bold=True) - a.fill=PatternFill("solid",fgColor=GREEN); a.border=_thin() - ws.cell(r,2,v).border=_thin() - r=6 - for c,t in ((1,"Sheet"),(2,"Components")): _hdr(ws.cell(r,c,t)) - for ctype,n in counts.items(): - r+=1; ws.cell(r,1,ctype).border=_thin(); ws.cell(r,2,n).border=_thin() - ws.column_dimensions["A"].width=20; ws.column_dimensions["B"].width=16 - return ws - -def resolve_version(template_path, version): - if version: return version - vf=os.path.join(os.path.dirname(template_path),"VERSION") - return "v"+open(vf).read().strip() if os.path.exists(vf) else "v1" - -def build_master(template_path, rows_by_type, version, dest_dir): - """Write the single master workbook from a copy of the template. `rows_by_type` = - {ctype: {tag: rowdict}} is the COMPLETE desired contents. Only sheets present (with at - least one row) are kept; all other template sheets are dropped. A Meta sheet is appended - last. Returns the output path.""" +def build_part_sheet(part, template_path, dest_dir, design=None): + """Write ``.xlsx`` = just this part's typeid sheet with one filled row. Returns + (path, tag). Reused for backfill: re-extract a part and call this again to regenerate its + sheet against the current template (new columns) and current stamped versions.""" + typeid = part["typeid"] + headers = template_headers(template_path, typeid) + if headers is None: + raise SystemExit(f"no template sheet for typeid '{typeid}'") + wb = openpyxl.load_workbook(template_path) + for name in list(wb.sheetnames): # keep only this typeid's sheet + if name != typeid: + del wb[name] + ws = wb[typeid] + tag, row = part_to_row(part, headers, design) + for r in range(2, ws.max_row + 1): # clear any stray rows below the header + for c in range(1, len(headers) + 1): + ws.cell(r, c).value = None + for c, h in enumerate(headers, start=1): + ws.cell(2, c, row.get(h, "")) os.makedirs(dest_dir, exist_ok=True) - wb=openpyxl.load_workbook(template_path); when=datetime.datetime.now(); counts={} - for ctype in template_types(template_path): - ws=wb[ctype]; rows=rows_by_type.get(ctype) or {} - if rows: - th=template_headers(template_path,ctype); width=len(th) if th else ws.max_column - _write_rows(ws, [ws.cell(1,c).value for c in range(1,width+1)], rows) - counts[ctype]=len(rows) - _build_meta_sheet(wb, version, counts, when) # append first so a sheet always remains - for ctype in template_types(template_path): - if ctype not in counts: del wb[ctype] # drop empty type sheets - out=os.path.join(dest_dir, MASTER_NAME); wb.save(out); return out + out = os.path.join(dest_dir, f"{tag}.xlsx") + wb.save(out) + return out, tag + + +def _iter_parts(data): + return data["parts"] if isinstance(data, dict) and "parts" in data else [data] + def main(): - ap=argparse.ArgumentParser() - ap.add_argument("parts_json"); ap.add_argument("--template", required=True) - ap.add_argument("--dest", required=True); ap.add_argument("--version", default=None) - a=ap.parse_args() - data=json.load(open(a.parts_json, encoding="utf-8")) - version=resolve_version(a.template, a.version); by={} - for p in data.get("parts", []): - headers=template_headers(a.template, p["type"]) - if headers is None: print(f"! no template sheet for type '{p['type']}' - skipping"); continue - tag,row=part_to_row(p, headers); by.setdefault(p["type"],{})[tag]=row - out=build_master(a.template, by, version, a.dest) - total=sum(len(v) for v in by.values()) - print(f"master: {out} ({total} part(s) across {len(by)} sheet(s) + Meta, template {version})") + ap = argparse.ArgumentParser() + ap.add_argument("part_json") + ap.add_argument("--template", required=True) + ap.add_argument("--dest", required=True) + ap.add_argument("--design", default=None, help="JSON map of design columns to merge in") + a = ap.parse_args() + data = json.load(open(a.part_json, encoding="utf-8")) + design = json.load(open(a.design, encoding="utf-8")) if a.design else None + for p in _iter_parts(data): + out, tag = build_part_sheet(p, a.template, a.dest, design) + sver, tver = version_labels(p.get("typeid", "NA")) + print(f"wrote {out} (tag {tag}, skill {sver}, template {tver})") -if __name__=="__main__": + +if __name__ == "__main__": main() diff --git a/scripts/gitea_components.py b/scripts/gitea_components.py new file mode 100644 index 0000000..868163b --- /dev/null +++ b/scripts/gitea_components.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""Talk to the two Gitea repos of the new library-manager layout. + +Repos (from config/gitea.env): + SKILL_REPO - this skill's own files (pushed with push_to_gitea.sh, not here). + COMPONENTS_REPO - one folder per Class (Diode, IC, Transistor, ...); inside each Class, + one MPN_make_typeid/ folder per part holding { xlsx, datasheet, symbol, + footprint }. + +Subcommands (all accept --local to run against a local folder instead of cloning, for +testing or offline work): + + check-mpn --mpn BAT46WJ --make Nexperia + Is any part with this MPN+make already in the components repo? (typeid-agnostic - the + early duplicate gate, run BEFORE classifying.) Prints EXISTS/ or ABSENT; exit 0 + if absent, 3 if it already exists (so a shell can hard-stop). + + checkout --dest work/ + Clone the components repo to a working dir you can browse and edit in place. + + list-type --typeid SCH [--root work/] [--json] + List every existing part of a typeid and the files in its folder (for backfill: the + datasheet to re-read is in there). Needs a checked-out --root (or --local). + + place-part --folder staging/BAT46WJ_Nexperia_SCH --typeid SCH --root work/ + Copy an assembled part folder into work/// (creates the Class folder if it + is missing). Then commit-push. + + commit-push --root work/ [--message "..."] + Commit everything in the checked-out clone and push. + + push-part --folder staging/BAT46WJ_Nexperia_SCH --typeid SCH [--message "..."] + Convenience: clone -> place-part -> commit-push in one go (the common single-part path). + +Host unreachable (e.g. sandbox without the domain allowlisted) -> clones fail clearly and +nothing is written. +""" +import argparse, os, re, shutil, subprocess, sys, tempfile +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from common import class_folder, load_taxonomy, mpn_make_prefix + +CFG_DEFAULT = os.path.join(os.path.dirname(__file__), "..", "config", "gitea.env") + + +# ------------------------------------------------------------------ gitea plumbing + +def load_env(cfg): + env = {} + if os.path.exists(cfg): + for line in open(cfg): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + env[k.strip()] = v.strip() + for k in ("GIT_HOST", "GIT_USER", "GIT_TOKEN", "SKILL_REPO", "COMPONENTS_REPO"): + if os.environ.get(k): + env[k] = os.environ[k] + return env + + +def repo_url(env, repo): + host = re.sub(r"^https?://", "", env["GIT_HOST"]).rstrip("/") + cred = (env.get("GIT_USER", "") + ":" if env.get("GIT_USER") else "") + env["GIT_TOKEN"] + return f"https://{cred}@{host}/{repo}.git" + + +def clone(env, dest): + url = repo_url(env, env["COMPONENTS_REPO"]) + r = subprocess.run(["git", "clone", url, dest], capture_output=True, text=True) + if r.returncode != 0: + err = (r.stderr or "").replace(env["GIT_TOKEN"], "***") + sys.exit(f"clone COMPONENTS failed (host reachable / token scope / repo exists?):\n{err[:400]}") + return dest + + +def commit_push(root, token, msg): + for args in (["config", "user.email", "datasheet-bot@local"], + ["config", "user.name", "library-manager"], + ["checkout", "-B", "main"], ["add", "-A"]): + subprocess.run(["git", "-C", root] + args, capture_output=True) + if subprocess.run(["git", "-C", root, "diff", "--cached", "--quiet"]).returncode == 0: + print("nothing new to push.") + return + subprocess.run(["git", "-C", root, "commit", "-m", msg], capture_output=True) + r = subprocess.run(["git", "-C", root, "push", "-u", "origin", "main"], + capture_output=True, text=True) + print((r.stdout + r.stderr).replace(token or "", "***").strip() or "pushed.") + + +def _root(env, args): + """Return (root_dir, is_temp_clone). Honour --local / --root, else clone.""" + if getattr(args, "local", None): + return args.local, False + if getattr(args, "root", None): + return args.root, False + tmp = tempfile.mkdtemp(prefix="components_") + return clone(env, tmp), True + + +# ------------------------------------------------------------------ folder logic + +def find_part_dirs(root, prefix="", typeid=None): + """Yield (class_folder, part_folder_name, abspath) for part folders under any Class dir + that match a name prefix and/or a typeid suffix.""" + if not os.path.isdir(root): + return + for cls in sorted(os.listdir(root)): + cdir = os.path.join(root, cls) + if not os.path.isdir(cdir) or cls.startswith("."): + continue + for name in sorted(os.listdir(cdir)): + pdir = os.path.join(cdir, name) + if not os.path.isdir(pdir): + continue + if prefix and not name.startswith(prefix): + continue + if typeid and not name.endswith(f"_{typeid}"): + continue + yield cls, name, pdir + + +def cmd_check_mpn(env, args): + root, _ = _root(env, args) + prefix = mpn_make_prefix(args.mpn, args.make) + matches = [(cls, name) for cls, name, _ in find_part_dirs(root, prefix=prefix)] + if matches: + for cls, name in matches: + print(f"EXISTS\t{cls}/{name}") + sys.exit(3) + print("ABSENT") + + +def cmd_checkout(env, args): + dest = clone(env, args.dest) + print(f"components repo checked out at {dest}") + + +def cmd_list_type(env, args): + root, _ = _root(env, args) + parts = list(find_part_dirs(root, typeid=args.typeid)) + if args.json: + import json + out = [{"class": cls, "tag": name, + "folder": pdir, + "files": sorted(os.listdir(pdir))} for cls, name, pdir in parts] + print(json.dumps(out, indent=2)) + return + if not parts: + print(f"no existing parts of typeid {args.typeid}") + return + for cls, name, pdir in parts: + files = ", ".join(sorted(os.listdir(pdir))) + print(f"{cls}/{name} -> {files}") + + +def place_part(root, folder, typeid): + cls = class_folder(typeid) + if not cls: + sys.exit(f"unknown typeid '{typeid}' (not in taxonomy)") + tag = os.path.basename(os.path.normpath(folder)) + cls_dir = os.path.join(root, cls) + # A repo may seed classes as 0-byte placeholder FILES (git can't store empty dirs). + # Replace such a placeholder with a real class directory before adding the part. + if os.path.exists(cls_dir) and not os.path.isdir(cls_dir): + os.remove(cls_dir) + os.makedirs(cls_dir, exist_ok=True) + dest = os.path.join(cls_dir, tag) + if os.path.isdir(dest): + shutil.rmtree(dest) + shutil.copytree(folder, dest) + return cls, tag, dest + + +def cmd_place_part(env, args): + root, _ = _root(env, args) + cls, tag, dest = place_part(root, args.folder, args.typeid) + print(f"placed {tag} -> {cls}/{tag}") + + +def cmd_commit_push(env, args): + commit_push(args.root, env.get("GIT_TOKEN", ""), args.message or "library-manager: update components") + + +def cmd_push_part(env, args): + tmp = tempfile.mkdtemp(prefix="components_") + clone(env, tmp) + cls, tag, _ = place_part(tmp, args.folder, args.typeid) + print(f"placed {tag} -> {cls}/{tag}") + commit_push(tmp, env.get("GIT_TOKEN", ""), args.message or f"library-manager: add {tag}") + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--config", default=CFG_DEFAULT) + sub = ap.add_subparsers(dest="cmd", required=True) + + p = sub.add_parser("check-mpn"); p.add_argument("--mpn", required=True) + p.add_argument("--make", required=True); p.add_argument("--local"); p.add_argument("--root") + + p = sub.add_parser("checkout"); p.add_argument("--dest", required=True) + + p = sub.add_parser("list-type"); p.add_argument("--typeid", required=True) + p.add_argument("--root"); p.add_argument("--local"); p.add_argument("--json", action="store_true") + + p = sub.add_parser("place-part"); p.add_argument("--folder", required=True) + p.add_argument("--typeid", required=True); p.add_argument("--root"); p.add_argument("--local") + + p = sub.add_parser("commit-push"); p.add_argument("--root", required=True) + p.add_argument("--message") + + p = sub.add_parser("push-part"); p.add_argument("--folder", required=True) + p.add_argument("--typeid", required=True); p.add_argument("--message") + + args = ap.parse_args() + env = load_env(args.config) + if args.cmd in ("check-mpn", "checkout", "commit-push", "push-part") or \ + (args.cmd in ("list-type", "place-part") and not getattr(args, "local", None) and not getattr(args, "root", None)): + for k in ("GIT_HOST", "GIT_TOKEN", "COMPONENTS_REPO"): + if not env.get(k): + sys.exit(f"missing {k} in env/config") + {"check-mpn": cmd_check_mpn, "checkout": cmd_checkout, "list-type": cmd_list_type, + "place-part": cmd_place_part, "commit-push": cmd_commit_push, "push-part": cmd_push_part}[args.cmd](env, args) + + +if __name__ == "__main__": + main() diff --git a/scripts/push_to_gitea.sh b/scripts/push_to_gitea.sh index 118985e..53f7a02 100644 --- a/scripts/push_to_gitea.sh +++ b/scripts/push_to_gitea.sh @@ -20,7 +20,7 @@ while [[ $# -gt 0 ]]; do case "$1" in *) echo "unknown arg: $1" >&2; exit 2;; esac; done [[ -z "$REPO" || -z "$SRC" || -z "$HOST" || -z "$TOKEN" ]] && { echo "need --repo, --src, and GIT_HOST/GIT_TOKEN" >&2; exit 2; } [[ -d "$SRC" ]] || { echo "ERROR: src not found: $SRC" >&2; exit 2; } -[[ -z "$MSG" ]] && MSG="Update from datasheet-extractor" +[[ -z "$MSG" ]] && MSG="Update from library-manager" HOST="${HOST#http://}"; HOST="${HOST#https://}"; HOST="${HOST%/}" CRED="${USER_:+$USER_:}${TOKEN}" URL="https://${CRED}@${HOST}/${REPO}.git" @@ -30,7 +30,7 @@ git clone "$URL" "$WORK" 2>&1 | scrub || { echo "ERROR: clone failed (repo must cd "$WORK"; git checkout -B main >/dev/null 2>&1 || true DEST="$WORK${SUBDIR:+/$SUBDIR}"; mkdir -p "$DEST" cp -r "$SRC/." "$DEST/" -git config user.email "datasheet-bot@local"; git config user.name "datasheet-extractor" +git config user.email "datasheet-bot@local"; git config user.name "library-manager" git add -A if git diff --cached --quiet; then echo "Nothing new for $REPO."; else git commit -m "$MSG" >/dev/null; git push -u origin main 2>&1 | scrub