dsh-eda-mcp 0.7.0 → 0.7.1
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.
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +0 -1
- package/README.md +6 -6
- package/README.zh.md +6 -6
- package/docs/release.md +1 -1
- package/dsh-eda-bridge-extension-0.4.0.zip +0 -0
- package/jlc-extension/api-bridge.js +1 -1
- package/lib/bridge.mjs +5 -5
- package/lib/client.js +0 -1
- package/lib/index.js +18 -3
- package/package.json +1 -2
- package/src/bridge/index.ts +6 -6
- package/src/client/EdaMcpSettingsCard.tsx +2 -1
- package/src/index.ts +23 -2
- package/src/routes.ts +3 -1
- package/dsh-eda-bridge-extension.zip +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
构建产物修正:**扩展 zip 只保留带版本号的那一个**。
|
|
6
|
+
|
|
7
|
+
- 0.7.0 里为了不打破旧引用,同时产出了 `dsh-eda-bridge-extension-0.4.0.zip` 和同内容的 `dsh-eda-bridge-extension.zip`(SHA256 完全一致)。两个同内容文件摆在一起只会让人分不清哪份是当前版本,属于多余的冗余,删掉。
|
|
8
|
+
- 现在只生成 `dsh-eda-bridge-extension-<线协议版本>.zip`。所有引用改为**按版本解析**:
|
|
9
|
+
- `src/index.ts`:从 `jlc-extension/extension.json` 读版本号拼出文件名(找不到时回退旧的固定名,兼容旧布局);
|
|
10
|
+
- `src/routes.ts`:下载响应头用实际文件名的 `basename`,浏览器另存下来自带版本号;
|
|
11
|
+
- 设置卡片的下载链接去掉固定的 `download` 属性,改由响应头决定文件名;
|
|
12
|
+
- 桥接里所有提示语改成 `dsh-eda-bridge-extension-' + VERSION + '.zip`;
|
|
13
|
+
- README(中/英)、CONTRIBUTING、`docs/release.md`、扩展头部注释同步。
|
|
14
|
+
- `package.json files` 去掉固定名条目,只保留 `dsh-eda-bridge-extension-*.zip`。
|
|
15
|
+
|
|
16
|
+
线协议仍是 **0.4.0**,扩展内容未变——已导入 0.4.0 的机器**无需重新导入**。
|
|
17
|
+
|
|
3
18
|
## 0.7.0 — 2026-10-20
|
|
4
19
|
|
|
5
20
|
主题:把「操作嘉立创EDA」从**画图**扩展到**应用与工程**层面,并把这一路真机联调暴露的 bug 全部修掉。
|
package/CONTRIBUTING.md
CHANGED
|
@@ -34,7 +34,6 @@ pnpm install
|
|
|
34
34
|
pnpm typecheck # tsc --noEmit
|
|
35
35
|
pnpm test # unit tests + the seam compatibility test
|
|
36
36
|
pnpm build # lib/*.js, lib/bridge.mjs, lib/client.js, dsh-eda-bridge-extension-<version>.zip
|
|
37
|
-
# (+ dsh-eda-bridge-extension.zip as a stable-name alias of the same build)
|
|
38
37
|
pnpm check # all three, in that order
|
|
39
38
|
pnpm docs # regenerate docs/tools.md from the built bridge
|
|
40
39
|
pnpm seam-check # assert the plugin's DSH contracts against another version line
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ DSH agent
|
|
|
16
16
|
└─ mcp__jlceda__eda_* tools
|
|
17
17
|
└─ @deepseek-ai/dsh-mcp-client (stdio)
|
|
18
18
|
└─ lib/bridge.mjs (local MCP + WebSocket server, 127.0.0.1:39009)
|
|
19
|
-
└─ JLCEDA Pro extension dsh-eda-bridge-extension
|
|
19
|
+
└─ JLCEDA Pro extension dsh-eda-bridge-extension-<version>.zip
|
|
20
20
|
└─ official EasyEDA Pro extension API (eda.sch_*)
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -36,9 +36,9 @@ pnpm add dsh-eda-mcp # or use the plugin manager in the Web GUI
|
|
|
36
36
|
# restart DSH
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
The published package already contains `lib/` and `dsh-eda-bridge-extension
|
|
39
|
+
The published package already contains `lib/` and `dsh-eda-bridge-extension-<version>.zip`, so no build
|
|
40
40
|
toolchain is needed. The extension still has to be imported into JLCEDA Pro by hand — the zip
|
|
41
|
-
sits at `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension
|
|
41
|
+
sits at `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension-<version>.zip` (or download it from the
|
|
42
42
|
settings card).
|
|
43
43
|
|
|
44
44
|
### 1. Install into the DSH web profile
|
|
@@ -71,10 +71,10 @@ The plugin now resolves a real Node executable (`src/node-runtime.ts`), in order
|
|
|
71
71
|
|
|
72
72
|
### 2. Import the JLCEDA Pro bridge extension
|
|
73
73
|
|
|
74
|
-
Build produces `dsh-eda-bridge-extension-<version>.zip`
|
|
74
|
+
Build produces `dsh-eda-bridge-extension-<version>.zip` only — a second copy under the stable name only made it unclear which build you were looking at. In JLCEDA Pro:
|
|
75
75
|
|
|
76
76
|
1. Open **扩展 → 导入** (Extensions → Import).
|
|
77
|
-
2. Select `dsh-eda-bridge-extension
|
|
77
|
+
2. Select `dsh-eda-bridge-extension-<version>.zip`.
|
|
78
78
|
3. In the extension list find **DSH EDA MCP Bridge**.
|
|
79
79
|
4. Enable it and, importantly, enable **允许外部交互 / Allow external interactions**.
|
|
80
80
|
|
|
@@ -188,7 +188,7 @@ Build outputs:
|
|
|
188
188
|
- `lib/index.js` — DSH host plugin.
|
|
189
189
|
- `lib/client.js` — DSH Web GUI settings card.
|
|
190
190
|
- `lib/bridge.mjs` — standalone MCP/WebSocket bridge child.
|
|
191
|
-
- `dsh-eda-bridge-extension
|
|
191
|
+
- `dsh-eda-bridge-extension-<version>.zip` — JLCEDA Pro extension (the version is the wire contract).
|
|
192
192
|
|
|
193
193
|
`lib/` and the zip are build outputs and are git-ignored; the host loads the packaged `lib/index.js`, so a change under `src/` needs `pnpm build` plus a Host restart (a plugin reload does not re-import a cached ESM module).
|
|
194
194
|
|
package/README.zh.md
CHANGED
|
@@ -16,7 +16,7 @@ DSH Agent
|
|
|
16
16
|
└─ mcp__jlceda__eda_* 工具
|
|
17
17
|
└─ @deepseek-ai/dsh-mcp-client(stdio)
|
|
18
18
|
└─ lib/bridge.mjs(本地 MCP + WebSocket 服务,127.0.0.1:39009)
|
|
19
|
-
└─ 嘉立创EDA专业版扩展 dsh-eda-bridge-extension
|
|
19
|
+
└─ 嘉立创EDA专业版扩展 dsh-eda-bridge-extension-<版本号>.zip
|
|
20
20
|
└─ 官方扩展 API(eda.sch_*)
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -47,8 +47,8 @@ pnpm add dsh-eda-mcp # 或用 Web GUI 的插件管理器安装
|
|
|
47
47
|
# 重启 DSH
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
发布包里已包含 `lib/` 与 `dsh-eda-bridge-extension
|
|
51
|
-
嘉立创EDA:zip 位于 `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension
|
|
50
|
+
发布包里已包含 `lib/` 与 `dsh-eda-bridge-extension-<线协议版本>.zip`,**不需要构建工具链**。扩展仍需手动导入
|
|
51
|
+
嘉立创EDA:zip 位于 `node_modules/dsh-eda-mcp/dsh-eda-bridge-extension-<线协议版本>.zip`(也可从设置卡片下载,下载到的文件名自带版本号)。
|
|
52
52
|
|
|
53
53
|
### 1. 安装到 DSH Web profile
|
|
54
54
|
|
|
@@ -65,10 +65,10 @@ cd dsh-eda-mcp && pnpm install && dsh plugin --profile web add "link:$PWD"
|
|
|
65
65
|
|
|
66
66
|
### 2. 导入嘉立创EDA桥接扩展
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
构建只生成 `dsh-eda-bridge-extension-<版本号>.zip`(不再保留同名别名——两个同内容文件只会让人分不清是哪一版)。在嘉立创EDA专业版中:
|
|
69
69
|
|
|
70
70
|
1. 打开 **扩展 → 导入**。
|
|
71
|
-
2. 选择 `dsh-eda-bridge-extension
|
|
71
|
+
2. 选择 `dsh-eda-bridge-extension-<版本号>.zip`。
|
|
72
72
|
3. 在扩展列表中找到 **DSH EDA MCP Bridge**。
|
|
73
73
|
4. 启用它,并务必开启 **允许外部交互**。
|
|
74
74
|
|
|
@@ -354,7 +354,7 @@ pnpm check # 以上三步
|
|
|
354
354
|
- `lib/index.js` — DSH Host 插件。
|
|
355
355
|
- `lib/client.js` — DSH Web GUI 设置卡片。
|
|
356
356
|
- `lib/bridge.mjs` — 独立 MCP/WebSocket 桥接子进程。
|
|
357
|
-
- `dsh-eda-bridge-extension
|
|
357
|
+
- `dsh-eda-bridge-extension-<版本号>.zip` — 嘉立创EDA专业版扩展(版本号 = 线协议版本)。
|
|
358
358
|
|
|
359
359
|
`lib/` 与 zip 都是构建产物(已 gitignore);宿主加载的是打包后的 `lib/index.js`,所以改 `src/` 后必须 `pnpm build` **并重启 Host**(插件重载不会重新 import 已缓存的 ESM 模块)。仓库结构、发布打 tag 流程、bridge/扩展版本规则见 [CONTRIBUTING.md](CONTRIBUTING.md);**发布到 npm** 的完整流程(首次手动发布,之后由 `v*` tag 经 trusted publishing/OIDC 自动发布)见 [docs/release.md](docs/release.md)。
|
|
360
360
|
|
package/docs/release.md
CHANGED
|
@@ -30,7 +30,7 @@ npm pack --dry-run # 看 tarball 内容
|
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
`npm pack --dry-run` 里必须能同时看到 **`lib/index.js`、`lib/client.js`、`lib/bridge.mjs`、
|
|
33
|
-
`dsh-eda-bridge-extension
|
|
33
|
+
`dsh-eda-bridge-extension-<线协议版本>.zip`、`cordis.patch.yml`、`docs/`**。`lib/` 与 zip 是构建产物(被 gitignore),
|
|
34
34
|
由 `prepare` 脚本在打包时生成 —— 所以**打包机必须是 Windows**(zip 用 PowerShell `Compress-Archive`)。
|
|
35
35
|
|
|
36
36
|
## 2. 首次手动发布(必须手动,顺序不能颠倒)
|
|
Binary file
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Connects to the local dsh-eda-mcp bridge process over WebSocket and executes
|
|
6
6
|
* a strict allow-list of official EasyEDA Pro extension APIs. Import through:
|
|
7
|
-
* Extensions → Import → select dsh-eda-bridge-extension
|
|
7
|
+
* Extensions → Import → select dsh-eda-bridge-extension-<version>.zip
|
|
8
8
|
* then enable "Allow interactive with external" for this extension.
|
|
9
9
|
*/
|
|
10
10
|
var DSH_EDA_MCP = {
|
package/lib/bridge.mjs
CHANGED
|
@@ -781,7 +781,7 @@ function sendToClient(client, method, args, timeoutMs) {
|
|
|
781
781
|
async function callExtension(method, args, timeoutMs) {
|
|
782
782
|
const pool = pickClients();
|
|
783
783
|
if (pool.length === 0) {
|
|
784
|
-
throw new Error(
|
|
784
|
+
throw new Error("JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension-" + VERSION + '.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.');
|
|
785
785
|
}
|
|
786
786
|
const deadline = Date.now() + timeoutMs;
|
|
787
787
|
let lastError;
|
|
@@ -1311,7 +1311,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1311
1311
|
versionMismatch: stale.length > 0,
|
|
1312
1312
|
mcpUnits: "0.01 inch",
|
|
1313
1313
|
defaultPaper: DEFAULT_PAPER,
|
|
1314
|
-
hint: ready.length === 0 ? "No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension.zip." : stale.length > 0 ? "Connected EDA extension " + stale.join(", ") + " does not match bridge " + VERSION + ". In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension.zip again, then restart the app; a simple reload keeps the old copy." : "ok"
|
|
1314
|
+
hint: ready.length === 0 ? "No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension-" + VERSION + ".zip." : stale.length > 0 ? "Connected EDA extension " + stale.join(", ") + " does not match bridge " + VERSION + ". In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension-" + VERSION + ".zip again, then restart the app; a simple reload keeps the old copy." : "ok"
|
|
1315
1315
|
};
|
|
1316
1316
|
}
|
|
1317
1317
|
case "eda_close_document":
|
|
@@ -1652,7 +1652,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1652
1652
|
connected: waited.connected,
|
|
1653
1653
|
connectedClients: waited.clients,
|
|
1654
1654
|
waitedMs: waited.waitedMs,
|
|
1655
|
-
hint: waited.connected ? "JLCEDA is connected; the eda_* tools can be used now." : "The app was started but no EDA extension connected yet. In JLCEDA Pro: \u6269\u5C55 \u2192 DSH MCP \u2192 \u8FDE\u63A5 (and enable \u201C\u5141\u8BB8\u5916\u90E8\u4EA4\u4E92\u201D); the extension must be imported once from dsh-eda-bridge-extension.zip."
|
|
1655
|
+
hint: waited.connected ? "JLCEDA is connected; the eda_* tools can be used now." : "The app was started but no EDA extension connected yet. In JLCEDA Pro: \u6269\u5C55 \u2192 DSH MCP \u2192 \u8FDE\u63A5 (and enable \u201C\u5141\u8BB8\u5916\u90E8\u4EA4\u4E92\u201D); the extension must be imported once from dsh-eda-bridge-extension-" + VERSION + ".zip."
|
|
1656
1656
|
};
|
|
1657
1657
|
}
|
|
1658
1658
|
case "eda_get_page_info": {
|
|
@@ -2215,7 +2215,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
2215
2215
|
componentsBefore: pcbComponentBaseline.length,
|
|
2216
2216
|
componentsAfter: after.length,
|
|
2217
2217
|
confirmedYet: after.length > pcbComponentBaseline.length,
|
|
2218
|
-
hint: "The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension.zip to use the async + polling flow."
|
|
2218
|
+
hint: "The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension-" + VERSION + ".zip to use the async + polling flow."
|
|
2219
2219
|
};
|
|
2220
2220
|
}
|
|
2221
2221
|
return {
|
|
@@ -2514,7 +2514,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
2514
2514
|
} catch (error) {
|
|
2515
2515
|
if (!isInternalHelperMissing(error)) throw error;
|
|
2516
2516
|
const blocking = asRecord(await callExtension("pcb_Document.autoRouting", [props], toolTimeout(args, timeoutMs)));
|
|
2517
|
-
return { ...blocking, backgroundUnavailable: true, props, hint: "The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension.zip to get the async + polling flow." };
|
|
2517
|
+
return { ...blocking, backgroundUnavailable: true, props, hint: "The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension-" + VERSION + ".zip to get the async + polling flow." };
|
|
2518
2518
|
}
|
|
2519
2519
|
const running = started.started === true || started.alreadyRunning === true;
|
|
2520
2520
|
return {
|
package/lib/client.js
CHANGED
|
@@ -1283,7 +1283,6 @@ function EdaMcpSettingsCard(props) {
|
|
|
1283
1283
|
"a",
|
|
1284
1284
|
{
|
|
1285
1285
|
href: "/api/dsh-eda-mcp/extension.zip",
|
|
1286
|
-
download: "dsh-eda-bridge-extension.zip",
|
|
1287
1286
|
style: { ...secondaryButtonStyle, textDecoration: "none", display: "inline-flex", width: "fit-content" },
|
|
1288
1287
|
children: t("settings.downloadExtension")
|
|
1289
1288
|
}
|
package/lib/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as mcpClient from "@deepseek-ai/dsh-mcp-client";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
5
6
|
import { dirname as dirname2 } from "node:path";
|
|
6
7
|
|
|
7
8
|
// src/guidance.ts
|
|
@@ -23,6 +24,7 @@ var EDA_MCP_GUIDANCE = [
|
|
|
23
24
|
|
|
24
25
|
// src/routes.ts
|
|
25
26
|
import { createReadStream } from "node:fs";
|
|
27
|
+
import { basename } from "node:path";
|
|
26
28
|
import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
|
|
27
29
|
|
|
28
30
|
// src/loopback.ts
|
|
@@ -196,7 +198,8 @@ function makeEdaMcpRoutes(deps) {
|
|
|
196
198
|
if (!isLoopbackRequest(req)) return json(res, 403, { ok: false, error: "forbidden" });
|
|
197
199
|
res.writeHead(200, {
|
|
198
200
|
"content-type": "application/zip",
|
|
199
|
-
|
|
201
|
+
// Keep the versioned file name the browser saves it as.
|
|
202
|
+
"content-disposition": 'attachment; filename="' + basename(deps.extensionZipPath) + '"',
|
|
200
203
|
"cache-control": "no-store"
|
|
201
204
|
});
|
|
202
205
|
const stream = createReadStream(deps.extensionZipPath);
|
|
@@ -532,7 +535,19 @@ function installSettingsSection(ctx, scoped, ns, schema, entry, hooks) {
|
|
|
532
535
|
}
|
|
533
536
|
var inject = ["webServer", "systemPrompt"];
|
|
534
537
|
var BRIDGE_URL = new URL("../lib/bridge.mjs", import.meta.url);
|
|
535
|
-
|
|
538
|
+
function resolveExtensionZipPath() {
|
|
539
|
+
const plain = fileURLToPath(new URL("../dsh-eda-bridge-extension.zip", import.meta.url));
|
|
540
|
+
try {
|
|
541
|
+
const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../jlc-extension/extension.json", import.meta.url)), "utf8"));
|
|
542
|
+
const version = typeof manifest.version === "string" ? manifest.version : "";
|
|
543
|
+
if (version !== "") {
|
|
544
|
+
const versioned = fileURLToPath(new URL("../dsh-eda-bridge-extension-" + version + ".zip", import.meta.url));
|
|
545
|
+
if (existsSync2(versioned)) return versioned;
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
return plain;
|
|
550
|
+
}
|
|
536
551
|
var BRIDGE_PATH = fileURLToPath(BRIDGE_URL);
|
|
537
552
|
function bridgeLaunchFor(active) {
|
|
538
553
|
const runtime = resolveBridgeRuntime();
|
|
@@ -614,7 +629,7 @@ function apply(ctx, config) {
|
|
|
614
629
|
ctx.effect(() => {
|
|
615
630
|
const disposers = makeEdaMcpRoutes({
|
|
616
631
|
getConfig: () => current(),
|
|
617
|
-
extensionZipPath:
|
|
632
|
+
extensionZipPath: resolveExtensionZipPath(),
|
|
618
633
|
reconnect: remountClient,
|
|
619
634
|
bridgeLaunch: describeBridgeLaunch,
|
|
620
635
|
diagnostics: () => diagnostics.liveSnapshot(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eda-mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "DeepSeek Harness (DSH) plugin — connects DSH to JLCEDA Pro (EasyEDA Pro) via a local MCP bridge and draws schematics through the JLCEDA extension API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"docs",
|
|
51
51
|
"jlc-extension",
|
|
52
52
|
"dsh-eda-bridge-extension-*.zip",
|
|
53
|
-
"dsh-eda-bridge-extension.zip",
|
|
54
53
|
"cordis.patch.yml",
|
|
55
54
|
"CHANGELOG.md",
|
|
56
55
|
"CONTRIBUTING.md",
|
package/src/bridge/index.ts
CHANGED
|
@@ -507,7 +507,7 @@ function sendToClient(client, method, args, timeoutMs) {
|
|
|
507
507
|
async function callExtension(method, args, timeoutMs) {
|
|
508
508
|
const pool = pickClients()
|
|
509
509
|
if (pool.length === 0) {
|
|
510
|
-
throw new Error('JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.')
|
|
510
|
+
throw new Error('JLCEDA bridge extension is not connected. Open JLCEDA Pro, import dsh-eda-bridge-extension-' + VERSION + '.zip, enable "Allow interactive with external", and keep a schematic or PCB page open.')
|
|
511
511
|
}
|
|
512
512
|
const deadline = Date.now() + timeoutMs
|
|
513
513
|
let lastError
|
|
@@ -1075,10 +1075,10 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1075
1075
|
versionMismatch: stale.length > 0,
|
|
1076
1076
|
mcpUnits: '0.01 inch', defaultPaper: DEFAULT_PAPER,
|
|
1077
1077
|
hint: ready.length === 0
|
|
1078
|
-
? 'No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension.zip.'
|
|
1078
|
+
? 'No JLCEDA extension is connected. Open JLCEDA Pro and click DSH MCP -> connect, or re-import dsh-eda-bridge-extension-' + VERSION + '.zip.'
|
|
1079
1079
|
: (stale.length > 0
|
|
1080
1080
|
? 'Connected EDA extension ' + stale.join(', ') + ' does not match bridge ' + VERSION +
|
|
1081
|
-
'. In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension.zip again, then restart the app; a simple reload keeps the old copy.'
|
|
1081
|
+
'. In JLCEDA Pro remove the OLD dsh-eda-bridge extension first, import dsh-eda-bridge-extension-' + VERSION + '.zip again, then restart the app; a simple reload keeps the old copy.'
|
|
1082
1082
|
: 'ok'),
|
|
1083
1083
|
}
|
|
1084
1084
|
}
|
|
@@ -1459,7 +1459,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1459
1459
|
waitedMs: waited.waitedMs,
|
|
1460
1460
|
hint: waited.connected
|
|
1461
1461
|
? 'JLCEDA is connected; the eda_* tools can be used now.'
|
|
1462
|
-
: 'The app was started but no EDA extension connected yet. In JLCEDA Pro: 扩展 → DSH MCP → 连接 (and enable “允许外部交互”); the extension must be imported once from dsh-eda-bridge-extension.zip.',
|
|
1462
|
+
: 'The app was started but no EDA extension connected yet. In JLCEDA Pro: 扩展 → DSH MCP → 连接 (and enable “允许外部交互”); the extension must be imported once from dsh-eda-bridge-extension-' + VERSION + '.zip.',
|
|
1463
1463
|
}
|
|
1464
1464
|
}
|
|
1465
1465
|
|
|
@@ -1982,7 +1982,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
1982
1982
|
triggered: true, backgroundUnavailable: true, importResult: result,
|
|
1983
1983
|
componentsBefore: pcbComponentBaseline.length, componentsAfter: after.length,
|
|
1984
1984
|
confirmedYet: after.length > pcbComponentBaseline.length,
|
|
1985
|
-
hint: 'The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension.zip to use the async + polling flow.',
|
|
1985
|
+
hint: 'The imported EDA extension has no background-job helper, so the sync was awaited. Re-import dsh-eda-bridge-extension-' + VERSION + '.zip to use the async + polling flow.',
|
|
1986
1986
|
}
|
|
1987
1987
|
}
|
|
1988
1988
|
return {
|
|
@@ -2284,7 +2284,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
2284
2284
|
// Older EDA extension without the background-job helper: fall back to the
|
|
2285
2285
|
// blocking call (which may hit the tool timeout for large boards).
|
|
2286
2286
|
const blocking = asRecord(await callExtension('pcb_Document.autoRouting', [props], toolTimeout(args, timeoutMs)))
|
|
2287
|
-
return { ...blocking, backgroundUnavailable: true, props, hint: 'The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension.zip to get the async + polling flow.' }
|
|
2287
|
+
return { ...blocking, backgroundUnavailable: true, props, hint: 'The imported EDA extension has no background-job helper, so routing was awaited. Re-import dsh-eda-bridge-extension-' + VERSION + '.zip to get the async + polling flow.' }
|
|
2288
2288
|
}
|
|
2289
2289
|
const running = started.started === true || started.alreadyRunning === true
|
|
2290
2290
|
return {
|
|
@@ -456,7 +456,8 @@ export function EdaMcpSettingsCard(props: EdaMcpSettingsCardProps) {
|
|
|
456
456
|
<Field label={t('settings.downloadExtension')} hint={t('settings.installHint')}>
|
|
457
457
|
<a
|
|
458
458
|
href="/api/dsh-eda-mcp/extension.zip"
|
|
459
|
-
download
|
|
459
|
+
// No download name: the server sends the versioned file name, so the
|
|
460
|
+
// saved copy always states which build it is.
|
|
460
461
|
style={{ ...secondaryButtonStyle, textDecoration: 'none', display: 'inline-flex', width: 'fit-content' }}
|
|
461
462
|
>
|
|
462
463
|
{t('settings.downloadExtension')}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {} from '@deepseek-ai/dsh-settings'
|
|
|
5
5
|
import * as mcpClient from '@deepseek-ai/dsh-mcp-client'
|
|
6
6
|
import z from '@deepseek-ai/schemastery'
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
8
9
|
import { dirname } from 'node:path'
|
|
9
10
|
import { EDA_MCP_GUIDANCE, SECTION_ORDER } from './guidance.ts'
|
|
10
11
|
import { makeEdaMcpRoutes, makeEdaMcpSettingsRoutes, type SettingsBridge } from './routes.ts'
|
|
@@ -141,7 +142,27 @@ function installSettingsSection<T>(ctx: Context, scoped: Context, ns: string, sc
|
|
|
141
142
|
export const inject = ['webServer', 'systemPrompt']
|
|
142
143
|
|
|
143
144
|
const BRIDGE_URL = new URL('../lib/bridge.mjs', import.meta.url)
|
|
144
|
-
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The extension zip is shipped as `dsh-eda-bridge-extension-<version>.zip` (the
|
|
148
|
+
* version is the wire contract in jlc-extension/extension.json). Resolve it from
|
|
149
|
+
* that manifest instead of hard-coding a file name; older layouts without a
|
|
150
|
+
* versioned file keep working through the plain-name fallback.
|
|
151
|
+
*/
|
|
152
|
+
function resolveExtensionZipPath(): string {
|
|
153
|
+
const plain = fileURLToPath(new URL('../dsh-eda-bridge-extension.zip', import.meta.url))
|
|
154
|
+
try {
|
|
155
|
+
const manifest = JSON.parse(readFileSync(fileURLToPath(new URL('../jlc-extension/extension.json', import.meta.url)), 'utf8')) as { version?: string }
|
|
156
|
+
const version = typeof manifest.version === 'string' ? manifest.version : ''
|
|
157
|
+
if (version !== '') {
|
|
158
|
+
const versioned = fileURLToPath(new URL('../dsh-eda-bridge-extension-' + version + '.zip', import.meta.url))
|
|
159
|
+
if (existsSync(versioned)) return versioned
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// Bundle-only installs may lack the manifest; the plain name is the fallback.
|
|
163
|
+
}
|
|
164
|
+
return plain
|
|
165
|
+
}
|
|
145
166
|
|
|
146
167
|
/** Bridge script path, resolved once. */
|
|
147
168
|
const BRIDGE_PATH = fileURLToPath(BRIDGE_URL)
|
|
@@ -252,7 +273,7 @@ export function apply(ctx: Context, config?: EdaMcpConfig): void {
|
|
|
252
273
|
ctx.effect(() => {
|
|
253
274
|
const disposers = makeEdaMcpRoutes({
|
|
254
275
|
getConfig: () => current(),
|
|
255
|
-
extensionZipPath:
|
|
276
|
+
extensionZipPath: resolveExtensionZipPath(),
|
|
256
277
|
reconnect: remountClient,
|
|
257
278
|
bridgeLaunch: describeBridgeLaunch,
|
|
258
279
|
diagnostics: () => diagnostics.liveSnapshot(),
|
package/src/routes.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createReadStream } from 'node:fs'
|
|
2
|
+
import { basename } from 'node:path'
|
|
2
3
|
import type { ServerResponse } from 'node:http'
|
|
3
4
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
|
|
4
5
|
import { SettingsConflictError, type SettingsPathOp } from '@deepseek-ai/dsh-settings'
|
|
@@ -186,7 +187,8 @@ export function makeEdaMcpRoutes(deps: EdaMcpRouteDeps): WebRoute[] {
|
|
|
186
187
|
if (!isLoopbackRequest(req)) return json(res, 403, { ok: false, error: 'forbidden' })
|
|
187
188
|
res.writeHead(200, {
|
|
188
189
|
'content-type': 'application/zip',
|
|
189
|
-
|
|
190
|
+
// Keep the versioned file name the browser saves it as.
|
|
191
|
+
'content-disposition': 'attachment; filename="' + basename(deps.extensionZipPath) + '"',
|
|
190
192
|
'cache-control': 'no-store',
|
|
191
193
|
})
|
|
192
194
|
const stream = createReadStream(deps.extensionZipPath)
|
|
Binary file
|