Skip to content

Add pcb datasheet <QUERY> command - #924

Open
lucia3e8 wants to merge 2 commits into
mainfrom
lucia/pcb-datasheet-command
Open

lucia3e8 wants to merge 2 commits into
mainfrom
lucia/pcb-datasheet-command

Conversation

@lucia3e8

@lucia3e8 lucia3e8 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new pcb datasheet <QUERY> command that deterministically resolves a component to its datasheet and prints the result (a URL or local file path) to stdout.

The query is one of three forms, tried in this order:

  1. Encoded component id — base64url JSON {source, mpn, manufacturer?, backendId} as returned in the component_id field of pcb search --mode web:components. Resolved via POST /api/component/download (reusing the existing authenticated API client) → the signed datasheet URL.
  2. Reference designator (e.g. U3, R5, J12) — valid only inside a workspace. Resolved by evaluating the board's BOM with the same machinery as pcb bom (board discovered like other workspace commands, or passed via --board <file>), matching the designator, and resolving that component's datasheet — preferring the design's own resolved symbol: the Datasheet property of the component's .kicad_sym and a sibling <MPN>.pdf in the component package dir, since that pins the exact design intent.
  3. MPN — resolved through deterministic tiers: workspace component packages (<MPN>.kicad_sym Datasheet / sibling <MPN>.pdf) → local registry SQLite index (registry:components) → KiCad symbol index (kicad:components) → POST /api/component/search taking datasheets[0] of the best-scored result. --manufacturer <name> disambiguates.

--refdes/--mpn/--id force interpretation when the heuristic is ambiguous. A refdes-shaped string is only treated as a refdes if it actually matches a BOM designator; otherwise it falls through to MPN.

Output & flags

  • Default: the datasheet URL or local path, one line, stdout.
  • -f json: {query, interpretation: "component_id"|"refdes"|"mpn", mpn, manufacturer, url, source: "download_cache"|"workspace"|"registry_index"|"kicad_index"|"web_search"}.
  • --scan: chains the resolved URL/path through the existing scan pipeline and prints the generated markdown path (so an agent can do pcb datasheet U3 --scan in one call).
  • --open: opens the URL/file with the system opener.

Fails with a clear one-line error and non-zero exit when a tier chain exhausts without a datasheet (distinguishing "component not found" from "component found but no datasheet on record"), when a refdes is used outside a workspace, or when auth is required but missing.

Reuse (no duplication)

Reuses the existing auth (pcb auth), API client (download_component/search_components), registry index access (RegistryClient), the KiCad symbol/datasheet extraction, and the scan pipeline (scan/resolve_datasheet). Resolution logic that doesn't need board evaluation lives in a new pcb_diode_api::datasheet_resolve module; the pcb crate wires up the command and does board evaluation for reference designators.

Tests

  • Unit (pcb-diode-api): query-form disambiguation (looks_like_refdes) and component-id decoding (decode_component_id), plus symbol/KiCad datasheet extraction.
  • Integration (crates/pcb/tests/datasheet.rs), one per resolution tier: workspace fixture with a vendored component package, reference designator (board eval), local registry index, KiCad symbol index, and mocked download/search API responses (incl. manufacturer disambiguation and the "found but no datasheet" path).

A small Sandbox::env() helper was added to pcb-test-utils so tests can inject env overrides (mock API URL, isolated index paths) that survive duct's full_env.

Docs

Updated the README command reference, the embedded PCB skill (.agents/skills/pcb/SKILL.md), and the CHANGELOG.


Note

Medium Risk
New CLI paths call authenticated download/search APIs and evaluate boards for refdes; behavior is mostly additive but wrong tier ordering or ambiguous queries could surprise users in real workspaces.

Overview
Adds pcb datasheet <QUERY> (API feature): prints a datasheet URL or local path to stdout, with optional -f json metadata (interpretation, source, mpn, manufacturer).

Query handling: encoded component id → reference designator (workspace only, board eval like pcb bom) → MPN. --refdes / --mpn / --id and --manufacturer force or disambiguate; --offline skips registry download and web search.

Resolution lives in new pcb_diode_api::datasheet_resolve: download API, workspace components//vendor/ (local PDF then symbol Datasheet), registry SQLite (find_component_datasheet, PCB_REGISTRY_DB override), KiCad symbol scan (PCB_KICAD_SYMBOL_PATH), then component search. Refdes path prefers the placed symbol, then component datasheet attr, then the MPN chain.

Docs (README, skill, CHANGELOG), Sandbox::env() for integration tests, and tier coverage in crates/pcb/tests/datasheet.rs.

Reviewed by Cursor Bugbot for commit 4aaeb87. Bugbot is set up for automated code reviews on this repo. Configure here.

Deterministically resolve a component to its datasheet and print the result
to stdout. The query is one of three forms, tried in order:

