dsh-packycode-api 1.0.3 → 1.0.5
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 +13 -0
- package/README.en.md +13 -4
- package/README.md +13 -4
- package/lib/client.js +253 -24
- package/lib/index.js +1177 -221
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.5
|
|
4
|
+
|
|
5
|
+
- **Balance on mainland networks.** The default balance host is now `https://www.packyapi.ai` (PackyAPI's current site). `www.packyapi.com`, the default through 1.0.4, is DNS-poisoned on mainland networks: every public resolver checked (AliDNS, DNSPod, 1.1.1.1, 8.8.8.8, even AliDNS over HTTPS) returned an unrelated foreign IP, and a direct Node fetch failed with `ECONNREFUSED`. Without a proxy every key read `fetch failed` from it, which is what a Windows user saw. With a proxy it worked, so the failure came and went. A profile that saved the old host keeps it as a fallback, but `.ai` is tried first. Verified live without a proxy: `.ai` answers in about 1.3 s.
|
|
6
|
+
- A failed balance read keeps each key's last good balance (and the last account figures), marked `stale` with the time of that read and this read's error. Settings shows a note, and the composer shows `≈US$…`. These values stay in the host process: a page refresh keeps them and a restart clears them. A transport failure now names its cause (`fetch failed (ENOTFOUND)`, `no answer within 20s`), and a 429 says the gateway is rate limiting. The composer says "余额读取失败" instead of "无可用 Key" when a usable key's read failed. The total limit shows `—` instead of "不限额度" when no key was read.
|
|
7
|
+
- **Claude Code CLI identity.** In `-p` mode the official CLI picks its identity line from whether it got appended instructions. With them, it tells the model "You are Claude Code, Anthropic's official CLI for Claude, running within the Claude Agent SDK."; without them, "You are a Claude agent, built on Anthropic's Claude Agent SDK." A delegated turn ran without them and was refused upstream with `403 Request blocked: this endpoint only accepts requests from the official Claude Code CLI`. Every run now passes `--append-system-prompt`: the delegation instructions (moved out of the prompt) or, for the `claude_code` tool, a short delegation note. This is the binary's own selection (read from its code and confirmed by capturing its request); the plugin does not rewrite the CLI's system prompt. That wording is also recognised as a client-policy refusal, explained without the bare 403, and never disables a key.
|
|
8
|
+
- **Prompt over stdin; images in CLI turns.** The CLI now reads its input from stdin instead of argv. A streaming turn sends one `stream-json` user message, the `claude_code` tool sends plain text, and stdin is closed afterwards. A Windows command line is capped near 32K characters and cannot carry an image. Images in a CLI-backed turn used to become the text `[图片]`; they are now read from the attachment store at the HTTP route's request size (2048×2048, about 1 MiB), sent as Anthropic image blocks where they appeared, and labelled with their read-only host copy. A turn carries at most 20 images and 20 MiB of base64, dropping the oldest first. An image left out, or unreadable, is described with its path instead of failing the turn. Uploaded files are named with their host copy, and structured tool results keep their text. Verified with the real CLI 2.1.295 against a local gateway: the image reached `/v1/messages` as a base64 image block.
|
|
9
|
+
- **No more silent CLI waits.** A delegated turn now opens its reasoning block with `▸ 启动 Claude Code CLI…`, shows the CLI's version and model once it is up, and shows each retry the CLI makes on its own (`↻ 网关返回 HTTP 503(server_error),CLI 自动重试 1/10,1.0 秒后`). Before, a gateway answering `503 packy_concurrency_queue_full` (账号并发排队已满) or 429 kept the CLI retrying for minutes with nothing on screen. When the retries run out, the turn says PackyCode's Claude upstream is busy and how many retries were made, keeping the gateway's words and request id. A streaming CLI that prints nothing within 2 minutes of starting is stopped and reported with its stderr tail. Verified with the real CLI against a local gateway that answers 503, both recovering after two retries and giving up.
|
|
10
|
+
|
|
11
|
+
## 1.0.4
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
|
|
3
16
|
## 1.0.3
|
|
4
17
|
|
|
5
18
|
- 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.5`.
|
|
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.5`. 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
|
|
|
@@ -73,7 +73,7 @@ A profile without a browser can still use the model route. It just has no settin
|
|
|
73
73
|
|
|
74
74
|
**Switch keys.** Keys are used in the order they were added. **Use this key** selects one; **Rename** changes its label (at most 60 characters, empty names are refused); **Re-enable** puts a disabled key back after a top-up or replacement.
|
|
75
75
|
|
|
76
|
-
**Read the balance.** The settings total is the remaining, used, and limit of **the keys listed here**, not the bill for every token on the PackyCode account. A key that could not be read is left out of the total and counted in a note. The composer shows the active key's remaining balance only while a PackyCode model is selected. **Refresh** bypasses the roughly 60-second cache. The default model host is `https://cf.api.fan` and the balance host is `https://www.packyapi.
|
|
76
|
+
**Read the balance.** The settings total is the remaining, used, and limit of **the keys listed here**, not the bill for every token on the PackyCode account. A key that could not be read is left out of the total and counted in a note; a key whose read failed this time but succeeded before keeps showing its last balance with the time it was read (the composer shows it as `≈US$…`) until DSH restarts. The composer shows the active key's remaining balance only while a PackyCode model is selected. **Refresh** bypasses the roughly 60-second cache. The default model host is `https://cf.api.fan` and the balance host is `https://www.packyapi.ai`; both can be changed under **Advanced: endpoints**. The old default, `https://www.packyapi.com`, is DNS-poisoned on mainland networks and answers `fetch failed` without a proxy; even when settings still name it, `.ai` is tried first.
|
|
77
77
|
|
|
78
78
|
**Use Claude.** The **Claude Code CLI** card at the top of Settings checks this device. If the CLI is missing, **Install Claude Code CLI** runs `npm install -g @anthropic-ai/claude-code` (Node.js 18+). No DSH restart is required after that install. Windows also needs [Git for Windows](https://git-scm.com/downloads/win).
|
|
79
79
|
|
|
@@ -109,6 +109,9 @@ A model in the list does not mean the current token group allows it. When changi
|
|
|
109
109
|
- The CLI receives this plugin's endpoint and current key (`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`) and an isolated config directory, `~/.dsh/packycode-claude-cli`. Without that isolation, `env` in `~/.claude/settings.json` would override the plugin key.
|
|
110
110
|
- CLI permission follows the session by default: `read-only` stays read-only, `workspace-write` may edit, and `danger-full-access` with approval disabled may use `full`. The CLI cannot ask the DSH user for approval, so a session that still asks never gets `full`. `claudeCliMode` can pin `read-only`, `edit`, or `full`. An empty `claudeCliPath` is looked up automatically; an empty `claudeCliCwd` uses the session workspace.
|
|
111
111
|
- Thinking and tool activity (`▸ Bash`, `▸ Read`, and `✗` on failure) appear in that turn's reasoning block. They are progress, not DSH tool calls. The CLI's own tools are outside the DSH sandbox.
|
|
112
|
+
- The reasoning block opens with `▸ 启动 Claude Code CLI…` and then shows the CLI's version and model once it is up. When the gateway throttles or is busy (429, 503 `账号并发排队已满` / per-account wait queue full, and similar), the CLI retries up to 10 times on its own; each retry now shows as `↻ 网关返回 HTTP 503,CLI 自动重试 n/10` instead of looking hung. When the retries run out, the turn says PackyCode's Claude upstream is busy, not that the key is bad. A CLI that prints nothing for 2 minutes after starting is stopped and reported.
|
|
113
|
+
- The conversation and its images reach the CLI over stdin (`--input-format stream-json`), not argv: a Windows command line is length-limited and cannot carry an image. Images in the conversation are sent to Claude as images, at the HTTP route's request size (at most 2048×2048, about 1 MiB each; at most 20 images and 20 MiB per turn, dropping the oldest first). An image left out is named with its read-only local copy, which the CLI can open with Read.
|
|
114
|
+
- Every run carries `--append-system-prompt` (the delegation note). In `-p` mode the official CLI introduces itself as "You are Claude Code, Anthropic's official CLI for Claude …" when it has appended instructions and as "You are a Claude agent, built on Anthropic's Claude Agent SDK." when it has none; the latter was refused upstream as not coming from the official CLI (403 `only accepts requests from the official Claude Code CLI`). That is the official CLI's own behavior; the plugin does not rewrite its system prompt.
|
|
112
115
|
- While the session model is a Claude model, titles are generated with `claude-haiku-5-5`, read-only, in a temporary directory, instead of a full Opus turn.
|
|
113
116
|
- The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol` or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows.
|
|
114
117
|
- An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
|
|
@@ -117,7 +120,7 @@ A model in the list does not mean the current token group allows it. When changi
|
|
|
117
120
|
|
|
118
121
|
## Update and uninstall
|
|
119
122
|
|
|
120
|
-
**Update:**
|
|
123
|
+
**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
124
|
|
|
122
125
|
**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
126
|
|
|
@@ -127,8 +130,14 @@ A model in the list does not mean the current token group allows it. When changi
|
|
|
127
130
|
|
|
128
131
|
**The balance does not match the account bill?** Settings totals only the keys stored here, not other tokens on the same PackyCode account. A key that failed to read is noted separately and is not treated as zero.
|
|
129
132
|
|
|
133
|
+
**The balance reads `fetch failed` from `https://www.packyapi.com`?** That host, the default through 1.0.4, is DNS-poisoned on mainland networks and only answers through a proxy, so it works on and off. The default is now `https://www.packyapi.ai`. The model list is built into the plugin and never queried from PackyCode, so it is unaffected.
|
|
134
|
+
|
|
135
|
+
**A Claude model sits on "thinking" with no answer?** Read the reasoning block: `↻ 网关返回 HTTP 503/429` lines mean PackyCode's Claude upstream is queueing or throttling and the CLI is retrying. `▸ 启动 Claude Code CLI…` with no "started" line after it means the local CLI did not come up (on Windows, check Git for Windows and run `claude --version` in a terminal).
|
|
136
|
+
|
|
130
137
|
**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
138
|
|
|
139
|
+
**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.
|
|
140
|
+
|
|
132
141
|
**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
142
|
|
|
134
143
|
## 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.5`。
|
|
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.5`。不要对 `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
|
|
|
@@ -73,7 +73,7 @@ dsh-packycode-api
|
|
|
73
73
|
|
|
74
74
|
**换 Key。** Key 按添加顺序使用。点「切到此 Key」可以指定当前 Key;「重命名」改备注(最多 60 字,不能为空);充值或更换后点「重新启用」。
|
|
75
75
|
|
|
76
|
-
**看余额。** 设置页的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key
|
|
76
|
+
**看余额。** 设置页的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key 不计入合计,并会注明个数;某个 Key 这次读失败但之前读到过,会继续显示上次的余额并注明读取时间(输入框里显示为 `≈US$…`),重启 DSH 后才清空。输入框右侧只在选中 PackyCode 模型时显示当前 Key 的剩余额度。「刷新」会跳过约 60 秒的缓存。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.ai`,可在「高级:接口地址」里改。旧默认值 `https://www.packyapi.com` 在国内网络下 DNS 被污染、直连会 `fetch failed`,即使设置里保存的是它,插件也会先试 `.ai`。
|
|
77
77
|
|
|
78
78
|
**用 Claude。** 设置页顶部的「Claude Code CLI」卡片会检测本机 CLI。没装时点「安装 Claude Code CLI」,本机执行 `npm install -g @anthropic-ai/claude-code`(需要 Node.js 18+)。装好后不用重启 DSH。Windows 上另外需要 [Git for Windows](https://git-scm.com/downloads/win)。
|
|
79
79
|
|
|
@@ -109,6 +109,9 @@ dsh-packycode-api
|
|
|
109
109
|
- CLI 使用插件配置的地址和当前 Key(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),以及独立配置目录 `~/.dsh/packycode-claude-cli`。不隔离的话,`~/.claude/settings.json` 里的 `env` 会盖掉插件 Key。
|
|
110
110
|
- CLI 权限默认跟随当前会话:`read-only` → 只读,`workspace-write` → 可编辑,`danger-full-access` 且不再审批 → `full`。CLI 不能向 DSH 弹审批,所以只要会话还要审批,就不会给它 `full`。也可把设置项 `claudeCliMode` 固定成 `read-only` / `edit` / `full`。`claudeCliPath` 留空则自动查找;`claudeCliCwd` 留空则用当前会话工作区。
|
|
111
111
|
- 思考过程和工具调用(`▸ Bash`、`▸ Read`,失败时带 `✗`)会出现在这一回合的思考区。它们是进度,不是 DSH 的工具调用。CLI 自己的工具不受 DSH 沙箱约束。
|
|
112
|
+
- 思考区第一行是「▸ 启动 Claude Code CLI…」,CLI 起来后显示版本和模型。网关限流或繁忙(429、503 `账号并发排队已满` 等)时,CLI 会自己重试最多 10 次,每次重试都会显示成 `↻ 网关返回 HTTP 503,CLI 自动重试 n/10`,不再像卡住。重试用完仍失败时,会说明是 PackyCode 的 Claude 上游繁忙,不是 Key 的问题。CLI 启动后 2 分钟内没有任何输出会被停止并报告原因。
|
|
113
|
+
- 对话记录和图片通过 stdin 交给 CLI(`--input-format stream-json`),不走命令行参数:Windows 命令行长度有上限,而且命令行带不了图片。对话里的图片会以图片形式发给 Claude(与 HTTP 路由相同的压缩规格:每张不超过 2048×2048、约 1 MiB;一回合最多 20 张、合计 20 MiB,超出时先舍弃最早的)。没随消息发出的图片会写明本机只读副本的路径,CLI 需要时可以用 Read 读取。
|
|
114
|
+
- 每次运行都带 `--append-system-prompt`(委派说明)。在 `-p` 模式下,官方 CLI 有追加说明时会以 "You are Claude Code, Anthropic's official CLI for Claude …" 自我介绍,没有时是 "You are a Claude agent, built on Anthropic's Claude Agent SDK.";后者曾被上游当成非官方 CLI 拒绝(403 `only accepts requests from the official Claude Code CLI`)。这是官方 CLI 自己的行为,插件没有改写它的系统提示词。
|
|
112
115
|
- 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
|
|
113
116
|
- 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限。
|
|
114
117
|
- 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
|
|
@@ -117,7 +120,7 @@ dsh-packycode-api
|
|
|
117
120
|
|
|
118
121
|
## 更新与卸载
|
|
119
122
|
|
|
120
|
-
**更新:**
|
|
123
|
+
**更新:** 打开 **设置 → PackyCode**,有新版本时点「更新插件」。它会在当前 profile 目录里用 DSH 自带的 pnpm 安装精确版本,并读回 `package.json` 确认版本落地。本地 `link:` 开发安装不会出现这个按钮。完成后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。也可以在**插件**页重新安装 `dsh-packycode-api`。
|
|
121
124
|
|
|
122
125
|
**卸载:** 在**插件**页卸载本插件,然后重启桌面应用。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
|
|
123
126
|
|
|
@@ -127,8 +130,14 @@ dsh-packycode-api
|
|
|
127
130
|
|
|
128
131
|
**余额对不上账号账单?** 设置页合计的是这里列出的 Key,不含同账号下没添加到本插件的其它令牌。个别 Key 读失败时会单独注明,不会被算成 0。
|
|
129
132
|
|
|
133
|
+
**余额显示 `读取失败: https://www.packyapi.com: fetch failed`?** 这是 1.0.4 及更早版本的默认余额接口在国内网络下被 DNS 污染,开了代理才能连上,所以有时正常、有时失败。新版本默认改用 `https://www.packyapi.ai`。模型列表是插件内置的,不需要向 PackyCode 查询,不受影响。
|
|
134
|
+
|
|
135
|
+
**选了 Claude 模型一直显示「深度求索中」?** 看思考区:有 `↻ 网关返回 HTTP 503/429` 说明 PackyCode 的 Claude 上游在排队或限流,CLI 正在自动重试;只有「▸ 启动 Claude Code CLI…」没有「已启动」,说明本机 CLI 没起来(Windows 上先确认装了 Git for Windows,并在终端执行 `claude --version`)。
|
|
136
|
+
|
|
130
137
|
**限速之后 Key 被停用了?** 不该发生。429 不会切换 Key。额度用尽或 401 才会停用;充值后点「重新启用」。
|
|
131
138
|
|
|
139
|
+
**插件页安装报 `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`?** pnpm 默认拒绝发布不满 24 小时的包,并且会检查 profile 里的整个 lockfile。报错里点名的往往是另一个刚更新过的插件,不是本插件。等它发布满 24 小时再重试,或把报错里的 `包名@版本` 加到 profile 目录 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 下。已安装本插件后,设置页的「更新插件」不受这个限制。
|
|
140
|
+
|
|
132
141
|
**装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
|
|
133
142
|
|
|
134
143
|
## 安全边界
|
package/lib/client.js
CHANGED
|
@@ -102,6 +102,9 @@ window.__ModuleLoader__.load({
|
|
|
102
102
|
totalUsed: "合计已用",
|
|
103
103
|
totalLimit: "合计额度",
|
|
104
104
|
totalPartial: "有 {count} 个 Key 未读到余额,未计入合计。",
|
|
105
|
+
totalStale: "其中 {count} 个 Key 本次读取失败,显示的是上次读到的余额。",
|
|
106
|
+
staleNote: "上次读取于 {time},本次读取失败:{error}",
|
|
107
|
+
composerStale: "上次读取于 {time},本次读取失败",
|
|
105
108
|
accountLimit: "额度",
|
|
106
109
|
refresh: "刷新",
|
|
107
110
|
refreshing: "刷新中…",
|
|
@@ -139,6 +142,7 @@ window.__ModuleLoader__.load({
|
|
|
139
142
|
notWritable: "当前 profile 不允许写入设置,改动只在本次运行内生效。",
|
|
140
143
|
composerLabel: "PackyCode 剩余额度",
|
|
141
144
|
composerNoKey: "无可用 Key",
|
|
145
|
+
composerUnread: "余额读取失败",
|
|
142
146
|
error: "读取 PackyCode 状态失败",
|
|
143
147
|
loadFailed: "读取失败",
|
|
144
148
|
cliTitle: "Claude Code CLI",
|
|
@@ -163,7 +167,26 @@ window.__ModuleLoader__.load({
|
|
|
163
167
|
cliNetwork: "下载失败,请检查网络或 npm 镜像设置后重试。",
|
|
164
168
|
cliTimeout: "安装超时,请检查网络后重试。",
|
|
165
169
|
cliNotFoundAfterInstall: "npm 显示安装完成,但仍找不到 claude 命令。请在终端执行 claude --version 确认,然后点「重新检测」。",
|
|
166
|
-
cliWindowsNote: "Windows 上 Claude Code 还需要 Git for Windows(https://git-scm.com/downloads/win)。"
|
|
170
|
+
cliWindowsNote: "Windows 上 Claude Code 还需要 Git for Windows(https://git-scm.com/downloads/win)。",
|
|
171
|
+
versionTitle: "插件版本",
|
|
172
|
+
versionCurrent: "当前版本",
|
|
173
|
+
versionLatest: "最新版本",
|
|
174
|
+
versionCheck: "检查更新",
|
|
175
|
+
versionChecking: "正在检查更新…",
|
|
176
|
+
versionFailed: "无法检查最新版本。",
|
|
177
|
+
versionUpToDate: "已是最新版本。",
|
|
178
|
+
versionAvailable: "发现新版本 v{version}。",
|
|
179
|
+
versionLinked: "当前是本地开发安装,请在插件仓库拉取代码并重新构建。",
|
|
180
|
+
versionManual: "无法在这里更新。请在桌面应用的插件页重新安装 dsh-packycode-api。",
|
|
181
|
+
versionUpdate: "更新插件",
|
|
182
|
+
versionUpdating: "正在更新…",
|
|
183
|
+
versionUpdateFailed: "更新失败,请重试,或在插件页重新安装。",
|
|
184
|
+
versionUpdated: "已更新到 v{version}。",
|
|
185
|
+
versionUpdatedHint: "新版本需要完全退出并重新打开桌面应用才会生效;只刷新页面不会重载 Host 里的插件。",
|
|
186
|
+
versionRestart: "如何重启",
|
|
187
|
+
versionRestartHint: "插件无法安全地重启桌面应用。请完全退出 DSH,再重新打开。",
|
|
188
|
+
versionRefresh: "刷新界面",
|
|
189
|
+
versionLater: "稍后"
|
|
167
190
|
};
|
|
168
191
|
const en = {
|
|
169
192
|
nav: "PackyCode",
|
|
@@ -210,6 +233,10 @@ window.__ModuleLoader__.load({
|
|
|
210
233
|
notWritable: "This profile does not allow settings writes; changes apply to this run only.",
|
|
211
234
|
composerLabel: "PackyCode remaining balance",
|
|
212
235
|
composerNoKey: "No usable key",
|
|
236
|
+
composerUnread: "Balance unavailable",
|
|
237
|
+
totalStale: "{count} of these keys failed this read and show their last read balance.",
|
|
238
|
+
staleNote: "Last read at {time}; this read failed: {error}",
|
|
239
|
+
composerStale: "last read at {time}; this read failed",
|
|
213
240
|
error: "Could not read PackyCode status",
|
|
214
241
|
loadFailed: "Read failed",
|
|
215
242
|
cliTitle: "Claude Code CLI",
|
|
@@ -234,7 +261,26 @@ window.__ModuleLoader__.load({
|
|
|
234
261
|
cliNetwork: "The download failed. Check your network or npm registry settings and retry.",
|
|
235
262
|
cliTimeout: "The install timed out. Check your network and retry.",
|
|
236
263
|
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)."
|
|
264
|
+
cliWindowsNote: "On Windows, Claude Code also needs Git for Windows (https://git-scm.com/downloads/win).",
|
|
265
|
+
versionTitle: "Plugin version",
|
|
266
|
+
versionCurrent: "Current version",
|
|
267
|
+
versionLatest: "Latest version",
|
|
268
|
+
versionCheck: "Check for updates",
|
|
269
|
+
versionChecking: "Checking for updates…",
|
|
270
|
+
versionFailed: "Could not check the latest version.",
|
|
271
|
+
versionUpToDate: "You are on the latest version.",
|
|
272
|
+
versionAvailable: "A new version v{version} is available.",
|
|
273
|
+
versionLinked: "Installed from a local checkout. Pull and rebuild the plugin repository instead.",
|
|
274
|
+
versionManual: "This install cannot be updated here. Reinstall dsh-packycode-api from the desktop Plugins page.",
|
|
275
|
+
versionUpdate: "Update plugin",
|
|
276
|
+
versionUpdating: "Updating…",
|
|
277
|
+
versionUpdateFailed: "The update failed. Try again, or reinstall from the Plugins page.",
|
|
278
|
+
versionUpdated: "Updated to v{version}.",
|
|
279
|
+
versionUpdatedHint: "Quit and reopen the desktop application. Refreshing the page does not reload the plugin inside the Host.",
|
|
280
|
+
versionRestart: "How to restart",
|
|
281
|
+
versionRestartHint: "The plugin cannot safely restart the desktop application. Quit DSH completely, then open it again.",
|
|
282
|
+
versionRefresh: "Refresh page",
|
|
283
|
+
versionLater: "Later"
|
|
238
284
|
};
|
|
239
285
|
const STYLE = `
|
|
240
286
|
.packyCodeSection{display:flex;flex-direction:column;gap:16px;font-size:13px;line-height:20px}
|
|
@@ -269,6 +315,9 @@ window.__ModuleLoader__.load({
|
|
|
269
315
|
.packyCodeWarning{color:var(--dsw-alias-state-warning-primary,#c27c0e);font-weight:500}
|
|
270
316
|
.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
317
|
.packyCodeMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;word-break:break-all}
|
|
318
|
+
.packyCodeVersionRows{display:flex;flex-direction:column;gap:6px}
|
|
319
|
+
.packyCodeVersionRow{display:flex;justify-content:space-between;gap:12px}
|
|
320
|
+
.packyCodeVersionValue{font-variant-numeric:tabular-nums;font-weight:600}
|
|
272
321
|
.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
322
|
@media(max-width:640px){.packyCodeRow{align-items:flex-start;flex-direction:column}.packyCodeActions{width:100%}}
|
|
274
323
|
`;
|
|
@@ -300,6 +349,18 @@ window.__ModuleLoader__.load({
|
|
|
300
349
|
if (balance === void 0) return void 0;
|
|
301
350
|
return balance.keys?.find((entry) => entry.id === balance.activeId) ?? balance.keys?.[0];
|
|
302
351
|
}
|
|
352
|
+
/** The clock time of an epoch-millisecond reading, for a "last read at" note. */
|
|
353
|
+
function readTime(ms) {
|
|
354
|
+
if (typeof ms !== "number" || !Number.isFinite(ms)) return "—";
|
|
355
|
+
try {
|
|
356
|
+
return new Date(ms).toLocaleTimeString(void 0, {
|
|
357
|
+
hour: "2-digit",
|
|
358
|
+
minute: "2-digit"
|
|
359
|
+
});
|
|
360
|
+
} catch {
|
|
361
|
+
return new Date(ms).toISOString().slice(11, 16);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
303
364
|
function clearMessage(error) {
|
|
304
365
|
const text = typeof error?.message === "string" && error.message.length > 0 ? error.message : String(error);
|
|
305
366
|
return text.length > 200 ? text.slice(0, 200) : text;
|
|
@@ -451,6 +512,156 @@ window.__ModuleLoader__.load({
|
|
|
451
512
|
]
|
|
452
513
|
});
|
|
453
514
|
}
|
|
515
|
+
function VersionCard({ call, t }) {
|
|
516
|
+
const [info, setInfo] = react.useState();
|
|
517
|
+
const [loading, setLoading] = react.useState(true);
|
|
518
|
+
const [error, setError] = react.useState(false);
|
|
519
|
+
const [updating, setUpdating] = react.useState(false);
|
|
520
|
+
const [updateFailed, setUpdateFailed] = react.useState(false);
|
|
521
|
+
const [updated, setUpdated] = react.useState();
|
|
522
|
+
const [restartHint, setRestartHint] = react.useState(false);
|
|
523
|
+
const generation = react.useRef(0);
|
|
524
|
+
const load = (force) => {
|
|
525
|
+
const current = ++generation.current;
|
|
526
|
+
setLoading(true);
|
|
527
|
+
setError(false);
|
|
528
|
+
call("plugin/version", { force }).then((value) => {
|
|
529
|
+
if (generation.current === current) setInfo(value);
|
|
530
|
+
}).catch(() => {
|
|
531
|
+
if (generation.current === current) setError(true);
|
|
532
|
+
}).finally(() => {
|
|
533
|
+
if (generation.current === current) setLoading(false);
|
|
534
|
+
});
|
|
535
|
+
};
|
|
536
|
+
react.useEffect(() => {
|
|
537
|
+
load(false);
|
|
538
|
+
return () => {
|
|
539
|
+
generation.current += 1;
|
|
540
|
+
};
|
|
541
|
+
}, []);
|
|
542
|
+
const update = () => {
|
|
543
|
+
setUpdating(true);
|
|
544
|
+
setUpdateFailed(false);
|
|
545
|
+
call("plugin/update").then((value) => {
|
|
546
|
+
setUpdated(value);
|
|
547
|
+
setRestartHint(false);
|
|
548
|
+
}).catch(() => setUpdateFailed(true)).finally(() => setUpdating(false));
|
|
549
|
+
};
|
|
550
|
+
const installKind = info?.install?.kind;
|
|
551
|
+
const showStatus = info !== void 0 && updated === void 0;
|
|
552
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
553
|
+
className: "packyCodeCard",
|
|
554
|
+
children: [
|
|
555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
556
|
+
className: "packyCodeCardHeader",
|
|
557
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
558
|
+
className: "packyCodeCardTitle",
|
|
559
|
+
children: t("versionTitle")
|
|
560
|
+
}), updated === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
561
|
+
type: "button",
|
|
562
|
+
className: "packyCodeButton",
|
|
563
|
+
disabled: loading || updating,
|
|
564
|
+
onClick: () => load(true),
|
|
565
|
+
children: loading ? t("versionChecking") : t("versionCheck")
|
|
566
|
+
})]
|
|
567
|
+
}),
|
|
568
|
+
loading && info === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
569
|
+
className: "packyCodeNote",
|
|
570
|
+
role: "status",
|
|
571
|
+
children: t("versionChecking")
|
|
572
|
+
}),
|
|
573
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
574
|
+
className: "packyCodeError",
|
|
575
|
+
role: "alert",
|
|
576
|
+
children: t("versionFailed")
|
|
577
|
+
}),
|
|
578
|
+
info !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
579
|
+
className: "packyCodeVersionRows",
|
|
580
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
581
|
+
className: "packyCodeVersionRow",
|
|
582
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionCurrent") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
583
|
+
className: "packyCodeVersionValue",
|
|
584
|
+
children: ["v", info.current]
|
|
585
|
+
})]
|
|
586
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
587
|
+
className: "packyCodeVersionRow",
|
|
588
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("versionLatest") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
589
|
+
className: "packyCodeVersionValue",
|
|
590
|
+
children: ["v", info.latest]
|
|
591
|
+
})]
|
|
592
|
+
})]
|
|
593
|
+
}),
|
|
594
|
+
showStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
595
|
+
className: "packyCodeNote",
|
|
596
|
+
role: "status",
|
|
597
|
+
children: info.updateAvailable ? t("versionAvailable").replace("{version}", info.latest) : t("versionUpToDate")
|
|
598
|
+
}),
|
|
599
|
+
showStatus && info.updateAvailable && installKind === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
600
|
+
className: "packyCodeActions",
|
|
601
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
602
|
+
type: "button",
|
|
603
|
+
className: "packyCodeButton packyCodeButtonPrimary",
|
|
604
|
+
disabled: updating,
|
|
605
|
+
onClick: update,
|
|
606
|
+
children: updating ? t("versionUpdating") : t("versionUpdate")
|
|
607
|
+
})
|
|
608
|
+
}),
|
|
609
|
+
showStatus && installKind === "link" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
610
|
+
className: "packyCodeNote",
|
|
611
|
+
children: t("versionLinked")
|
|
612
|
+
}),
|
|
613
|
+
showStatus && info.updateAvailable && installKind === "unknown" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
614
|
+
className: "packyCodeNote",
|
|
615
|
+
children: t("versionManual")
|
|
616
|
+
}),
|
|
617
|
+
updateFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
618
|
+
className: "packyCodeError",
|
|
619
|
+
role: "alert",
|
|
620
|
+
children: t("versionUpdateFailed")
|
|
621
|
+
}),
|
|
622
|
+
updated !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
623
|
+
role: "status",
|
|
624
|
+
children: [
|
|
625
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
626
|
+
className: "packyCodeNote",
|
|
627
|
+
children: t("versionUpdated").replace("{version}", updated.version)
|
|
628
|
+
}),
|
|
629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
630
|
+
className: "packyCodeNote",
|
|
631
|
+
children: t("versionUpdatedHint")
|
|
632
|
+
}),
|
|
633
|
+
restartHint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
634
|
+
className: "packyCodeNote",
|
|
635
|
+
children: t("versionRestartHint")
|
|
636
|
+
}),
|
|
637
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
638
|
+
className: "packyCodeActions",
|
|
639
|
+
children: [
|
|
640
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
641
|
+
type: "button",
|
|
642
|
+
className: "packyCodeButton",
|
|
643
|
+
onClick: () => setRestartHint(true),
|
|
644
|
+
children: t("versionRestart")
|
|
645
|
+
}),
|
|
646
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
647
|
+
type: "button",
|
|
648
|
+
className: "packyCodeButton packyCodeButtonPrimary",
|
|
649
|
+
onClick: () => window.location.reload(),
|
|
650
|
+
children: t("versionRefresh")
|
|
651
|
+
}),
|
|
652
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
653
|
+
type: "button",
|
|
654
|
+
className: "packyCodeButton",
|
|
655
|
+
onClick: () => setUpdated(void 0),
|
|
656
|
+
children: t("versionLater")
|
|
657
|
+
})
|
|
658
|
+
]
|
|
659
|
+
})
|
|
660
|
+
]
|
|
661
|
+
})
|
|
662
|
+
]
|
|
663
|
+
});
|
|
664
|
+
}
|
|
454
665
|
function PackyCodeSection({ rpc, t }) {
|
|
455
666
|
const call = react.useMemo(() => (endpoint, payload = {}) => rpc.call(CHANNEL, endpoint, payload).then(unwrap), [rpc]);
|
|
456
667
|
const [status, setStatus] = react.useState();
|
|
@@ -528,23 +739,30 @@ window.__ModuleLoader__.load({
|
|
|
528
739
|
className: "packyCodeRow",
|
|
529
740
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
530
741
|
className: "packyCodeRowMain",
|
|
531
|
-
children: [
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
742
|
+
children: [
|
|
743
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
744
|
+
className: "packyCodeRowName",
|
|
745
|
+
children: [
|
|
746
|
+
row.label,
|
|
747
|
+
row.id === status?.activeId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
748
|
+
className: "packyCodeChip packyCodeChipActive",
|
|
749
|
+
children: t("keyActive")
|
|
750
|
+
}),
|
|
751
|
+
row.disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
752
|
+
className: "packyCodeChip",
|
|
753
|
+
children: t("keyDisabled")
|
|
754
|
+
})
|
|
755
|
+
]
|
|
756
|
+
}),
|
|
757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
758
|
+
className: "packyCodeRowMeta",
|
|
759
|
+
children: figures.length > 0 ? [tokenName, ...figures].filter(Boolean).join(" · ") : row.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
|
|
760
|
+
}),
|
|
761
|
+
row.stale !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
762
|
+
className: "packyCodeRowMeta",
|
|
763
|
+
children: t("staleNote").replace("{time}", readTime(row.stale.fetchedAt)).replace("{error}", row.stale.error ?? "")
|
|
764
|
+
})
|
|
765
|
+
]
|
|
548
766
|
})
|
|
549
767
|
}, row.id);
|
|
550
768
|
};
|
|
@@ -555,6 +773,10 @@ window.__ModuleLoader__.load({
|
|
|
555
773
|
className: "packyCodeIntro",
|
|
556
774
|
children: t("intro")
|
|
557
775
|
}),
|
|
776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
|
|
777
|
+
call,
|
|
778
|
+
t
|
|
779
|
+
}),
|
|
558
780
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClaudeCliCard, {
|
|
559
781
|
call,
|
|
560
782
|
t
|
|
@@ -611,7 +833,7 @@ window.__ModuleLoader__.load({
|
|
|
611
833
|
children: t("totalLimit")
|
|
612
834
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
613
835
|
className: "packyCodeFigureValue",
|
|
614
|
-
children: totals.counted === 0
|
|
836
|
+
children: totals.counted === 0 ? "—" : totals.unlimited > 0 ? t("keyUnlimited") : formatUsd(totals.limitUsd) ?? "—"
|
|
615
837
|
})]
|
|
616
838
|
})
|
|
617
839
|
]
|
|
@@ -619,6 +841,10 @@ window.__ModuleLoader__.load({
|
|
|
619
841
|
totals.unread > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
620
842
|
className: "packyCodeNote",
|
|
621
843
|
children: t("totalPartial").replace("{count}", String(totals.unread))
|
|
844
|
+
}),
|
|
845
|
+
rows.some((row) => row.stale !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
846
|
+
className: "packyCodeNote",
|
|
847
|
+
children: t("totalStale").replace("{count}", String(rows.filter((row) => row.stale !== void 0).length))
|
|
622
848
|
})
|
|
623
849
|
] })]
|
|
624
850
|
}),
|
|
@@ -847,15 +1073,18 @@ window.__ModuleLoader__.load({
|
|
|
847
1073
|
};
|
|
848
1074
|
}, [call, enabled]);
|
|
849
1075
|
if (!enabled || balance === void 0) return null;
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
const
|
|
1076
|
+
const row = activeUsage(balance);
|
|
1077
|
+
const usage = row?.usage;
|
|
1078
|
+
const display = usage?.unlimitedQuota === true ? t("keyUnlimited") : formatUsd(usage?.availableUsd) ?? (row?.error === void 0 ? t("composerNoKey") : t("composerUnread"));
|
|
1079
|
+
const stale = row?.stale !== void 0 && usage !== void 0;
|
|
1080
|
+
const shown = stale ? `≈${display}` : display;
|
|
1081
|
+
const label = stale ? `${t("composerLabel")}: ${display}(${t("composerStale").replace("{time}", readTime(row.stale.fetchedAt))})` : `${t("composerLabel")}: ${display}`;
|
|
853
1082
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
854
1083
|
className: "packyCodeComposer",
|
|
855
1084
|
role: "status",
|
|
856
1085
|
"aria-label": label,
|
|
857
1086
|
title: label,
|
|
858
|
-
children:
|
|
1087
|
+
children: shown
|
|
859
1088
|
});
|
|
860
1089
|
}
|
|
861
1090
|
const inject = [
|