driverge-mcp 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/dist/codegen/esp32.d.ts +4 -0
  4. package/dist/codegen/esp32.js +78 -0
  5. package/dist/codegen/esp32.js.map +1 -0
  6. package/dist/codegen/ident.d.ts +10 -0
  7. package/dist/codegen/ident.js +31 -0
  8. package/dist/codegen/ident.js.map +1 -0
  9. package/dist/codegen/index.d.ts +15 -0
  10. package/dist/codegen/index.js +30 -0
  11. package/dist/codegen/index.js.map +1 -0
  12. package/dist/codegen/lint.d.ts +4 -0
  13. package/dist/codegen/lint.js +112 -0
  14. package/dist/codegen/lint.js.map +1 -0
  15. package/dist/codegen/portable.d.ts +4 -0
  16. package/dist/codegen/portable.js +258 -0
  17. package/dist/codegen/portable.js.map +1 -0
  18. package/dist/codegen/stm32.d.ts +4 -0
  19. package/dist/codegen/stm32.js +64 -0
  20. package/dist/codegen/stm32.js.map +1 -0
  21. package/dist/codegen/types.d.ts +20 -0
  22. package/dist/codegen/types.js +11 -0
  23. package/dist/codegen/types.js.map +1 -0
  24. package/dist/mcp/cache.d.ts +12 -0
  25. package/dist/mcp/cache.js +24 -0
  26. package/dist/mcp/cache.js.map +1 -0
  27. package/dist/mcp/register.d.ts +6 -0
  28. package/dist/mcp/register.js +195 -0
  29. package/dist/mcp/register.js.map +1 -0
  30. package/dist/mcp/summary.d.ts +2 -0
  31. package/dist/mcp/summary.js +36 -0
  32. package/dist/mcp/summary.js.map +1 -0
  33. package/dist/pdf/analyze.d.ts +4 -0
  34. package/dist/pdf/analyze.js +29 -0
  35. package/dist/pdf/analyze.js.map +1 -0
  36. package/dist/pdf/classify.d.ts +8 -0
  37. package/dist/pdf/classify.js +35 -0
  38. package/dist/pdf/classify.js.map +1 -0
  39. package/dist/pdf/command.d.ts +12 -0
  40. package/dist/pdf/command.js +96 -0
  41. package/dist/pdf/command.js.map +1 -0
  42. package/dist/pdf/extract.d.ts +2 -0
  43. package/dist/pdf/extract.js +47 -0
  44. package/dist/pdf/extract.js.map +1 -0
  45. package/dist/pdf/index.d.ts +11 -0
  46. package/dist/pdf/index.js +13 -0
  47. package/dist/pdf/index.js.map +1 -0
  48. package/dist/pdf/interface-kind.d.ts +2 -0
  49. package/dist/pdf/interface-kind.js +46 -0
  50. package/dist/pdf/interface-kind.js.map +1 -0
  51. package/dist/pdf/manufacturer.d.ts +2 -0
  52. package/dist/pdf/manufacturer.js +86 -0
  53. package/dist/pdf/manufacturer.js.map +1 -0
  54. package/dist/pdf/page-map.d.ts +5 -0
  55. package/dist/pdf/page-map.js +52 -0
  56. package/dist/pdf/page-map.js.map +1 -0
  57. package/dist/pdf/part.d.ts +3 -0
  58. package/dist/pdf/part.js +41 -0
  59. package/dist/pdf/part.js.map +1 -0
  60. package/dist/pdf/register-table.d.ts +5 -0
  61. package/dist/pdf/register-table.js +234 -0
  62. package/dist/pdf/register-table.js.map +1 -0
  63. package/dist/pdf/table.d.ts +18 -0
  64. package/dist/pdf/table.js +33 -0
  65. package/dist/pdf/table.js.map +1 -0
  66. package/dist/pdf/types.d.ts +82 -0
  67. package/dist/pdf/types.js +3 -0
  68. package/dist/pdf/types.js.map +1 -0
  69. package/dist/schema/assemble.d.ts +4 -0
  70. package/dist/schema/assemble.js +48 -0
  71. package/dist/schema/assemble.js.map +1 -0
  72. package/dist/schema/index.d.ts +2 -0
  73. package/dist/schema/index.js +4 -0
  74. package/dist/schema/index.js.map +1 -0
  75. package/dist/schema/types.d.ts +51 -0
  76. package/dist/schema/types.js +7 -0
  77. package/dist/schema/types.js.map +1 -0
  78. package/dist/schema/validate.d.ts +2 -0
  79. package/dist/schema/validate.js +84 -0
  80. package/dist/schema/validate.js.map +1 -0
  81. package/dist/server.d.ts +7 -0
  82. package/dist/server.js +49 -0
  83. package/dist/server.js.map +1 -0
  84. package/package.json +59 -0
  85. package/schemas/.gitkeep +0 -0
  86. package/schemas/datasheet.schema.json +109 -0