1. An encoded component id (base64url JSON `{source, mpn, manufacturer?,
   backendId}` from `pcb search --mode web:components`), resolved via
   `POST /api/component/download`.
2. A reference designator (e.g. `U3`), valid only inside a workspace: the
   board's BOM is evaluated with the same machinery as `pcb bom` (board
   discovered like other workspace commands or passed via `--board`), and the
   matched component's datasheet is resolved, preferring the design's own
   resolved symbol (its `.kicad_sym` `Datasheet` property and a sibling
   `<MPN>.pdf`).
3. An MPN, resolved through deterministic tiers: workspace component packages,
   the local registry SQLite index (registry:components), the KiCad symbol
   index (kicad:components), then `POST /api/component/search`.

`--refdes`/`--mpn`/`--id` force interpretation; a refdes-shaped string is only
treated as a refdes when it matches a real BOM designator, otherwise it falls
through to MPN. `--manufacturer` disambiguates parts sharing an MPN.

Default output is the datasheet URL or local path (one line); `-f json` emits
`{query, interpretation, mpn, manufacturer, url, source}`. `--scan` chains the
resolved URL/path through the existing scan pipeline and prints the generated
markdown path; `--open` opens it with the system opener.

Reuses the existing auth, API client, registry/KiCad access, and scan pipeline
without duplication. Adds unit tests for query-form disambiguation and
component-id decoding, plus an integration test per resolution tier (workspace
fixture, registry index, KiCad index, and mocked download/search APIs).
cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Minimize the command surface and added lines:

- Drop `--scan`/`--scan-model` and `--open`: compose with existing tools
  instead, e.g. `pcb scan $(pcb datasheet U3)` or `open $(pcb datasheet U3)`.
  This also resolves the review notes about `--open` ignoring the scanned
  markdown path and `--scan-model` being dropped for URL datasheets.
- Serialize `Interpretation`/`DatasheetSource` via their serde derives instead
  of hand-written `as_str()` tables.
- Make the existing `extract_datasheet_url_from_kicad_sym` /
  `is_usable_datasheet_value` helpers public instead of adding wrappers, and
  drop the crate-root re-exports.
- Collapse `MpnResolveConfig::{allow_web, offline}` into a single `offline`
  flag; drop the unused `edatasheet` column from the registry query.
- Remove unit tests that duplicated integration coverage (including the
  env-var-mutating KiCad index test flagged in review) and the `tempfile`
  dev-dependency they required; merge redundant integration tests; trim docs.

Review fixes:

- Workspace tier now honors `--manufacturer`: only component packages whose
  path contains the sanitized manufacturer (canonical layout
  `components/<manufacturer>/<mpn>/`) are considered, so parts sharing an MPN
  across vendors resolve correctly. Covered by a new integration test.
- Registry index query errors (e.g. a stale local index) now fall through to
  the next resolution tier instead of aborting, matching how index-open
  failures are handled.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4aaeb87. Configure here.

// 1. Encoded component id.
if decode_component_id(query).is_some() {
let token = pcb_diode_api::auth::get_valid_token()?;
return resolve_component_id(&token, query);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Offline flag ignores component id

Medium Severity

--offline is documented for resolution tiers but never applied when the query is treated as an encoded component id (--id or heuristic decode). Those paths always call resolve_component_id, which hits POST /api/component/download and needs network/auth even with --offline.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4aaeb87. Configure here.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +474 to +477
anyhow::bail!(
"component '{mpn}' not found (searched workspace packages, registry index, and KiCad index{})",
if cfg.offline { "" } else { ", and web search" }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 "Not found" error message contains a grammatically broken double "and" when online

The fallback error message is assembled with a fixed "and" before "KiCad index" plus a conditional ", and web search" suffix (format! at crates/pcb-diode-api/src/datasheet_resolve.rs:475-476), so when online the user sees "…registry index, and KiCad index, and web search".

Impact: Users see a confusing, grammatically broken error message when a component is not found online.

Incorrect string concatenation in format! macro

The base string at line 475 is:

"component '{mpn}' not found (searched workspace packages, registry index, and KiCad index{})"

When cfg.offline is false, the {} placeholder is filled with ", and web search" (line 476), producing:

component 'X' not found (searched workspace packages, registry index, and KiCad index, and web search)

The fix is to restructure so the list reads naturally in both cases, e.g. removing the "and" before "KiCad index" in the base string and adjusting the suffix.

Suggested change
anyhow::bail!(
"component '{mpn}' not found (searched workspace packages, registry index, and KiCad index{})",
if cfg.offline { "" } else { ", and web search" }
);
anyhow::bail!(
"component '{mpn}' not found (searched workspace packages, registry index, KiCad index{})",
if cfg.offline { "" } else { ", and web search" }
);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

No deployments
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.

1 participant