Skip to content

feat: add kilm import command for SamacSys/Mouser/Ultralibrarian ZIPs#9

Open
monkey265 wants to merge 5 commits into
barisgit:mainfrom
monkey265:feature/import-zip
Open

feat: add kilm import command for SamacSys/Mouser/Ultralibrarian ZIPs#9
monkey265 wants to merge 5 commits into
barisgit:mainfrom
monkey265:feature/import-zip

Conversation

@monkey265

@monkey265 monkey265 commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Added kilm import command that imports KiCad component ZIPs downloaded from Mouser or SamacSys directly into the configured library.

Usage: kilm import [OPTIONS] ZIP_FILES... COMMAND [ARGS]...

Import SamacSys/Mouser KiCad ZIP(s) into the library

╭─ Arguments ──────────────────────────────────────────────────────────╮
│ *    zip_files...      PATH  SamacSys/Mouser ZIP file(s) to import   │
╰──────────────────────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────────────────────╮
│ --library    -l      TEXT  Target library name (default: first       │
│                            github library)                           │
│ --kicad-cli          PATH  Path to kicad-cli or kicad.appimage for   │
│                            format upgrade                            │
│ --dry-run                  Show what would be imported without       │
│                            making changes                            │
╰──────────────────────────────────────────────────────────────────────╯
  • Extracts 3D models (.stp/.step) → LIB.3dshapes/
  • Upgrades legacy (module ...) footprints to current format via kicad-cli (optional, skipped gracefully if not found)
  • Merges symbols into symbols/LIB.kicad_sym with correct LIB: footprint prefix
  • Skips duplicates (safe to re-run)
  • Fixes zip-slip vulnerability for Python < 3.12

Test plan

  • 11 tests added covering symbol merge, footprint path fixing, dry-run, duplicate skipping, zip-slip guard
  • black, ruff check, pytest all pass

Summary by CodeRabbit

  • New Features

    • Added a new CLI subcommand to import SamacSys/Mouser KiCad ZIPs into your library.
    • Merges missing symbols, footprints, and related 3D models, with a --dry-run preview and detailed add/skip reporting.
  • Bug Fixes

    • Hardened ZIP extraction against path traversal (ZIP Slip) issues.
    • Improved symbol parsing and footprint/3D model reference rewriting, including normalization to KiCad’s ${KICAD_3RD_PARTY} layout.
  • Tests

    • Added unit and CLI integration tests for safe extraction, parsing/rewriting behavior, correct skip/add outcomes, and dry-run vs. real writes.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

@monkey265 is attempting to deploy a commit to the Paxia LLC Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a kilm import command that imports KiCad ZIP archives into a library, merges symbols and footprints, rewrites 3D model paths, supports dry-run and kicad-cli upgrades, and includes CLI and helper tests.

Changes

Import ZIP feature

Layer / File(s) Summary
Symbol and footprint helpers
kicad_lib_manager/commands/import_zip/command.py
Adds helpers for symbol-block parsing, footprint reference rewriting, 3D path normalization, legacy format upgrades, and ZIP-slip-safe extraction.
Archive import flow
kicad_lib_manager/commands/import_zip/command.py
Implements per-archive extraction, model and footprint copying, symbol merging, and kicad-cli detection.
CLI command and registration
kicad_lib_manager/commands/import_zip/command.py, kicad_lib_manager/commands/import_zip/__init__.py, kicad_lib_manager/main.py
Adds the Typer command wrapper, exports the sub-app, and registers kilm import on the main CLI.
Import ZIP tests
tests/test_import_zip_command.py
Adds unit and integration tests for helper behavior, dry-run handling, add/skip cases, and ZIP-based imports.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

A rabbit hops with ZIP in paw,
Unpacking parts without a flaw;
Symbols nest and footprints fit,
3D models now line up just right,
Dry-run blinks, then hops away,
Hooray for KiCad import day!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a kilm import command for SamacSys, Mouser, and UltraLibrarian ZIPs.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
tests/test_import_zip_command.py (1)

174-229: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for the kicad-cli footprint upgrade path.

