dsh-packycode-api 1.0.3 → 1.0.4
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 +5 -0
- package/README.en.md +5 -3
- package/README.md +5 -3
- package/lib/client.js +197 -2
- package/lib/index.js +396 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.4
|
|
4
|
+
|
|
5
|
+
- Settings can check for and install a newer npm release. **Update plugin** installs the exact published version into the owning profile with the host's bundled pnpm, then reads `package.json` back before reporting success. A `link:` checkout is left alone. The desktop app still needs a full restart afterwards.
|
|
6
|
+
- The update runs with `minimumReleaseAge=0`, so neither a fresh release of this plugin nor a fresh release of a sibling plugin already in the profile lockfile blocks it. The README FAQ explains the `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` that a Plugins-page install can still hit.
|
|
7
|
+
|
|
3
8
|
## 1.0.3
|
|
4
9
|
|
|
5
10
|
- The Claude CLI timeout test holds the event loop open. The runner unrefs its timer and the test stub is not a real process, so Node 22's test runner cancelled the still-pending test and stopped the publish workflow. Plugin behavior is unchanged. `1.0.2` was tagged but not published.
|
package/README.en.md
CHANGED
|
@@ -42,7 +42,7 @@ Open the desktop application's **Plugins** page, click **Add plugin**, and enter
|
|
|
42
42
|
dsh-packycode-api
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.0.
|
|
45
|
+
Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.0.4`.
|
|
46
46
|
|
|
47
47
|
Then **manually restart the desktop application**. Refreshing the page does not reload the plugin inside the Host process.
|
|
48
48
|
|
|
@@ -61,7 +61,7 @@ Choose a model from the `PackyCode` group. `deepseek-flash` and `gpt-6.1-sol` wo
|
|
|
61
61
|
|
|
62
62
|
The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
|
|
63
63
|
|
|
64
|
-
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.
|
|
64
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.4`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
|
|
65
65
|
|
|
66
66
|
A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
|
|
67
67
|
|
|
@@ -117,7 +117,7 @@ A model in the list does not mean the current token group allows it. When changi
|
|
|
117
117
|
|
|
118
118
|
## Update and uninstall
|
|
119
119
|
|
|
120
|
-
**Update:**
|
|
120
|
+
**Update:** open **Settings → PackyCode** and, when a newer version is listed, click **Update plugin**. It installs that exact version into the owning profile with DSH's bundled pnpm and reads `package.json` back before reporting success. A local `link:` checkout does not get the button. Then quit and reopen the desktop application. A page refresh does not reload the Host. Reinstalling `dsh-packycode-api` from the **Plugins** page does the same job.
|
|
121
121
|
|
|
122
122
|
**Uninstall:** remove this plugin on the **Plugins** page, then restart the desktop application. Uninstalling does not remove other plugins, the profile, or the token in the PackyCode console.
|
|
123
123
|
|
|
@@ -129,6 +129,8 @@ A model in the list does not mean the current token group allows it. When changi
|
|
|
129
129
|
|
|
130
130
|
**A rate limit disabled the key?** It should not. HTTP 429 does not rotate. Exhausted quota or HTTP 401 does; after a top-up, click **Re-enable**.
|
|
131
131
|
|
|
132
|
+
**Installing from the Plugins page fails with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm refuses packages published less than 24 hours ago and checks the whole profile lockfile. The package it names is often another plugin that was just updated, not this one. Retry once it is 24 hours old, or add the reported `name@version` under `minimumReleaseAgeExclude` in the profile's `pnpm-workspace.yaml`. Once this plugin is installed, **Update plugin** in Settings is not subject to that check.
|
|
133
|
+
|
|
132
134
|
**The CLI is installed, but Claude still fails?** A host launched from Finder or the Start menu usually has a shorter `PATH` than a terminal. The plugin also checks the official install location, common package-manager directories, and `npm prefix -g`. If it still cannot find the binary, set `claudeCliPath` or click **Check again**. After a Host change or a reinstall, quit and reopen the desktop application.
|
|
133
135
|
|
|
134
136
|
## Security and scope
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
dsh-packycode-api
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.0.
|
|
45
|
+
在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.0.4`。
|
|
46
46
|
|
|
47
47
|
完成后**手动重启桌面应用**。仅刷新页面不会重新加载 Host 里的插件。
|
|
48
48
|
|
|
@@ -61,7 +61,7 @@ dsh-packycode-api
|
|
|
61
61
|
|
|
62
62
|
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
63
63
|
|
|
64
|
-
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.
|
|
64
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.4`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
|
|
65
65
|
|
|
66
66
|
本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
|
|
67
67
|
|
|
@@ -117,7 +117,7 @@ dsh-packycode-api
|
|
|
117
117
|
|
|
118
118
|
## 更新与卸载
|
|
119
119
|
|
|
120
|
-
**更新:**
|
|
120
|
+
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装 `dsh-packycode-api`。
|
|
121
121
|
|
|
122
122
|
**卸载:** 在**插件**页卸载本插件,然后重启桌面应用。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
|
|
123
123
|
|
|
@@ -129,6 +129,8 @@ dsh-packycode-api
|
|
|
129
129
|
|
|
130
130
|
**限速之后 Key 被停用了?** 不该发生。429 不会切换 Key。额度用尽或 401 才会停用;充值后点「重新启用」。
|
|
131
131
|
|
|
132
|
+
**插件页安装报 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm 默认拒绝发布不满 24 小时的包,并且会检查 profile 里的整个 lockfile。报错里点名的往往是另一个刚更新过的插件,不是本插件。等它发布满 24 小时再重试,或把报错里的 `包名@版本` 加到 profile 目录 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 下。已安装本插件后,设置页的「更新插件」不受这个限制。
|
|
133
|
+
|
|
132
134
|
**装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
|
|
133
135
|
|
|
134
136
|
## 安全边界
|
package/lib/client.js
CHANGED
|
@@ -163,7 +163,26 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
cliNetwork: "下载失败,请检查网络或 npm 镜像设置后重试。",
|
|
164
164
|
cliTimeout: "安装超时,请检查网络后重试。",
|
|
165
165
|
cliNotFoundAfterInstall: "npm 显示安装完成,但仍找不到 claude 命令。请在终端执行 claude --version 确认,然后点「重新检测」。",
|
|
166
|
-
cliWindowsNote: "Windows 上 Claude Code 还需要 Git for Windows(https://git-scm.com/downloads/win)。"
|
|
166
|
+
cliWindowsNote: "Windows 上 Claude Code 还需要 Git for Windows(https://git-scm.com/downloads/win)。",
|
|
167
|
+
versionTitle: "插件版本",
|
|
168
|
+
versionCurrent: "当前版本",
|
|
169
|
+
versionLatest: "最新版本",
|
|
170
|
+
versionCheck: "检查更新",
|
|
171
|
+
versionChecking: "正在检查更新…",
|
|
172
|
+
versionFailed: "无法检查最新版本。",
|
|
173
|
+
versionUpToDate: "已是最新版本。",
|
|
174
|
+
versionAvailable: "发现新版本 v{version}。",
|
|
175
|
+
versionLinked: "当前是本地开发安装,请在插件仓库拉取代码并重新构建。",
|
|
176
|
+
versionManual: "无法在这里更新。请在桌面应用的插件页重新安装 dsh-packycode-api。",
|
|
177
|
+
versionUpdate: "更新插件",
|
|
178
|
+
versionUpdating: "正在更新…",
|
|
179
|
+
versionUpdateFailed: "更新失败,请重试,或在插件页重新安装。",
|
|
180
|
+
versionUpdated: "已更新到 v{version}。",
|
|
181
|
+
versionUpdatedHint: "新版本需要完全退出并重新打开桌面应用才会生效;只刷新页面不会重载 Host 里的插件。",
|
|
182
|
+
versionRestart: "如何重启",
|
|
183
|
+
versionRestartHint: "插件无法安全地重启桌面应用。请完全退出 DSH,再重新打开。",
|
|
184
|
+
versionRefresh: "刷新界面",
|
|
185
|
+
versionLater: "稍后"
|
|
167
186
|
};
|
|
168
187
|
const en = {
|
|
169
188
|
nav: "PackyCode",
|
|
@@ -234,7 +253,26 @@ window.__ModuleLoader__.load({
|
|
|
234
253
|
cliNetwork: "The download failed. Check your network or npm registry settings and retry.",
|
|
235
254
|
cliTimeout: "The install timed out. Check your network and retry.",
|
|
236
255
|
cliNotFoundAfterInstall: "npm reported success, but the claude command still cannot be found. Run claude --version in a terminal, then click Check again.",
|
|
237
|
-
cliWindowsNote: "On Windows, Claude Code also needs Git for Windows (https://git-scm.com/downloads/win)."
|
|
256
|
+
cliWindowsNote: "On Windows, Claude Code also needs Git for Windows (https://git-scm.com/downloads/win).",
|
|
257
|
+
versionTitle: "Plugin version",
|
|
258
|
+
versionCurrent: "Current version",
|
|
259
|
+
versionLatest: "Latest version",
|
|
260
|
+
versionCheck: "Check for updates",
|
|
261
|
+
versionChecking: "Checking for updates…",
|
|
262
|
+
versionFailed: "Could not check the latest version.",
|
|
263
|
+
versionUpToDate: "You are on the latest version.",
|
|
264
|
+
versionAvailable: "A new version v{version} is available.",
|
|
265
|
+
versionLinked: "Installed from a local checkout. Pull and rebuild the plugin repository instead.",
|
|
266
|
+
versionManual: "This install cannot be updated here. Reinstall dsh-packycode-api from the desktop Plugins page.",
|
|
267
|
+
versionUpdate: "Update plugin",
|
|
268
|
+
versionUpdating: "Updating…",
|
|
269
|
+
versionUpdateFailed: "The update failed. Try again, or reinstall from the Plugins page.",
|
|
270
|
+
versionUpdated: "Updated to v{version}.",
|
|
271
|
+
versionUpdatedHint: "Quit and reopen the desktop application. Refreshing the page does not reload the plugin inside the Host.",
|
|
272
|
+
versionRestart: "How to restart",
|
|
273
|
+
versionRestartHint: "The plugin cannot safely restart the desktop application. Quit DSH completely, then open it again.",
|
|
274
|
+
versionRefresh: "Refresh page",
|
|
275
|
+
versionLater: "Later"
|
|
238
276
|
};
|
|
239
277
|
const STYLE = `
|
|
240
278
|
.packyCodeSection{display:flex;flex-direction:column;gap:16px;font-size:13px;line-height:20px}
|
|
@@ -269,6 +307,9 @@ window.__ModuleLoader__.load({
|
|
|
269
307
|
.packyCodeWarning{color:var(--dsw-alias-state-warning-primary,#c27c0e);font-weight:500}
|
|
270
308
|
.packyCodeDetail{margin:0;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;white-space:pre-wrap;word-break:break-all;max-height:160px;overflow:auto}
|
|
271
309
|
.packyCodeMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;word-break:break-all}
|
|
310
|
+
.packyCodeVersionRows{display:flex;flex-direction:column;gap:6px}
|
|
311
|
+
.packyCodeVersionRow{display:flex;justify-content:space-between;gap:12px}
|
|
312
|
+
.packyCodeVersionValue{font-variant-numeric:tabular-nums;font-weight:600}
|
|
272
313
|
.packyCodeComposer{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}
|
|
273
314
|
@media(max-width:640px){.packyCodeRow{align-items:flex-start;flex-direction:column}.packyCodeActions{width:100%}}
|
|
274
315
|
`;
|
|
@@ -451,6 +492,156 @@ window.__ModuleLoader__.load({
|
|
|
451
492
|
]
|
|
452
493
|
});
|
|
453
494
|
}
|
|
495
|
+
function VersionCard({ call, t }) {
|
|
496
|
+
const [info, setInfo] = react.useState();
|
|
497
|
+
const [loading, setLoading] = react.useState(true);
|
|
498
|
+
const [error, setError] = react.useState(false);
|
|
499
|
+
const [updating, setUpdating] = react.useState(false);
|
|
500
|
+
const [updateFailed, setUpdateFailed] = react.useState(false);
|
|
501
|
+
const [updated, setUpdated] = react.useState();
|
|
502
|
+
const [restartHint, setRestartHint] = react.useState(false);
|
|
503
|
+
const generation = react.useRef(0);
|
|
504
|
+
const load = (force) => {
|
|
505
|
+
const current = ++generation.current;
|
|
506
|
+
setLoading(true);
|
|
507
|
+
setError(false);
|
|
508
|
+
call("plugin/version", { force }).then((value) => {
|
|
509
|
+
if (generation.current === current) setInfo(value);
|
|
510
|
+
}).catch(() => {
|
|
511
|
+
if (generation.current === current) setError(true);
|
|
512
|
+
}).finally(() => {
|
|
513
|
+
if (generation.current === current) setLoading(false);
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
react.useEffect(() => {
|
|
517
|
+
load(false);
|
|
518
|
+
return () => {
|
|
519
|
+
generation.current += 1;
|
|
520
|
+
};
|
|
521
|
+
}, []);
|
|
522
|
+
const update = () => {
|
|
523
|
+
setUpdating(true);
|
|
524
|
+
setUpdateFailed(false);
|
|
525
|
+
call("plugin/update").then((value) => {
|
|
526
|
+
setUpdated(value);
|
|
527
|
+
setRestartHint(false);
|
|
528
|
+
}).catch(() => setUpdateFailed(true)).finally(() => setUpdating(false));
|
|
529
|
+
};
|
|
530
|
+
const installKind = info?.install?.kind;
|
|
531
|
+
const showStatus = info !== void 0 && updated === void 0;
|
|
532
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
533
|
+
className: "packyCodeCard",
|
|
534
|
+
children: [
|
|
535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
536
|
+
className: "packyCodeCardHeader",
|
|
537
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
538
|
+
className: "packyCodeCardTitle",
|
|
539
|
+
children: t("versionTitle")
|
|
540
|
+
}), updated === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
541
|
+
type: "button",
|
|
542
|
+
className: "packyCodeButton",
|
|
543
|
+
disabled: loading || updating,
|
|
544
|
+
onClick: () => load(true),
|
|
545
|
+
children: loading ? t("versionChecking") : t("versionCheck")
|
|
546
|
+
})]
|
|
547
|
+
}),
|
|
548
|
+
loading && info === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
549
|
+
className: "packyCodeNote",
|
|
550
|
+
role: "status",
|
|
551
|
+
children: t("versionChecking")
|
|
552
|
+
}),
|
|
553
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
554
|
+
className: "packyCodeError",
|
|
555
|
+
role: "alert",
|
|
556
|
+
children: t("versionFailed")
|
|
557
|
+
}),
|
|
558
|
+
info !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
559
|
+
className: "packyCodeVersionRows",
|
|
560
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
561
|
+
className: "packyCodeVersionRow",
|
|
562
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionCurrent") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
563
|
+
className: "packyCodeVersionValue",
|
|
564
|
+
children: ["v", info.current]
|
|
565
|
+
})]
|
|
566
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
567
|
+
className: "packyCodeVersionRow",
|
|
568
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionLatest") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
569
|
+
className: "packyCodeVersionValue",
|
|
570
|
+
children: ["v", info.latest]
|
|
571
|
+
})]
|
|
572
|
+
})]
|
|
573
|
+
}),
|
|
574
|
+
showStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
575
|
+
className: "packyCodeNote",
|
|
576
|
+
role: "status",
|
|
577
|
+
children: info.updateAvailable ? t("versionAvailable").replace("{version}", info.latest) : t("versionUpToDate")
|
|
578
|
+
}),
|
|
579
|
+
showStatus && info.updateAvailable && installKind === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
580
|
+
className: "packyCodeActions",
|
|
581
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
582
|
+
type: "button",
|
|
583
|
+
className: "packyCodeButton packyCodeButtonPrimary",
|
|
584
|
+
disabled: updating,
|
|
585
|
+
onClick: update,
|
|
586
|
+
children: updating ? t("versionUpdating") : t("versionUpdate")
|
|
587
|
+
})
|
|
588
|
+
}),
|
|
589
|
+
showStatus && installKind === "link" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
590
|
+
className: "packyCodeNote",
|
|
591
|
+
children: t("versionLinked")
|
|
592
|
+
}),
|
|
593
|
+
showStatus && info.updateAvailable && installKind === "unknown" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
594
|
+
className: "packyCodeNote",
|
|
595
|
+
children: t("versionManual")
|
|
596
|
+
}),
|
|
597
|
+
updateFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
598
|
+
className: "packyCodeError",
|
|
599
|
+
role: "alert",
|
|
600
|
+
children: t("versionUpdateFailed")
|
|
601
|
+
}),
|
|
602
|
+
updated !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
603
|
+
role: "status",
|
|
604
|
+
children: [
|
|
605
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
606
|
+
className: "packyCodeNote",
|
|
607
|
+
children: t("versionUpdated").replace("{version}", updated.version)
|
|
608
|
+
}),
|
|
609
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
610
|
+
className: "packyCodeNote",
|
|
611
|
+
children: t("versionUpdatedHint")
|
|
612
|
+
}),
|
|
613
|
+
restartHint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
614
|
+
className: "packyCodeNote",
|
|
615
|
+
children: t("versionRestartHint")
|
|
616
|
+
}),
|
|
617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
618
|
+
className: "packyCodeActions",
|
|
619
|
+
children: [
|
|
620
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
621
|
+
type: "button",
|
|
622
|
+
className: "packyCodeButton",
|
|
623
|
+
onClick: () => setRestartHint(true),
|
|
624
|
+
children: t("versionRestart")
|
|
625
|
+
}),
|
|
626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
627
|
+
type: "button",
|
|
628
|
+
className: "packyCodeButton packyCodeButtonPrimary",
|
|
629
|
+
onClick: () => window.location.reload(),
|
|
630
|
+
children: t("versionRefresh")
|
|
631
|
+
}),
|
|
632
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
633
|
+
type: "button",
|
|
634
|
+
className: "packyCodeButton",
|
|
635
|
+
onClick: () => setUpdated(void 0),
|
|
636
|
+
children: t("versionLater")
|
|
637
|
+
})
|
|
638
|
+
]
|
|
639
|
+
})
|
|
640
|
+
]
|
|
641
|
+
})
|
|
642
|
+
]
|
|
643
|
+
});
|
|
644
|
+
}
|
|
454
645
|
function PackyCodeSection({ rpc, t }) {
|
|
455
646
|
const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
|
|
456
647
|
const [status, setStatus] = react.useState();
|
|
@@ -555,6 +746,10 @@ window.__ModuleLoader__.load({
|
|
|
555
746
|
className: "packyCodeIntro",
|
|
556
747
|
children: t("intro")
|
|
557
748
|
}),
|
|
749
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
|
|
750
|
+
call,
|
|
751
|
+
t
|
|
752
|
+
}),
|
|
558
753
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClaudeCliCard, {
|
|
559
754
|
call,
|
|
560
755
|
t
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { homedir, tmpdir } from "node:os";
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
10
10
|
import { credentialKey } from "@deepseek-ai/dsh-credentials";
|
|
11
|
+
import { readFile, readdir, realpath } from "node:fs/promises";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
11
13
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
12
14
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
13
15
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
@@ -67,7 +69,7 @@ const DEFAULT_REASONING_LEVEL = "high";
|
|
|
67
69
|
* @module dsh-packycode-api/balance
|
|
68
70
|
*/
|
|
69
71
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
70
|
-
const clone = (value) => structuredClone(value);
|
|
72
|
+
const clone$1 = (value) => structuredClone(value);
|
|
71
73
|
function number(value) {
|
|
72
74
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
73
75
|
if (typeof value === "string" && value.trim() !== "") {
|
|
@@ -274,7 +276,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
274
276
|
return Object.freeze({
|
|
275
277
|
async read({ force = false, signal } = {}) {
|
|
276
278
|
signal?.throwIfAborted();
|
|
277
|
-
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone(cached);
|
|
279
|
+
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone$1(cached);
|
|
278
280
|
const createPending = (requestSignal) => {
|
|
279
281
|
const observedGeneration = generation;
|
|
280
282
|
return load(requestSignal).then((value) => {
|
|
@@ -282,7 +284,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
282
284
|
return value;
|
|
283
285
|
});
|
|
284
286
|
};
|
|
285
|
-
if (force) return clone(await createPending(signal));
|
|
287
|
+
if (force) return clone$1(await createPending(signal));
|
|
286
288
|
const pending = inFlight ?? createPending();
|
|
287
289
|
if (inFlight === void 0) {
|
|
288
290
|
inFlight = pending;
|
|
@@ -291,7 +293,7 @@ function createBalanceReader({ listKeys, getHosts, fetchImpl = fetch, now = Date
|
|
|
291
293
|
};
|
|
292
294
|
pending.then(clear, clear);
|
|
293
295
|
}
|
|
294
|
-
return clone(await pending);
|
|
296
|
+
return clone$1(await pending);
|
|
295
297
|
},
|
|
296
298
|
invalidate() {
|
|
297
299
|
generation += 1;
|
|
@@ -1960,6 +1962,383 @@ function capClaudeMode(requested, ceiling) {
|
|
|
1960
1962
|
return RANK[requested] > RANK[ceiling] ? ceiling : requested;
|
|
1961
1963
|
}
|
|
1962
1964
|
//#endregion
|
|
1965
|
+
//#region src/plugin-version.js
|
|
1966
|
+
/**
|
|
1967
|
+
* Version self-knowledge for the settings panel.
|
|
1968
|
+
*
|
|
1969
|
+
* The running version comes from this package. The latest version comes from
|
|
1970
|
+
* the npm registry. An in-place update is only offered for a registry install,
|
|
1971
|
+
* and it installs that exact version with the host's own pnpm — never through
|
|
1972
|
+
* `dsh plugin --profile desktop`, which the desktop app refuses. Registry
|
|
1973
|
+
* bodies and package-manager output stay host-side.
|
|
1974
|
+
*
|
|
1975
|
+
* @module dsh-packycode-api/plugin-version
|
|
1976
|
+
*/
|
|
1977
|
+
const PACKAGE_NAME = "dsh-packycode-api";
|
|
1978
|
+
const NPM_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
1979
|
+
const DEFAULT_VERSION_TTL_MS = 5 * 6e4;
|
|
1980
|
+
const DEFAULT_VERSION_TIMEOUT_MS = 1e4;
|
|
1981
|
+
const DEFAULT_UPDATE_TIMEOUT_MS = 18e4;
|
|
1982
|
+
const clone = (value) => structuredClone(value);
|
|
1983
|
+
/**
|
|
1984
|
+
* Resolve fetch at call time. A sibling plugin may wrap `globalThis.fetch`
|
|
1985
|
+
* and later dismantle that wrapper; a binding captured during `apply()` would
|
|
1986
|
+
* keep the dead wrapper for the process lifetime.
|
|
1987
|
+
*/
|
|
1988
|
+
function ambientFetch(input, init) {
|
|
1989
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
1990
|
+
if (!headers.has("accept-encoding")) headers.set("accept-encoding", "identity");
|
|
1991
|
+
return globalThis.fetch(input, {
|
|
1992
|
+
...init,
|
|
1993
|
+
headers
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
/** One caller's cancellation must not abort the shared registry read. */
|
|
1997
|
+
function waitForSharedRequest(promise, signal) {
|
|
1998
|
+
if (signal === void 0) return promise;
|
|
1999
|
+
signal.throwIfAborted();
|
|
2000
|
+
return new Promise((resolve, reject) => {
|
|
2001
|
+
const onAbort = () => reject(signal.reason);
|
|
2002
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2003
|
+
promise.then((value) => {
|
|
2004
|
+
signal.removeEventListener("abort", onAbort);
|
|
2005
|
+
resolve(value);
|
|
2006
|
+
}, (error) => {
|
|
2007
|
+
signal.removeEventListener("abort", onAbort);
|
|
2008
|
+
reject(error);
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
/** Parse a strict `x.y.z[-pre][+build]` version; anything else is undefined. */
|
|
2013
|
+
function parseSemver(value) {
|
|
2014
|
+
if (typeof value !== "string") return void 0;
|
|
2015
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value.trim());
|
|
2016
|
+
if (match === null) return void 0;
|
|
2017
|
+
return {
|
|
2018
|
+
major: Number(match[1]),
|
|
2019
|
+
minor: Number(match[2]),
|
|
2020
|
+
patch: Number(match[3]),
|
|
2021
|
+
pre: match[4]
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
/** Compare two versions: -1/0/1, or undefined when either side is not semver. */
|
|
2025
|
+
function compareSemver(a, b) {
|
|
2026
|
+
const left = parseSemver(a);
|
|
2027
|
+
const right = parseSemver(b);
|
|
2028
|
+
if (left === void 0 || right === void 0) return void 0;
|
|
2029
|
+
for (const key of [
|
|
2030
|
+
"major",
|
|
2031
|
+
"minor",
|
|
2032
|
+
"patch"
|
|
2033
|
+
]) if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1;
|
|
2034
|
+
if (left.pre === right.pre) return 0;
|
|
2035
|
+
if (left.pre === void 0) return 1;
|
|
2036
|
+
if (right.pre === void 0) return -1;
|
|
2037
|
+
return left.pre < right.pre ? -1 : 1;
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Classify how the running package reached the profile. Only registry specs
|
|
2041
|
+
* may be updated in place; local checkouts belong to the owner's iteration.
|
|
2042
|
+
*/
|
|
2043
|
+
function classifySpec(spec) {
|
|
2044
|
+
if (typeof spec !== "string" || spec === "") return "unknown";
|
|
2045
|
+
if (/^(?:link|file):/u.test(spec)) return "link";
|
|
2046
|
+
if (/^(?:\d|[~^*])/u.test(spec)) return "npm";
|
|
2047
|
+
return "unknown";
|
|
2048
|
+
}
|
|
2049
|
+
const readManifest = async (path) => {
|
|
2050
|
+
try {
|
|
2051
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
2052
|
+
return parsed !== null && typeof parsed === "object" ? parsed : void 0;
|
|
2053
|
+
} catch {
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
/**
|
|
2058
|
+
* A zero exit only proves the command ran. pnpm can skip a too-new version
|
|
2059
|
+
* and still exit zero, so the landed manifest is what makes the answer true.
|
|
2060
|
+
*/
|
|
2061
|
+
async function confirmInstalled({ profileDir, expected, settleDelayMs, attempts = 3 }) {
|
|
2062
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
2063
|
+
if ((await readManifest(join(profileDir, "node_modules", "dsh-packycode-api", "package.json")))?.version === expected) return;
|
|
2064
|
+
if (attempt < attempts) await new Promise((resolve) => {
|
|
2065
|
+
setTimeout(resolve, settleDelayMs);
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
throw new Error("PackyCode plugin update did not install the requested version");
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Find the profile that owns this running installation. pnpm symlinks resolve
|
|
2072
|
+
* to the same realpath on both sides. When no realpath matches, fall back to
|
|
2073
|
+
* the only profile that declares the dependency at all.
|
|
2074
|
+
*/
|
|
2075
|
+
async function findInstall({ dshHome, ownPackageJsonUrl }) {
|
|
2076
|
+
let ownReal;
|
|
2077
|
+
try {
|
|
2078
|
+
ownReal = await realpath(fileURLToPath(ownPackageJsonUrl));
|
|
2079
|
+
} catch {
|
|
2080
|
+
ownReal = void 0;
|
|
2081
|
+
}
|
|
2082
|
+
let entries = [];
|
|
2083
|
+
try {
|
|
2084
|
+
entries = await readdir(join(dshHome, "profiles"), { withFileTypes: true });
|
|
2085
|
+
} catch {
|
|
2086
|
+
entries = [];
|
|
2087
|
+
}
|
|
2088
|
+
const declared = [];
|
|
2089
|
+
for (const entry of entries) {
|
|
2090
|
+
if (!entry.isDirectory()) continue;
|
|
2091
|
+
const profileDir = join(dshHome, "profiles", entry.name);
|
|
2092
|
+
const spec = (await readManifest(join(profileDir, "package.json")))?.dependencies?.[PACKAGE_NAME];
|
|
2093
|
+
if (typeof spec !== "string") continue;
|
|
2094
|
+
declared.push({
|
|
2095
|
+
profile: entry.name,
|
|
2096
|
+
spec
|
|
2097
|
+
});
|
|
2098
|
+
if (ownReal === void 0) continue;
|
|
2099
|
+
try {
|
|
2100
|
+
if (await realpath(join(profileDir, "node_modules", "dsh-packycode-api", "package.json")) === ownReal) return {
|
|
2101
|
+
kind: classifySpec(spec),
|
|
2102
|
+
profile: entry.name
|
|
2103
|
+
};
|
|
2104
|
+
} catch {}
|
|
2105
|
+
}
|
|
2106
|
+
if (declared.length === 1) return {
|
|
2107
|
+
kind: classifySpec(declared[0].spec),
|
|
2108
|
+
profile: declared[0].profile
|
|
2109
|
+
};
|
|
2110
|
+
return { kind: "unknown" };
|
|
2111
|
+
}
|
|
2112
|
+
const isExistingPath = (value) => typeof value === "string" && value !== "" && existsSync(value);
|
|
2113
|
+
/** Anchored to the file name so a directory that merely contains the word cannot match. */
|
|
2114
|
+
const PNPM_SCRIPT = /[\\/]pnpm\.(?:mjs|cjs|js)$/u;
|
|
2115
|
+
const dshRuntimeRoot = (value) => isExistingPath(value) && existsSync(join(value, "node_modules", "@deepseek-ai", "dsh", "package.json")) ? value : void 0;
|
|
2116
|
+
/**
|
|
2117
|
+
* Locate the package manager that owns this profile.
|
|
2118
|
+
*
|
|
2119
|
+
* `dsh plugin --profile desktop` is refused by the desktop app, so the update
|
|
2120
|
+
* installs with pnpm directly. The desktop host puts the bundled pnpm in its
|
|
2121
|
+
* own arguments; nothing is guessed from the entry script.
|
|
2122
|
+
*/
|
|
2123
|
+
function resolvePnpm({ argv } = {}) {
|
|
2124
|
+
const entries = Array.isArray(argv) ? argv : [];
|
|
2125
|
+
for (const entry of entries) if (isExistingPath(entry) && PNPM_SCRIPT.test(entry)) return entry;
|
|
2126
|
+
for (const entry of entries) {
|
|
2127
|
+
const root = dshRuntimeRoot(entry);
|
|
2128
|
+
if (root === void 0) continue;
|
|
2129
|
+
const script = join(root, "..", "pnpm", "bin", "pnpm.mjs");
|
|
2130
|
+
if (existsSync(script)) return script;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function resolveLauncher({ argv, execPath }) {
|
|
2134
|
+
const pnpm = resolvePnpm({ argv });
|
|
2135
|
+
if (pnpm === void 0) return {
|
|
2136
|
+
command: "pnpm",
|
|
2137
|
+
prefix: []
|
|
2138
|
+
};
|
|
2139
|
+
return {
|
|
2140
|
+
command: execPath,
|
|
2141
|
+
prefix: [pnpm]
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
/** Run pnpm host-side. Its output is never returned to the browser. */
|
|
2145
|
+
function spawnRunCommand({ command, args, cwd }, { signal, timeoutMs }) {
|
|
2146
|
+
return new Promise((resolve, reject) => {
|
|
2147
|
+
if (signal?.aborted) {
|
|
2148
|
+
reject(signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted"));
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const controller = new AbortController();
|
|
2152
|
+
const onAbort = () => controller.abort(signal.reason);
|
|
2153
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2154
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("PackyCode plugin update timed out")), timeoutMs);
|
|
2155
|
+
let settled = false;
|
|
2156
|
+
const settle = (callback, value) => {
|
|
2157
|
+
if (settled) return;
|
|
2158
|
+
settled = true;
|
|
2159
|
+
clearTimeout(timer);
|
|
2160
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2161
|
+
callback(value);
|
|
2162
|
+
};
|
|
2163
|
+
let child;
|
|
2164
|
+
try {
|
|
2165
|
+
child = spawn(command, args, {
|
|
2166
|
+
stdio: [
|
|
2167
|
+
"ignore",
|
|
2168
|
+
"pipe",
|
|
2169
|
+
"pipe"
|
|
2170
|
+
],
|
|
2171
|
+
signal: controller.signal,
|
|
2172
|
+
...cwd === void 0 ? {} : { cwd }
|
|
2173
|
+
});
|
|
2174
|
+
} catch (error) {
|
|
2175
|
+
settle(reject, error);
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
let stdout = "";
|
|
2179
|
+
let stderr = "";
|
|
2180
|
+
child.stdout?.on("data", (chunk) => {
|
|
2181
|
+
stdout = (stdout + chunk).slice(-8192);
|
|
2182
|
+
});
|
|
2183
|
+
child.stderr?.on("data", (chunk) => {
|
|
2184
|
+
stderr = (stderr + chunk).slice(-8192);
|
|
2185
|
+
});
|
|
2186
|
+
child.on("error", (error) => settle(reject, error));
|
|
2187
|
+
child.on("close", (code) => {
|
|
2188
|
+
if (controller.signal.aborted) {
|
|
2189
|
+
const reason = controller.signal.reason;
|
|
2190
|
+
settle(reject, reason instanceof Error ? reason : /* @__PURE__ */ new Error("PackyCode plugin update aborted"));
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
settle(resolve, {
|
|
2194
|
+
code: code ?? 1,
|
|
2195
|
+
stdout,
|
|
2196
|
+
stderr
|
|
2197
|
+
});
|
|
2198
|
+
});
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* The running version, the latest registry version, which profile installed
|
|
2203
|
+
* this copy, and registry-spec updates against that profile.
|
|
2204
|
+
*/
|
|
2205
|
+
function createPackyCodePluginManager({ fetchImpl = ambientFetch, runCommand = spawnRunCommand, execPath = process.execPath, argv = process.argv, env = process.env, ownPackageJsonUrl = new URL("../package.json", import.meta.url), registryUrl = NPM_REGISTRY_LATEST_URL, now = Date.now, ttlMs = DEFAULT_VERSION_TTL_MS, timeoutMs = DEFAULT_VERSION_TIMEOUT_MS, updateTimeoutMs = DEFAULT_UPDATE_TIMEOUT_MS, settleDelayMs = 250 } = {}) {
|
|
2206
|
+
if (typeof fetchImpl !== "function") throw new Error("PackyCode plugin manager requires fetch");
|
|
2207
|
+
if (typeof runCommand !== "function") throw new Error("PackyCode plugin manager requires runCommand");
|
|
2208
|
+
const dshHome = typeof env.DSH_HOME === "string" && env.DSH_HOME !== "" ? env.DSH_HOME : join(homedir(), ".dsh");
|
|
2209
|
+
const launcher = resolveLauncher({
|
|
2210
|
+
argv,
|
|
2211
|
+
execPath
|
|
2212
|
+
});
|
|
2213
|
+
let cached;
|
|
2214
|
+
let inFlight;
|
|
2215
|
+
let generation = 0;
|
|
2216
|
+
let updating = false;
|
|
2217
|
+
const fetchLatest = async (signal) => {
|
|
2218
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
2219
|
+
const requestSignal = signal === void 0 ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
|
|
2220
|
+
let response;
|
|
2221
|
+
try {
|
|
2222
|
+
response = await fetchImpl(registryUrl, {
|
|
2223
|
+
headers: { Accept: "application/json" },
|
|
2224
|
+
redirect: "error",
|
|
2225
|
+
signal: requestSignal
|
|
2226
|
+
});
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
if (signal?.aborted) throw error;
|
|
2229
|
+
throw new Error("PackyCode plugin version check failed");
|
|
2230
|
+
}
|
|
2231
|
+
if (!response.ok) throw new Error("PackyCode plugin version check failed");
|
|
2232
|
+
let version;
|
|
2233
|
+
try {
|
|
2234
|
+
version = (await response.json())?.version;
|
|
2235
|
+
} catch {
|
|
2236
|
+
version = void 0;
|
|
2237
|
+
}
|
|
2238
|
+
if (parseSemver(version) === void 0) throw new Error("PackyCode plugin version check failed");
|
|
2239
|
+
return version;
|
|
2240
|
+
};
|
|
2241
|
+
const readOwnVersion = async () => {
|
|
2242
|
+
const version = (await readManifest(fileURLToPath(ownPackageJsonUrl)))?.version;
|
|
2243
|
+
if (parseSemver(version) === void 0) throw new Error("PackyCode plugin version is unavailable");
|
|
2244
|
+
return version;
|
|
2245
|
+
};
|
|
2246
|
+
const load = async (signal) => {
|
|
2247
|
+
signal?.throwIfAborted();
|
|
2248
|
+
const [current, install, latest] = await Promise.all([
|
|
2249
|
+
readOwnVersion(),
|
|
2250
|
+
findInstall({
|
|
2251
|
+
dshHome,
|
|
2252
|
+
ownPackageJsonUrl
|
|
2253
|
+
}),
|
|
2254
|
+
fetchLatest(signal)
|
|
2255
|
+
]);
|
|
2256
|
+
return {
|
|
2257
|
+
current,
|
|
2258
|
+
latest,
|
|
2259
|
+
updateAvailable: compareSemver(latest, current) === 1,
|
|
2260
|
+
install,
|
|
2261
|
+
fetchedAt: now()
|
|
2262
|
+
};
|
|
2263
|
+
};
|
|
2264
|
+
return Object.freeze({
|
|
2265
|
+
async read({ force = false, signal } = {}) {
|
|
2266
|
+
signal?.throwIfAborted();
|
|
2267
|
+
if (!force && cached !== void 0 && now() - cached.fetchedAt < ttlMs) return clone(cached);
|
|
2268
|
+
const createPending = (requestSignal) => {
|
|
2269
|
+
const observedGeneration = generation;
|
|
2270
|
+
return load(requestSignal).then((value) => {
|
|
2271
|
+
if (generation === observedGeneration) cached = value;
|
|
2272
|
+
return value;
|
|
2273
|
+
});
|
|
2274
|
+
};
|
|
2275
|
+
if (force) return clone(await createPending(signal));
|
|
2276
|
+
const pending = inFlight ?? createPending();
|
|
2277
|
+
if (inFlight === void 0) {
|
|
2278
|
+
inFlight = pending;
|
|
2279
|
+
const clear = () => {
|
|
2280
|
+
if (inFlight === pending) inFlight = void 0;
|
|
2281
|
+
};
|
|
2282
|
+
pending.then(clear, clear);
|
|
2283
|
+
}
|
|
2284
|
+
return clone(await waitForSharedRequest(pending, signal));
|
|
2285
|
+
},
|
|
2286
|
+
async update({ signal } = {}) {
|
|
2287
|
+
signal?.throwIfAborted();
|
|
2288
|
+
if (updating) throw new Error("PackyCode plugin update is already running");
|
|
2289
|
+
updating = true;
|
|
2290
|
+
try {
|
|
2291
|
+
const install = await findInstall({
|
|
2292
|
+
dshHome,
|
|
2293
|
+
ownPackageJsonUrl
|
|
2294
|
+
});
|
|
2295
|
+
if (install.kind === "link") throw new Error("PackyCode plugin is installed from a local checkout");
|
|
2296
|
+
if (install.kind !== "npm" || install.profile === void 0) throw new Error("PackyCode plugin update could not find the owning profile");
|
|
2297
|
+
const latest = await fetchLatest(signal);
|
|
2298
|
+
const profileDir = join(dshHome, "profiles", install.profile);
|
|
2299
|
+
let result;
|
|
2300
|
+
try {
|
|
2301
|
+
result = await runCommand({
|
|
2302
|
+
command: launcher.command,
|
|
2303
|
+
args: [
|
|
2304
|
+
...launcher.prefix,
|
|
2305
|
+
"add",
|
|
2306
|
+
"--save-exact",
|
|
2307
|
+
`--config.minimumReleaseAge=0`,
|
|
2308
|
+
`${PACKAGE_NAME}@${latest}`
|
|
2309
|
+
],
|
|
2310
|
+
cwd: profileDir
|
|
2311
|
+
}, {
|
|
2312
|
+
signal,
|
|
2313
|
+
timeoutMs: updateTimeoutMs
|
|
2314
|
+
});
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
if (signal?.aborted) throw error;
|
|
2317
|
+
throw new Error("PackyCode plugin update failed");
|
|
2318
|
+
}
|
|
2319
|
+
if (result.code !== 0) throw new Error("PackyCode plugin update failed");
|
|
2320
|
+
await confirmInstalled({
|
|
2321
|
+
profileDir,
|
|
2322
|
+
expected: latest,
|
|
2323
|
+
settleDelayMs
|
|
2324
|
+
});
|
|
2325
|
+
generation += 1;
|
|
2326
|
+
cached = void 0;
|
|
2327
|
+
return {
|
|
2328
|
+
version: latest,
|
|
2329
|
+
profile: install.profile
|
|
2330
|
+
};
|
|
2331
|
+
} finally {
|
|
2332
|
+
updating = false;
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
invalidate() {
|
|
2336
|
+
generation += 1;
|
|
2337
|
+
cached = void 0;
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
//#endregion
|
|
1963
2342
|
//#region src/catalog.js
|
|
1964
2343
|
/**
|
|
1965
2344
|
* The built-in PackyCode model catalog.
|
|
@@ -2368,11 +2747,15 @@ function normalizeHost(value, fallback) {
|
|
|
2368
2747
|
* Claude Code CLI manager (optional: without it the CLI endpoints refuse).
|
|
2369
2748
|
* @returns a handler for `connection.rpc.handle(CHANNEL, handler)`.
|
|
2370
2749
|
*/
|
|
2371
|
-
function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
2750
|
+
function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli, pluginManager }) {
|
|
2372
2751
|
const requireCli = () => {
|
|
2373
2752
|
if (claudeCli === void 0) throw new Error("PackyCode Claude Code CLI support is unavailable in this runtime");
|
|
2374
2753
|
return claudeCli;
|
|
2375
2754
|
};
|
|
2755
|
+
const requireManager = () => {
|
|
2756
|
+
if (pluginManager === void 0) throw new Error("PackyCode plugin version is unavailable");
|
|
2757
|
+
return pluginManager;
|
|
2758
|
+
};
|
|
2376
2759
|
const status = async () => ({
|
|
2377
2760
|
...await ring.list(),
|
|
2378
2761
|
hosts: hosts(),
|
|
@@ -2382,7 +2765,7 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2382
2765
|
statusHost: DEFAULT_STATUS_HOST
|
|
2383
2766
|
}
|
|
2384
2767
|
});
|
|
2385
|
-
return async (endpoint, payload) => {
|
|
2768
|
+
return async (endpoint, payload, signal) => {
|
|
2386
2769
|
try {
|
|
2387
2770
|
switch (endpoint) {
|
|
2388
2771
|
case "status": return ok(await status());
|
|
@@ -2431,6 +2814,11 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2431
2814
|
cli.install();
|
|
2432
2815
|
return ok(await cli.status());
|
|
2433
2816
|
}
|
|
2817
|
+
case "plugin/version": return ok(await requireManager().read({
|
|
2818
|
+
force: payload?.force === true,
|
|
2819
|
+
signal
|
|
2820
|
+
}));
|
|
2821
|
+
case "plugin/update": return ok(await requireManager().update({ signal }));
|
|
2434
2822
|
default: return failed("Unknown PackyCode endpoint");
|
|
2435
2823
|
}
|
|
2436
2824
|
} catch (error) {
|
|
@@ -2721,7 +3109,8 @@ function apply(ctx, config) {
|
|
|
2721
3109
|
balance,
|
|
2722
3110
|
hosts,
|
|
2723
3111
|
preference,
|
|
2724
|
-
claudeCli
|
|
3112
|
+
claudeCli,
|
|
3113
|
+
pluginManager: createPackyCodePluginManager()
|
|
2725
3114
|
});
|
|
2726
3115
|
/**
|
|
2727
3116
|
* Connection registers a channel through `owner.webServer`, read on its own
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"packageManager": "pnpm@10.32.1",
|
|
5
5
|
"description": "Use a PackyCode (PackyAPI) API key in DeepSeek Harness through a built-in multi-protocol model route with key rotation and balance display",
|
|
6
6
|
"type": "module",
|