@@ -0,0 +1,2 @@
1
+ import type { CacheEntry } from "./cache.js";
2
+ export declare function buildSummary(entry: CacheEntry): Record<string, unknown>;
@@ -0,0 +1,36 @@
1
+ // The compact summary analyze_datasheet returns to the model — NOT the full JSON
2
+ // (that travels by ref via the driverge://datasheet resource). See wiki:
3
+ // parsed-json-handoff-via-handle.
4
+ export function buildSummary(entry) {
5
+ const { ref, json } = entry;
6
+ const counts = json.interface.kind === "register_map"
7
+ ? { registers: json.interface.registers.length }
8
+ : { commands: json.interface.commands.length };
9
+ const proto = json.protocol;
10
+ const protocolSummary = [
11
+ proto.bus,
12
+ proto.addresses?.length ? `addresses ${proto.addresses.join("/")}` : undefined,
13
+ proto.maxClockHz ? `max ${proto.maxClockHz} Hz` : undefined,
14
+ ]
15
+ .filter(Boolean)
16
+ .join(", ");
17
+ return {
18
+ ref,
19
+ metadata: {
20
+ part_number: json.metadata.part,
21
+ manufacturer: json.metadata.manufacturer,
22
+ manufacturer_confidence: json.metadata.manufacturerConfidence,
23
+ pdf_type: json.metadata.pdfType,
24
+ page_count: json.metadata.pageCount,
25
+ },
26
+ interface: { kind: json.interface.kind },
27
+ protocol_summary: protocolSummary,
28
+ counts,
29
+ validation: {
30
+ passed: json.validation.valid,
31
+ warnings: json.validation.warnings,
32
+ errors: json.validation.errors,
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/mcp/summary.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,yEAAyE;AACzE,kCAAkC;AAIlC,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC5B,MAAM,MAAM,GACV,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,cAAc;QACpC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE;QAChD,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAEnD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC5B,MAAM,eAAe,GAAG;QACtB,KAAK,CAAC,GAAG;QACT,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;QAC9E,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,SAAS;KAC5D;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,GAAG;QACH,QAAQ,EAAE;YACR,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;YAC/B,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY;YACxC,uBAAuB,EAAE,IAAI,CAAC,QAAQ,CAAC,sBAAsB;YAC7D,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;YAC/B,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS;SACpC;QACD,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;QACxC,gBAAgB,EAAE,eAAe;QACjC,MAAM;QACN,UAAU,EAAE;YACV,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK;YAC7B,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ;YAClC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM;SAC/B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { PdfAnalysis } from "./types.js";
2
+ export declare function analyzePdf(data: Uint8Array): Promise<PdfAnalysis>;
3
+ /** Convenience wrapper: analyze a PDF given a local file path (FR: path input). */
4
+ export declare function analyzePdfFile(path: string): Promise<PdfAnalysis>;
@@ -0,0 +1,29 @@
1
+ // Orchestrates L1 (format detection) + L2 (page map) over a whole PDF and
2
+ // surfaces a detect-and-warn message for scanned/mixed input (OCR is deferred).
3
+ import { readFile } from "node:fs/promises";
4
+ import { classifyDocument } from "./classify.js";
5
+ import { extractPages } from "./extract.js";
6
+ import { buildPageMap } from "./page-map.js";
7
+ export async function analyzePdf(data) {
8
+ const pages = await extractPages(data);
9
+ const signals = pages.map((page) => ({
10
+ textLength: page.text.length,
11
+ hasImage: page.hasImage,
12
+ }));
13
+ const { type } = classifyDocument(signals);
14
+ const pageMap = buildPageMap(pages.map((page) => page.text));
15
+ const warnings = [];
16
+ if (type === "scanned") {
17
+ warnings.push("PDF appears to be scanned (little or no extractable text). OCR is not supported in v0.1.0, so parsing will be incomplete.");
18
+ }
19
+ else if (type === "mixed") {
20
+ warnings.push("PDF mixes text and scanned pages. Scanned pages cannot be parsed (OCR deferred to a future release).");
21
+ }
22
+ return { type, pageCount: pages.length, pageMap, pages, warnings };
23
+ }
24
+ /** Convenience wrapper: analyze a PDF given a local file path (FR: path input). */
25
+ export async function analyzePdfFile(path) {
26
+ const buffer = await readFile(path);
27
+ return analyzePdf(new Uint8Array(buffer));
28
+ }
29
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/pdf/analyze.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,gFAAgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAgB;IAC/C,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IAEvC,MAAM,OAAO,GAAiB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjD,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;QAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC,CAAC;IACJ,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,QAAQ,CAAC,IAAI,CACX,2HAA2H,CAC5H,CAAC;IACJ,CAAC;SAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CACX,sGAAsG,CACvG,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACrE,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY;IAC/C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { PageKind, PageSignal, PdfClassification } from "./types.js";
2
+ /**
3
+ * Minimum extractable characters for a page to count as a real "text" page.
4
+ * Below this, a page carrying a raster image is treated as scanned.
5
+ */
6
+ export declare const MIN_TEXT_CHARS = 16;
7
+ export declare function classifyPage(signal: PageSignal): PageKind;
8
+ export declare function classifyDocument(signals: PageSignal[]): PdfClassification;
@@ -0,0 +1,35 @@
1
+ // L1 — PDF format detection. Pure logic over per-page signals so it can be unit
2
+ // tested without a real PDF.
3
+ /**
4
+ * Minimum extractable characters for a page to count as a real "text" page.
5
+ * Below this, a page carrying a raster image is treated as scanned.
6
+ */
7
+ export const MIN_TEXT_CHARS = 16;
8
+ export function classifyPage(signal) {
9
+ if (signal.textLength >= MIN_TEXT_CHARS)
10
+ return "text";
11
+ if (signal.hasImage)
12
+ return "scanned";
13
+ return "blank";
14
+ }
15
+ export function classifyDocument(signals) {
16
+ const pageKinds = signals.map(classifyPage);
17
+ // Blank pages carry no evidence either way; decide on the content pages.
18
+ const content = pageKinds.filter((kind) => kind !== "blank");
19
+ let type;
20
+ if (content.length === 0) {
21
+ // Nothing extractable anywhere — treat as scanned so the caller warns.
22
+ type = "scanned";
23
+ }
24
+ else if (content.every((kind) => kind === "text")) {
25
+ type = "text_based";
26
+ }
27
+ else if (content.every((kind) => kind === "scanned")) {
28
+ type = "scanned";
29
+ }
30
+ else {
31
+ type = "mixed";
32
+ }
33
+ return { type, pageKinds };
34
+ }
35
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/pdf/classify.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6BAA6B;AAS7B;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,IAAI,MAAM,CAAC,UAAU,IAAI,cAAc;QAAE,OAAO,MAAM,CAAC;IACvD,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5C,yEAAyE;IACzE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IAE7D,IAAI,IAAa,CAAC;IAClB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,uEAAuE;QACvE,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;QACpD,IAAI,GAAG,YAAY,CAAC;IACtB,CAAC;SAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;QACvD,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Command, Protocol } from "../schema/types.js";
2
+ import type { PageContent } from "./types.js";
3
+ /** L4b — bus + device addresses. */
4
+ export declare function extractProtocol(pages: PageContent[]): Protocol;
5
+ /** L4b — CRC parameters from the checksum section. */
6
+ export declare function extractCrc(pages: PageContent[]): {
7
+ poly: string;
8
+ init: string;
9
+ width: number;
10
+ } | undefined;
11
+ /** L4c — extract named commands with full hex codes. */
12
+ export declare function extractCommands(pages: PageContent[]): Command[];
@@ -0,0 +1,96 @@
1
+ // L4b/L4c (scoped) — text extraction for command-set devices (e.g. Sensirion
2
+ // SHT3x): I2C protocol + CRC parameters, and the cleanly-tabulated standalone /
3
+ // representative commands. Full split-code measurement/periodic matrices are left
4
+ // to host-AI enumeration (hybrid philosophy). See wiki: command-set-interface.
5
+ const joinText = (pages) => pages
6
+ .map((p) => p.text)
7
+ .join(" ")
8
+ .replace(/\s+/g, " ");
9
+ const upperHex = (code) => "0x" + code.slice(2).toUpperCase();
10
+ function normalizeName(raw) {
11
+ return raw
12
+ .trim()
13
+ .replace(/^(?:(?:command|hex|code)\s+)+/i, "")
14
+ .split(/\s+/)
15
+ .slice(0, 6)
16
+ .join(" ")
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9]+/g, "_")
19
+ .replace(/^_+|_+$/g, "");
20
+ }
21
+ /** L4b — bus + device addresses. */
22
+ export function extractProtocol(pages) {
23
+ const text = joinText(pages);
24
+ const bus = /\bI(?:2|²)C\b/i.test(text)
25
+ ? "I2C"
26
+ : /\bSPI\b/.test(text)
27
+ ? "SPI"
28
+ : "unknown";
29
+ const addresses = [];
30
+ // Within each "address …" window, collect every plausible 7-bit I2C address.
31
+ for (const ctx of text.matchAll(/address[^.]{0,60}/gi)) {
32
+ for (const m of ctx[0].matchAll(/0x[0-7][0-9a-f]/gi)) {
33
+ const addr = upperHex(m[0]);
34
+ const value = Number.parseInt(addr, 16);
35
+ if (value >= 0x08 && value <= 0x77 && !addresses.includes(addr)) {
36
+ addresses.push(addr);
37
+ }
38
+ }
39
+ }
40
+ return addresses.length > 0 ? { bus, addresses } : { bus };
41
+ }
42
+ /** L4b — CRC parameters from the checksum section. */
43
+ export function extractCrc(pages) {
44
+ const text = joinText(pages);
45
+ const poly = /polynomial\s+(0x[0-9a-f]{2})/i.exec(text);
46
+ const init = /initiali[sz]ation\s+(0x[0-9a-f]{2})/i.exec(text);
47
+ if (!poly || !init)
48
+ return undefined;
49
+ return { poly: upperHex(poly[1]), init: upperHex(init[1]), width: 8 };
50
+ }
51
+ // Only patterns whose NAME is a pure run of letters/spaces are used — a digit or
52
+ // "0x" in the span means we've crossed into the other text column, so we bail
53
+ // rather than emit a garbled name. Reliable > complete for this scoped pass.
54
+ const P_MINI = /hex\s+code\s+([a-z][a-z /]{1,35}?)\s+(0x[0-9a-f]{4})\s+table/gi;
55
+ const P_EG = /e\.g\.\s*(0x[0-9a-f]{4})\s*:\s*([a-z][a-z /]{3,70})/gi;
56
+ const P_GENCALL = /general call address\s+(0x[0-9a-f]{4})/gi;
57
+ const P_CRC_EXAMPLE = /crc\s*\(\s*(0x[0-9a-f]{4})\s*\)/gi;
58
+ const isDataReturning = /measurement|repeatab|periodic|status|art|fetch/i;
59
+ const isMeasurement = /measurement|repeatab|periodic/i;
60
+ /** L4c — extract named commands with full hex codes. */
61
+ export function extractCommands(pages) {
62
+ const text = joinText(pages);
63
+ const crc = extractCrc(pages);
64
+ const excluded = new Set();
65
+ for (const m of text.matchAll(P_CRC_EXAMPLE))
66
+ excluded.add(m[1].toLowerCase());
67
+ // code -> name; earlier (cleaner) patterns win on collision.
68
+ const found = new Map();
69
+ const add = (rawName, rawCode) => {
70
+ const code = rawCode.toLowerCase();
71
+ if (excluded.has(code) || found.has(code))
72
+ return;
73
+ const name = normalizeName(rawName);
74
+ if (name)
75
+ found.set(code, name);
76
+ };
77
+ for (const m of text.matchAll(P_MINI))
78
+ add(m[1], m[2]);
79
+ for (const m of text.matchAll(P_EG))
80
+ add(m[2], m[1]);
81
+ for (const m of text.matchAll(P_GENCALL))
82
+ add("general_call_reset", m[1]);
83
+ const commands = [];
84
+ for (const [code, name] of found) {
85
+ const command = { name, code: upperHex(code) };
86
+ if (crc && isDataReturning.test(name)) {
87
+ command.crc = crc;
88
+ if (isMeasurement.test(name))
89
+ command.responseWords = 2;
90
+ }
91
+ commands.push(command);
92
+ }
93
+ commands.sort((a, b) => a.code.localeCompare(b.code));
94
+ return commands;
95
+ }
96
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/pdf/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { PageContent } from "./types.js";
2
+ export declare function extractPages(data: Uint8Array): Promise<PageContent[]>;
@@ -0,0 +1,47 @@
1
+ // pdfjs-dist adapter: load a PDF and pull positioned text + an image flag from
2
+ // every page. The legacy build is the Node-friendly entry (and ships types).
3
+ import { getDocument, OPS } from "pdfjs-dist/legacy/build/pdf.mjs";
4
+ const IMAGE_OPS = new Set([
5
+ OPS.paintImageXObject,
6
+ OPS.paintInlineImageXObject,
7
+ OPS.paintImageMaskXObject,
8
+ ]);
9
+ export async function extractPages(data) {
10
+ const task = getDocument({ data });
11
+ const pdf = await task.promise;
12
+ try {
13
+ const pages = [];
14
+ for (let n = 1; n <= pdf.numPages; n++) {
15
+ const page = await pdf.getPage(n);
16
+ const textContent = await page.getTextContent();
17
+ const items = [];
18
+ for (const item of textContent.items) {
19
+ // TextMarkedContent entries carry no `str`; skip them.
20
+ if (!("str" in item))
21
+ continue;
22
+ items.push({
23
+ str: item.str,
24
+ x: item.transform[4],
25
+ y: item.transform[5],
26
+ width: item.width,
27
+ height: item.height,
28
+ });
29
+ }
30
+ const text = items
31
+ .map((i) => i.str)
32
+ .join(" ")
33
+ .replace(/\s+/g, " ")
34
+ .trim();
35
+ const opList = await page.getOperatorList();
36
+ const hasImage = opList.fnArray.some((fn) => IMAGE_OPS.has(fn));
37
+ pages.push({ index: n, text, items, hasImage });
38
+ page.cleanup();
39
+ }
40
+ return pages;
41
+ }
42
+ finally {
43
+ await pdf.cleanup();
44
+ await task.destroy();
45
+ }
46
+ }
47
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/pdf/extract.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAE7E,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,iCAAiC,CAAC;AAGnE,MAAM,SAAS,GAAwB,IAAI,GAAG,CAAC;IAC7C,GAAG,CAAC,iBAAiB;IACrB,GAAG,CAAC,uBAAuB;IAC3B,GAAG,CAAC,qBAAqB;CAC1B,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAgB;IACjD,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,KAAK,GAAkB,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAElC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;YAChD,MAAM,KAAK,GAAqB,EAAE,CAAC;YACnC,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;gBACrC,uDAAuD;gBACvD,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC;oBAAE,SAAS;gBAC/B,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBACpB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBACpB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,GAAG,KAAK;iBACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;iBACjB,IAAI,CAAC,GAAG,CAAC;iBACT,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;iBACpB,IAAI,EAAE,CAAC;YAEV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ export * from "./types.js";
2
+ export { MIN_TEXT_CHARS, classifyDocument, classifyPage } from "./classify.js";
3
+ export { buildPageMap, labelPage } from "./page-map.js";
4
+ export { extractPages } from "./extract.js";
5
+ export { analyzePdf, analyzePdfFile } from "./analyze.js";
6
+ export { centerX, clusterRows, type TableRow } from "./table.js";
7
+ export { findRegisterTable, parseRegisterTable } from "./register-table.js";
8
+ export { detectManufacturer } from "./manufacturer.js";
9
+ export { detectPart } from "./part.js";
10
+ export { detectInterfaceKind } from "./interface-kind.js";
11
+ export { extractCommands, extractCrc, extractProtocol } from "./command.js";
@@ -0,0 +1,13 @@
1
+ // Public surface of the PDF parsing pipeline (L1 format detection + L2 page map).
2
+ export * from "./types.js";
3
+ export { MIN_TEXT_CHARS, classifyDocument, classifyPage } from "./classify.js";
4
+ export { buildPageMap, labelPage } from "./page-map.js";
5
+ export { extractPages } from "./extract.js";
6
+ export { analyzePdf, analyzePdfFile } from "./analyze.js";
7
+ export { centerX, clusterRows } from "./table.js";
8
+ export { findRegisterTable, parseRegisterTable } from "./register-table.js";
9
+ export { detectManufacturer } from "./manufacturer.js";
10
+ export { detectPart } from "./part.js";
11
+ export { detectInterfaceKind } from "./interface-kind.js";
12
+ export { extractCommands, extractCrc, extractProtocol } from "./command.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pdf/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { InterfaceKindDetection, PageContent } from "./types.js";
2
+ export declare function detectInterfaceKind(pages: PageContent[]): InterfaceKindDetection;
@@ -0,0 +1,46 @@
1
+ // L3b — interface-kind classifier: register_map vs command_set. This, not the
2
+ // manufacturer, is the primary strategy switch (routes L4a table vs L4c command).
3
+ // See wiki: device-class-before-manufacturer, command-set-interface.
4
+ import { findRegisterTable } from "./register-table.js";
5
+ const REGISTER_KEYWORD = /register\s+(map|table|description|addresses?|summary)|memory\s+map/i;
6
+ const COMMAND_KEYWORD = /command\s+word|commands?\s+(set|table|list)|list\s+of\s+commands/i;
7
+ const CRC_KEYWORD = /crc[-\s]?\d|clock\s+stretch/i;
8
+ export function detectInterfaceKind(pages) {
9
+ const text = pages.map((p) => p.text).join(" ");
10
+ const registerSignals = [];
11
+ const commandSignals = [];
12
+ let registerScore = 0;
13
+ let commandScore = 0;
14
+ if (findRegisterTable(pages)) {
15
+ registerScore += 3;
16
+ registerSignals.push("register-table");
17
+ }
18
+ if (REGISTER_KEYWORD.test(text)) {
19
+ registerScore += 2;
20
+ registerSignals.push("register-keyword");
21
+ }
22
+ if (COMMAND_KEYWORD.test(text)) {
23
+ commandScore += 2;
24
+ commandSignals.push("command-keyword");
25
+ }
26
+ if (CRC_KEYWORD.test(text)) {
27
+ commandScore += 1;
28
+ commandSignals.push("crc/clock-stretch");
29
+ }
30
+ if (registerScore === 0 && commandScore === 0) {
31
+ return { kind: "unknown", confidence: 0, signals: [] };
32
+ }
33
+ if (registerScore >= commandScore) {
34
+ return {
35
+ kind: "register_map",
36
+ confidence: Math.min(1, registerScore / 5),
37
+ signals: registerSignals,
38
+ };
39
+ }
40
+ return {
41
+ kind: "command_set",
42
+ confidence: Math.min(1, commandScore / 3),
43
+ signals: commandSignals,
44
+ };
45
+ }
46
+ //# sourceMappingURL=interface-kind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface-kind.js","sourceRoot":"","sources":["../../src/pdf/interface-kind.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,kFAAkF;AAClF,qEAAqE;AAErE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,gBAAgB,GACpB,qEAAqE,CAAC;AACxE,MAAM,eAAe,GACnB,mEAAmE,CAAC;AACtE,MAAM,WAAW,GAAG,8BAA8B,CAAC;AAEnD,MAAM,UAAU,mBAAmB,CACjC,KAAoB;IAEpB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEhD,MAAM,eAAe,GAAa,EAAE,CAAC;IACrC,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,aAAa,IAAI,CAAC,CAAC;QACnB,eAAe,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,aAAa,IAAI,CAAC,CAAC;QACnB,eAAe,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,YAAY,IAAI,CAAC,CAAC;QAClB,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,YAAY,IAAI,CAAC,CAAC;QAClB,cAAc,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC3C,CAAC;IAED,IAAI,aAAa,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzD,CAAC;IACD,IAAI,aAAa,IAAI,YAAY,EAAE,CAAC;QAClC,OAAO;YACL,IAAI,EAAE,cAAc;YACpB,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC;YAC1C,OAAO,EAAE,eAAe;SACzB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC;QACzC,OAAO,EAAE,cAAc;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ManufacturerDetection, PageContent } from "./types.js";
2
+ export declare function detectManufacturer(pages: PageContent[]): ManufacturerDetection;
@@ -0,0 +1,86 @@
1
+ // L3a — manufacturer detection: deterministic layered scoring over datasheet
2
+ // text. Strong signals (copyright, document-number, URL) outweigh the weak
3
+ // part-prefix heuristic, so second-source collisions (PCA9555 vs TCA9555) resolve
4
+ // correctly. Low confidence degrades gracefully to a generic adapter — it never
5
+ // hard-fails. See wiki: manufacturer-detection, device-class-before-manufacturer.
6
+ const VENDORS = [
7
+ {
8
+ name: "Bosch Sensortec",
9
+ copyright: /Bosch\s+Sensortec/i,
10
+ docNumber: /\bBST-[A-Z0-9]+-DS\d+/i,
11
+ domain: /bosch-sensortec\.com/i,
12
+ partPrefix: /\bBM[EPAI]\d/i,
13
+ },
14
+ {
15
+ name: "Microchip",
16
+ copyright: /Microchip\s+Technology/i,
17
+ docNumber: /\bDS\d{8}[A-Z]?\b/i,
18
+ domain: /microchip\.com/i,
19
+ partPrefix: /\bMCP\d/i,
20
+ },
21
+ {
22
+ name: "STMicroelectronics",
23
+ copyright: /STMicroelectronics/i,
24
+ docNumber: /\bDS\d{4,5}\b/i,
25
+ domain: /\bst\.com\b/i,
26
+ partPrefix: /\b(LIS|LSM|STM)\d/i,
27
+ },
28
+ {
29
+ name: "Texas Instruments",
30
+ copyright: /Texas\s+Instruments/i,
31
+ docNumber: /\b(SLLS|SBAS|SBOS|SNOS)\w+/i,
32
+ domain: /\bti\.com\b/i,
33
+ partPrefix: /\b(SN65|SN74|TCA|ADS)\d/i,
34
+ },
35
+ {
36
+ name: "NXP",
37
+ copyright: /NXP\s+Semiconductors/i,
38
+ domain: /nxp\.com/i,
39
+ partPrefix: /\b(PCA|PCF)\d/i,
40
+ },
41
+ {
42
+ name: "Sensirion",
43
+ copyright: /Sensirion/i,
44
+ domain: /sensirion\.com/i,
45
+ partPrefix: /\b(SHT|SCD|SGP)\d/i,
46
+ },
47
+ ];
48
+ const STRONG = 3;
49
+ const WEAK = 1;
50
+ /** Below this, no confident vendor — fall back to generic. */
51
+ const MIN_SCORE = 2;
52
+ export function detectManufacturer(pages) {
53
+ const text = pages.map((p) => p.text).join(" ");
54
+ let best = { name: "generic", score: 0, signals: [] };
55
+ for (const vendor of VENDORS) {
56
+ let score = 0;
57
+ const signals = [];
58
+ if (vendor.copyright?.test(text)) {
59
+ score += STRONG;
60
+ signals.push("copyright");
61
+ }
62
+ if (vendor.docNumber?.test(text)) {
63
+ score += STRONG;
64
+ signals.push("doc-number");
65
+ }
66
+ if (vendor.domain?.test(text)) {
67
+ score += STRONG;
68
+ signals.push("url");
69
+ }
70
+ if (vendor.partPrefix?.test(text)) {
71
+ score += WEAK;
72
+ signals.push("part-prefix");
73
+ }
74
+ if (score > best.score)
75
+ best = { name: vendor.name, score, signals };
76
+ }
77
+ if (best.score < MIN_SCORE) {
78
+ return { manufacturer: "generic", confidence: 0, signals: [] };
79
+ }
80
+ return {
81
+ manufacturer: best.name,
82
+ confidence: Math.min(1, best.score / 6),
83
+ signals: best.signals,
84
+ };
85
+ }
86
+ //# sourceMappingURL=manufacturer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manufacturer.js","sourceRoot":"","sources":["../../src/pdf/manufacturer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { PageMap } from "./types.js";
2
+ /** Labels detected on a single page's text. */
3
+ export declare function labelPage(text: string): string[];
4
+ /** Build the page map from each page's text (index 0 => page 1). */
5
+ export declare function buildPageMap(pageTexts: string[]): PageMap;
@@ -0,0 +1,52 @@
1
+ // L2 — Page map. Labels pages by section keyword patterns so later layers do not
2
+ // search every page blindly. Pure over page text; unit tested without a PDF.
3
+ // Ordered, multi-word patterns to keep false positives low. Matching is over the
4
+ // whole page's text; heading-aware refinement can come once font-size heading
5
+ // extraction lands.
6
+ const LABEL_PATTERNS = [
7
+ {
8
+ label: "register_map",
9
+ pattern: /\bregister\s+(map|description|summary|table|overview)\b|\bmemory\s+map\b/i,
10
+ },
11
+ {
12
+ label: "electrical_characteristics",
13
+ pattern: /\belectrical\s+(characteristics|specifications?)\b|\b(dc|ac)\s+characteristics\b/i,
14
+ },
15
+ {
16
+ label: "timing",
17
+ pattern: /\btiming\s+(diagram|characteristics|specifications?|requirements?|parameters?)\b/i,
18
+ },
19
+ {
20
+ label: "pin_description",
21
+ pattern: /\bpin\s+(description|configuration|assignment|function)\b|\bpin(-|\s)?out\b/i,
22
+ },
23
+ {
24
+ label: "absolute_maximum_ratings",
25
+ pattern: /\babsolute\s+maximum\s+ratings\b/i,
26
+ },
27
+ {
28
+ label: "commands",
29
+ pattern: /\bcommand\s+(set|table|description|list)\b|\blist\s+of\s+commands\b/i,
30
+ },
31
+ ];
32
+ /** Labels detected on a single page's text. */
33
+ export function labelPage(text) {
34
+ const labels = [];
35
+ for (const { label, pattern } of LABEL_PATTERNS) {
36
+ if (pattern.test(text))
37
+ labels.push(label);
38
+ }
39
+ return labels;
40
+ }
41
+ /** Build the page map from each page's text (index 0 => page 1). */
42
+ export function buildPageMap(pageTexts) {
43
+ const map = {};
44
+ pageTexts.forEach((text, i) => {
45
+ const pageNo = i + 1;
46
+ for (const label of labelPage(text)) {
47
+ (map[label] ??= []).push(pageNo);
48
+ }
49
+ });
50
+ return map;
51
+ }
52
+ //# sourceMappingURL=page-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-map.js","sourceRoot":"","sources":["../../src/pdf/page-map.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,6EAA6E;AAI7E,iFAAiF;AACjF,8EAA8E;AAC9E,oBAAoB;AACpB,MAAM,cAAc,GAAsD;IACxE;QACE,KAAK,EAAE,cAAc;QACrB,OAAO,EACL,2EAA2E;KAC9E;IACD;QACE,KAAK,EAAE,4BAA4B;QACnC,OAAO,EACL,mFAAmF;KACtF;IACD;QACE,KAAK,EAAE,QAAQ;QACf,OAAO,EACL,mFAAmF;KACtF;IACD;QACE,KAAK,EAAE,iBAAiB;QACxB,OAAO,EACL,8EAA8E;KACjF;IACD;QACE,KAAK,EAAE,0BAA0B;QACjC,OAAO,EAAE,mCAAmC;KAC7C;IACD;QACE,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,sEAAsE;KAChF;CACF,CAAC;AAEF,+CAA+C;AAC/C,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,cAAc,EAAE,CAAC;QAChD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,SAAmB;IAC9C,MAAM,GAAG,GAAY,EAAE,CAAC;IACxB,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;QACrB,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PageContent } from "./types.js";
2
+ /** The most frequently occurring vendor part token, uppercased; "" if none. */
3
+ export declare function detectPart(pages: PageContent[]): string;
@@ -0,0 +1,41 @@
1
+ // L3 (metadata) — best-effort part-number extraction. The part number recurs in
2
+ // every page header/footer, so the most frequently matched vendor part token is
3
+ // a strong, deterministic signal. Degrades to "" (validator then warns) rather
4
+ // than guessing. Only used for naming/metadata — never for register geometry.
5
+ // Full part tokens per vendor family (broader than manufacturer.ts's prefix-only
6
+ // signals, which exist just to score a vendor). Ordered most-specific first.
7
+ const PART_PATTERNS = [
8
+ /\bBM[EPAI]\d{3}\b/gi, // BME280, BMP280, BMA...
9
+ /\bMCP\d{4,5}[A-Z]?\b/gi, // MCP23017, MCP23S17
10
+ /\bSHT\d{1,2}[A-Za-z]?\b/gi, // SHT3x, SHT31
11
+ /\b(?:SCD|SGP)\d{2,3}[A-Za-z]?\b/gi, // SCD40, SGP30
12
+ /\b(?:LIS|LSM)\d[A-Z0-9]{2,}\b/gi, // LIS3DH, LSM6DS3
13
+ /\bSTM32[A-Z]\d[A-Z0-9]+\b/gi,
14
+ /\b(?:PCA|PCF)\d{3,4}[A-Z]?\b/gi, // PCA9555
15
+ /\b(?:TCA|SN65|SN74|ADS)\d{3,4}[A-Z]?\b/gi,
16
+ ];
17
+ /** The most frequently occurring vendor part token, uppercased; "" if none. */
18
+ export function detectPart(pages) {
19
+ const text = pages
20
+ .map((p) => p.text)
21
+ .join(" ")
22
+ .replace(/\s+/g, " ");
23
+ const counts = new Map();
24
+ for (const pattern of PART_PATTERNS) {
25
+ for (const m of text.matchAll(pattern)) {
26
+ const token = m[0].toUpperCase();
27
+ counts.set(token, (counts.get(token) ?? 0) + 1);
28
+ }
29
+ }
30
+ let best = "";
31
+ let bestCount = 0;
32
+ for (const [token, count] of counts) {
33
+ // Prefer higher frequency; break ties by the longer (more specific) token.
34
+ if (count > bestCount || (count === bestCount && token.length > best.length)) {
35
+ best = token;
36
+ bestCount = count;
37
+ }
38
+ }
39
+ return best;
40
+ }
41
+ //# sourceMappingURL=part.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"part.js","sourceRoot":"","sources":["../../src/pdf/part.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAI9E,iFAAiF;AACjF,6EAA6E;AAC7E,MAAM,aAAa,GAAsB;IACvC,qBAAqB,EAAE,yBAAyB;IAChD,wBAAwB,EAAE,qBAAqB;IAC/C,2BAA2B,EAAE,eAAe;IAC5C,mCAAmC,EAAE,eAAe;IACpD,iCAAiC,EAAE,kBAAkB;IACrD,6BAA6B;IAC7B,gCAAgC,EAAE,UAAU;IAC5C,0CAA0C;CAC3C,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,UAAU,CAAC,KAAoB;IAC7C,MAAM,IAAI,GAAG,KAAK;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAExB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACpC,2EAA2E;QAC3E,IAAI,KAAK,GAAG,SAAS,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7E,IAAI,GAAG,KAAK,CAAC;YACb,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { PageContent, RegisterTable } from "./types.js";
2
+ /** Parse a register map table from a single page, if it holds one. */
3
+ export declare function parseRegisterTable(page: PageContent): RegisterTable | undefined;
4
+ /** Scan pages and return the first register map table found. */
5
+ export declare function findRegisterTable(pages: PageContent[]): RegisterTable | undefined;