All three CLI integration tests patch _detect_kicad_cli to return None, so the kicad-cli-based legacy footprint upgrade path — a distinct feature described in the PR objectives — is never exercised end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_import_zip_command.py` around lines 174 - 229, Add an end-to-end
CLI integration test that exercises the kicad-cli footprint upgrade path in
import_zip.command instead of patching _detect_kicad_cli to None, so the legacy
footprint conversion flow is covered. Reuse the existing import/app runner setup
and a Samacsys ZIP fixture, then assert the upgraded footprint is produced
through the kicad-cli branch and the expected output reflects that path. Keep
the current no-cli tests, but add a separate test that specifically validates
the _detect_kicad_cli-driven behavior.

Source: Coding guidelines

kicad_lib_manager/commands/import_zip/command.py (3)

139-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate kicad-cli command-building logic.

The .appimage vs. plain-binary branching to build cmd is duplicated almost verbatim between _upgrade_fp and _upgrade_sym. Extract a small helper, e.g. _build_kicad_cli_cmd(kicad_cli: Path, *args: str) -> list[str], to avoid the two copies drifting.

♻️ Proposed helper
+def _build_kicad_cli_cmd(kicad_cli: Path, *args: str) -> list[str]:
+    if kicad_cli.suffix.lower() == ".appimage":
+        return [str(kicad_cli), "kicad-cli", *args]
+    return [str(kicad_cli), *args]

Also applies to: 171-174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kicad_lib_manager/commands/import_zip/command.py` around lines 139 - 159, The
`cmd` construction in `_upgrade_fp` and `_upgrade_sym` is duplicated and should
be centralized to avoid drift. Add a small helper such as `_build_kicad_cli_cmd`
in `command.py` that takes `kicad_cli` plus the subcommand arguments and returns
the correct command list for both `.appimage` and plain binaries, then replace
both branching blocks with calls to that helper.

84-109: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Consider a proper S-expression parser instead of text splicing.

_merge_symbols inserts new symbol blocks by locating the last literal ) in the whole file and splicing text around it. This works for well-formed kicad_sym files but is fragile against any trailing content/formatting variance and offers no structural validation of the merged result. A KiCad-aware library (e.g. kiutils) would parse/serialize the S-expression tree and avoid this class of corruption risk for a file users rely on.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kicad_lib_manager/commands/import_zip/command.py` around lines 84 - 109, The
_merge_symbols function is currently doing unsafe text splicing by finding the
last literal ")" in the kicad_sym file, which can corrupt formatting or
structure. Refactor this flow to use a proper KiCad S-expression
parser/serializer (for example via kiutils) when reading, merging, and writing
symbols in _merge_symbols, so new blocks are inserted into the parsed tree and
the result is structurally validated before saving.

274-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant shutil.which call and suppressed type error.

shutil.which("kicad-cli") is called twice (once in the if, once inside Path(...)), and the # type: ignore[arg-type] masks the fact that shutil.which returns Optional[str]. As per coding guidelines, code should use full explicit type hints and avoid dynamic-typing workarounds; capturing the result once avoids both issues.

🔧 Proposed fix
-    if shutil.which("kicad-cli"):
-        return Path(shutil.which("kicad-cli"))  # type: ignore[arg-type]
+    found = shutil.which("kicad-cli")
+    if found:
+        return Path(found)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kicad_lib_manager/commands/import_zip/command.py` around lines 274 - 277, The
_detect_kicad_cli function redundantly calls shutil.which("kicad-cli") twice and
relies on a suppressed type error to pass its result into Path. Update
_detect_kicad_cli to store the result of shutil.which("kicad-cli") in a clearly
typed local variable, check that variable once, and return Path(...) only when
it is not None. This removes the duplicate lookup and the need for the type
ignore while keeping the Optional[Path] behavior intact.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kicad_lib_manager/commands/import_zip/command.py`:
- Around line 278-286: The fallback kicad-cli location list in the command
lookup logic is hardcoded inline and should be made configurable. Hoist the
paths used by the kicad-cli discovery function into a module-level constant
tuple in import_zip/command.py, and have the lookup helper iterate over that
constant instead of embedding literals directly; if appropriate, also allow an
environment variable override so additional paths like Windows installs can be
added without changing the function.
- Around line 381-396: The per-zip import path in the loop over zip_files
currently calls _import_zip without any exception handling, so failures like
ValueError or I/O errors will escape as a traceback instead of the existing
skip-and-log behavior. Wrap the _import_zip call in a try/except in the same
loop, catch import-related exceptions, and print a consistent red error message
that includes zip_path.name before continuing to the next archive, while leaving
the successful totals["sym"], totals["fp"], and totals["models"] aggregation
unchanged.
- Around line 125-176: The `_upgrade_fp` and `_upgrade_sym` helpers invoke
`subprocess.run` on `kicad-cli` without a timeout and ignore non-zero exits, so
a stuck or failed upgrade is silently swallowed. Update both helpers to call
`subprocess.run` with a reasonable `timeout=` and `check=True` or explicit
exit-code handling, and catch `subprocess.TimeoutExpired` plus command failures
to log the stderr/stdout details. Keep the handling localized in `_upgrade_fp`,
`_upgrade_sym`, and the shared `kicad_cli` invocation path so import continues
safely while surfacing the failure.
- Around line 181-195: The containment check in _safe_extractall currently
relies on string prefix matching, which breaks on Windows because Path string
separators differ; replace it with a path-aware relative check against
dest_resolved so valid ZIP entries are accepted across platforms. Also update
the _safe_extractall docstring to describe this as defense-in-depth rather than
implying zipfile.extractall() only recently gained sanitization in Python 3.12,
while keeping the unsafe-entry rejection behavior unchanged.

In `@tests/test_import_zip_command.py`:
- Around line 46-51: Add explicit return annotations to every test_* function in
this file so they consistently use -> None like the existing typed helpers and
fixtures. Update the affected test functions such as test_extract_symbol_blocks
and the other test cases referenced in this file to include a None return type,
keeping the rest of the test logic unchanged and preserving strict
pyrefly-compatible typing.

---

Nitpick comments:
In `@kicad_lib_manager/commands/import_zip/command.py`:
- Around line 139-159: The `cmd` construction in `_upgrade_fp` and
`_upgrade_sym` is duplicated and should be centralized to avoid drift. Add a
small helper such as `_build_kicad_cli_cmd` in `command.py` that takes
`kicad_cli` plus the subcommand arguments and returns the correct command list
for both `.appimage` and plain binaries, then replace both branching blocks with
calls to that helper.
- Around line 84-109: The _merge_symbols function is currently doing unsafe text
splicing by finding the last literal ")" in the kicad_sym file, which can
corrupt formatting or structure. Refactor this flow to use a proper KiCad
S-expression parser/serializer (for example via kiutils) when reading, merging,
and writing symbols in _merge_symbols, so new blocks are inserted into the
parsed tree and the result is structurally validated before saving.
- Around line 274-277: The _detect_kicad_cli function redundantly calls
shutil.which("kicad-cli") twice and relies on a suppressed type error to pass
its result into Path. Update _detect_kicad_cli to store the result of
shutil.which("kicad-cli") in a clearly typed local variable, check that variable
once, and return Path(...) only when it is not None. This removes the duplicate
lookup and the need for the type ignore while keeping the Optional[Path]
behavior intact.

In `@tests/test_import_zip_command.py`:
- Around line 174-229: Add an end-to-end CLI integration test that exercises the
kicad-cli footprint upgrade path in import_zip.command instead of patching
_detect_kicad_cli to None, so the legacy footprint conversion flow is covered.
Reuse the existing import/app runner setup and a Samacsys ZIP fixture, then
assert the upgraded footprint is produced through the kicad-cli branch and the
expected output reflects that path. Keep the current no-cli tests, but add a
separate test that specifically validates the _detect_kicad_cli-driven behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58c31866-df41-45b0-b0b9-8f8f5776e581

📥 Commits

Reviewing files that changed from the base of the PR and between f58fa1c and de6357e.

📒 Files selected for processing (4)
  • kicad_lib_manager/commands/import_zip/__init__.py
  • kicad_lib_manager/commands/import_zip/command.py
  • kicad_lib_manager/main.py
  • tests/test_import_zip_command.py

Comment thread kicad_lib_manager/commands/import_zip/command.py
Comment thread kicad_lib_manager/commands/import_zip/command.py
Comment thread kicad_lib_manager/commands/import_zip/command.py Outdated
Comment thread kicad_lib_manager/commands/import_zip/command.py
Comment thread tests/test_import_zip_command.py
@monkey265

Copy link
Copy Markdown
Author

New commit fixes all CodeRabbit suggestions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kicad_lib_manager/commands/import_zip/command.py`:
- Around line 141-148: The warning output in the import ZIP upgrade path is
printing raw bytes because `subprocess.run` in the upgrade helper(s) under
`command.py` uses `capture_output=True` without text decoding. Update the
relevant `subprocess.run` call(s) so `exc.stderr` is handled as text, either by
enabling `text=True`/`universal_newlines=True` or by decoding before formatting,
and keep the `except subprocess.CalledProcessError` message in the same `fp
upgrade` flow readable for `path.name`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31621876-079c-47f6-8d25-86cd644f4dca

