Conversation
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).
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.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 4aaeb87. Configure here.
| anyhow::bail!( | ||
| "component '{mpn}' not found (searched workspace packages, registry index, and KiCad index{})", | ||
| if cfg.offline { "" } else { ", and web search" } | ||
| ); |
There was a problem hiding this comment.
🟡 "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.
| 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" } | |
| ); |
Was this helpful? React with 👍 or 👎 to provide feedback.


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:
{source, mpn, manufacturer?, backendId}as returned in thecomponent_idfield ofpcb search --mode web:components. Resolved viaPOST /api/component/download(reusing the existing authenticated API client) → the signed datasheet URL.U3,R5,J12) — valid only inside a workspace. Resolved by evaluating the board's BOM with the same machinery aspcb 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: theDatasheetproperty of the component's.kicad_symand a sibling<MPN>.pdfin the component package dir, since that pins the exact design intent.<MPN>.kicad_symDatasheet/ sibling<MPN>.pdf) → local registry SQLite index (registry:components) → KiCad symbol index (kicad:components) →POST /api/component/searchtakingdatasheets[0]of the best-scored result.--manufacturer <name>disambiguates.--refdes/--mpn/--idforce 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
-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 dopcb datasheet U3 --scanin 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 newpcb_diode_api::datasheet_resolvemodule; thepcbcrate wires up the command and does board evaluation for reference designators.Tests
pcb-diode-api): query-form disambiguation (looks_like_refdes) and component-id decoding (decode_component_id), plus symbol/KiCad datasheet extraction.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 mockeddownload/searchAPI responses (incl. manufacturer disambiguation and the "found but no datasheet" path).A small
Sandbox::env()helper was added topcb-test-utilsso tests can inject env overrides (mock API URL, isolated index paths) that survive duct'sfull_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 jsonmetadata (interpretation,source,mpn,manufacturer).Query handling: encoded component id → reference designator (workspace only, board eval like
pcb bom) → MPN.--refdes/--mpn/--idand--manufacturerforce or disambiguate;--offlineskips registry download and web search.Resolution lives in new
pcb_diode_api::datasheet_resolve: download API, workspacecomponents//vendor/(local PDF then symbolDatasheet), registry SQLite (find_component_datasheet,PCB_REGISTRY_DBoverride), 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 incrates/pcb/tests/datasheet.rs.Reviewed by Cursor Bugbot for commit 4aaeb87. Bugbot is set up for automated code reviews on this repo. Configure here.