@hybridlabor-api/bdb-hardware-pcb 0.1.0 → 0.1.2

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.
@@ -5,7 +5,7 @@
5
5
  "command": "__INSTALL_DIR__/scripts/run_kicad_mcp.sh",
6
6
  "args": [],
7
7
  "env": {
8
- "KICAD_CLI_PATH": "/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"
8
+ "KICAD_CLI_PATH": "__KICAD_CLI_PATH__"
9
9
  }
10
10
  },
11
11
  "openscad": {
@@ -5,7 +5,7 @@
5
5
  "command": "__INSTALL_DIR__/scripts/run_kicad_mcp.sh",
6
6
  "args": [],
7
7
  "env": {
8
- "KICAD_CLI_PATH": "/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"
8
+ "KICAD_CLI_PATH": "__KICAD_CLI_PATH__"
9
9
  }
10
10
  },
11
11
  "openscad": {
@@ -14,7 +14,7 @@ args = []
14
14
  enabled = true
15
15
 
16
16
  [mcp_servers.kicad.env]
17
- KICAD_CLI_PATH = "/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"
17
+ KICAD_CLI_PATH = "__KICAD_CLI_PATH__"
18
18
 
19
19
  [mcp_servers.openscad]
20
20
  command = "__INSTALL_DIR__/scripts/run_openscad_mcp.sh"
package/installer.js CHANGED
@@ -25,6 +25,7 @@
25
25
  const os = require('os');
26
26
  const path = require('path');
27
27
  const fs = require('fs');
28
+ const crypto = require('crypto');
28
29
  const { spawnSync } = require('child_process');
29
30
 
30
31
  const AUTO = process.argv.includes('--auto');
@@ -71,12 +72,71 @@ function findPython() {
71
72
  return null;
72
73
  }
73
74
 
75
+ function findKicadCliPath() {
76
+ if (process.env.KICAD_CLI_PATH) return process.env.KICAD_CLI_PATH;
77
+ if (commandExists('kicad-cli')) return 'kicad-cli';
78
+ let candidates = [];
79
+ if (IS_WIN) {
80
+ const base = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'KiCad');
81
+ if (fs.existsSync(base)) {
82
+ candidates = fs.readdirSync(base)
83
+ .map((v) => path.join(base, v, 'bin', 'kicad-cli.exe'))
84
+ .sort()
85
+ .reverse(); // prefer the highest version dir (e.g. 9.0 over 7.0)
86
+ }
87
+ } else if (process.platform === 'darwin') {
88
+ candidates = ['/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli'];
89
+ } else {
90
+ candidates = ['/usr/bin/kicad-cli', '/usr/local/bin/kicad-cli'];
91
+ }
92
+ const found = candidates.find((p) => fs.existsSync(p));
93
+ if (found) return found;
94
+ log.warn('kicad-cli not found on PATH or in known install locations — set the KICAD_CLI_PATH env var manually once KiCad is installed.');
95
+ return 'kicad-cli';
96
+ }
97
+
98
+ // Hash of pyproject.toml, used as a cheap "did the dependency spec change"
99
+ // marker. AOS's swap-in-place upgrade preserves .venv across a version bump
100
+ // (that's the whole point of the PRESERVE list), so "the venv dir exists" is
101
+ // NOT enough on its own to prove it's still current for THIS version -- the
102
+ // hash comparison is what makes skipping safe across an upgrade that did
103
+ // change deps, not just across an unattended re-run that didn't.
104
+ function depsSpecHash(serverDir) {
105
+ const p = path.join(serverDir, 'pyproject.toml');
106
+ if (!fs.existsSync(p)) return null;
107
+ return crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
108
+ }
109
+
74
110
  function setupServerEnv(server, uvBin, pythonBin) {
75
111
  const venvDir = path.join(server.dir, '.venv');
112
+ const markerPath = path.join(venvDir, '.aos-deps-hash');
113
+ const currentHash = depsSpecHash(server.dir);
114
+
115
+ // Every Quick Update used to re-run `uv sync` / `pip install` here
116
+ // unconditionally, even when downloadOrUpdateModule() (in the main AOS
117
+ // installer) had already determined this module's version hadn't
118
+ // changed at all -- real network calls and, on the pip fallback path
119
+ // with no uv on PATH, a genuinely slow full dependency resolve, every
120
+ // single run, for nothing.
121
+ if (currentHash && fs.existsSync(venvDir) && fs.existsSync(markerPath)) {
122
+ try {
123
+ if (fs.readFileSync(markerPath, 'utf8').trim() === currentHash) {
124
+ log.ok(`${server.name}-mcp-server: dependencies already up to date, skipping sync`);
125
+ return true;
126
+ }
127
+ } catch (e) { /* fall through to a real sync if the marker is unreadable */ }
128
+ }
129
+
130
+ const markDone = () => {
131
+ if (!currentHash) return;
132
+ try { fs.writeFileSync(markerPath, currentHash); } catch (e) { /* non-fatal: just means next run re-syncs once more */ }
133
+ };
134
+
76
135
  if (uvBin) {
77
136
  const res = spawnSync(uvBin, ['sync'], { cwd: server.dir, stdio: 'inherit' });
78
137
  if (res.status === 0) {
79
138
  log.ok(`${server.name}-mcp-server: dependencies synced via uv`);
139
+ markDone();
80
140
  return true;
81
141
  }
82
142
  log.warn(`${server.name}-mcp-server: 'uv sync' failed (exit ${res.status}); falling back to venv+pip`);
@@ -99,6 +159,7 @@ function setupServerEnv(server, uvBin, pythonBin) {
99
159
  return false;
100
160
  }
101
161
  log.ok(`${server.name}-mcp-server: dependencies installed via venv+pip`);
162
+ markDone();
102
163
  return true;
103
164
  }
104
165
 
@@ -166,7 +227,7 @@ function mcpServerEntries() {
166
227
  kicad: {
167
228
  command: kicad.command,
168
229
  args: kicad.args,
169
- env: { KICAD_CLI_PATH: process.env.KICAD_CLI_PATH || '/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli' },
230
+ env: { KICAD_CLI_PATH: findKicadCliPath() },
170
231
  },
171
232
  openscad: {
172
233
  command: openscad.command,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/bdb-hardware-pcb",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "BDB Hardware & PCB — KiCad + OpenSCAD MCP servers and AI-era skills for schematic capture, PCB layout/routing, DFM sign-off, and parametric 3D enclosure design",
5
5
  "main": "installer.js",
6
6
  "bin": {