📥 Commits

Reviewing files that changed from the base of the PR and between de6357e and 2a75791.

📒 Files selected for processing (1)
  • kicad_lib_manager/commands/import_zip/command.py

Comment thread kicad_lib_manager/commands/import_zip/command.py
@monkey265

Copy link
Copy Markdown
Author

Fixed subprocess stderr decoding: added text=True to both subprocess.run calls in _upgrade_fp and _upgrade_sym so error messages print as readable strings instead of b'...' bytes.

UltraLibrarian exports differ from SamacSys/Mouser: the KiCad dir is
named "KiCADv6" (case differs, prior lookup was case-sensitive),
footprints nest under a "*.pretty" subdir, and symbol files use
2-space/CRLF formatting instead of tabs. Match KiCad/3D dirs
case-insensitively, glob footprints/symbols recursively, and rewrite
symbol-block extraction to track paren depth instead of relying on a
tab-indentation regex, so it works regardless of generator whitespace
style. Verified against a real UltraLibrarian export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@monkey265

Copy link
Copy Markdown
Author

Also added support for ultralibrarian

@monkey265 monkey265 changed the title feat: add kilm import command for SamacSys/Mouser ZIPs feat: add kilm import command for SamacSys/Mouser/Ultralibrarian ZIPs Jul 8, 2026
@Breakd0w

Breakd0w commented Jul 8, 2026

Copy link
Copy Markdown

Awesome idea! This would really help my workflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants