dsh-packycode-api 1.0.1 → 1.0.3
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/AGENTS.md +49 -1
- package/CHANGELOG.md +10 -0
- package/README.en.md +159 -0
- package/README.md +129 -71
- package/docs/assets/dsh-packycode-mascot.png +0 -0
- package/icon-subscription.webp +0 -0
- package/lib/client.js +63 -5
- package/lib/index.js +18 -0
- package/package.json +5 -1
package/AGENTS.md
CHANGED
|
@@ -46,4 +46,52 @@ Live gateway behavior is verified with `node scripts/live-check.mjs` after a bui
|
|
|
46
46
|
|
|
47
47
|
## Release authorization
|
|
48
48
|
|
|
49
|
-
`npm publish`, Git tags, GitHub Releases, and version publication are externally visible and partially irreversible. Execute them only when the user explicitly asks and confirms the version.
|
|
49
|
+
`npm publish`, Git tags, GitHub Releases, and version publication are externally visible and partially irreversible. Execute them only when the user explicitly asks to publish or release and confirms the target version. A request to modify, test, or commit alone is not release authorization.
|
|
50
|
+
|
|
51
|
+
Never reuse, move, delete, or overwrite a published npm version or Git tag. If a published version needs a correction, create a new patch version.
|
|
52
|
+
|
|
53
|
+
## Release checklist
|
|
54
|
+
|
|
55
|
+
1. Synchronize and confirm a clean base:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git fetch origin --tags --prune
|
|
59
|
+
git status --short --branch
|
|
60
|
+
git log --oneline HEAD..origin/main
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Fast-forward clean remote changes before editing. Never discard remote changes.
|
|
64
|
+
|
|
65
|
+
2. Confirm the requested SemVer is unused:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
npm view dsh-packycode-api@<version> version --json
|
|
69
|
+
git ls-remote --exit-code --tags origin refs/tags/v<version>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For an unused npm version, `npm view` normally returns `E404`. An existing npm version or Git tag requires choosing a new version.
|
|
73
|
+
|
|
74
|
+
3. Update all release metadata together: `package.json` version, `CHANGELOG.md`, and any version pinned in `README.md` / `README.en.md`.
|
|
75
|
+
|
|
76
|
+
4. Validate and inspect the exact release artifact:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
pnpm install --frozen-lockfile
|
|
80
|
+
pnpm run check
|
|
81
|
+
npm publish .artifacts/dsh-packycode-api-<version>.tgz --access public --dry-run --json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
5. Commit the release and push `main` without force. [`.github/workflows/publish.yml`](.github/workflows/publish.yml) must already be on the commit that will be tagged. Do not push the tag in this step.
|
|
85
|
+
|
|
86
|
+
6. Confirm the package has a GitHub Actions trusted publisher before pushing the tag: repository `BaronCyrus/dsh-packycode-api`, workflow filename `publish.yml`, environment empty. Never request that a token, password, or one-time code be pasted into chat.
|
|
87
|
+
|
|
88
|
+
7. Create the annotated tag and push it. That push publishes with GitHub OIDC. Do not run `npm publish` locally for a release. A local publish cannot attach provenance. Do not set `NODE_AUTH_TOKEN` or a setup-node `registry-url` in the publish workflow; npm treats either as token auth and skips OIDC.
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
git tag -a v<version> -m "dsh-packycode-api v<version>"
|
|
92
|
+
git push origin v<version>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`workflow_dispatch` on the same workflow only prints the OIDC identity npm will match. It does not publish.
|
|
96
|
+
|
|
97
|
+
8. Confirm the publish workflow succeeded for that tag before declaring the release public. On an OIDC `package not found` error, fix the trusted publisher and re-run the same workflow. Do not move the tag. If the tagged tree itself must change, publish a new patch version.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
7
|
+
## 1.0.2
|
|
8
|
+
|
|
9
|
+
- The plugin card and both READMEs show the PackyCode mascot. The package declares a top-level `icon` and ships a 512×512 transparent lossless WebP, under DSH's 256 KiB icon limit. The original README PNG is unchanged.
|
|
10
|
+
- Keys can be renamed from the settings panel (`keys/rename`, up to 60 characters, empty names refused). Before, a label was fixed at add time, so keys stored under a duplicate default name could only be fixed by removing and re-adding them.
|
|
11
|
+
- The account-balance rows now show the key's own name instead of the PackyCode token name, so a rename shows up in both lists; the token name moves to the row's detail line when it differs.
|
|
12
|
+
|
|
3
13
|
## 1.0.1
|
|
4
14
|
|
|
5
15
|
- The balance card no longer shows PackyCode's account-level billing line (`PackyCode 账号(含同账号其它用量):剩余 … · 已用 … · 额度 …`). It counts every token on the account, including usage from keys not stored here, so it could read as a large negative remaining beside the per-key totals. The headline figures — the sum of the listed keys — are unchanged.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/assets/dsh-packycode-mascot.png" width="220" height="220" alt="DSH PackyCode API mascot">
|
|
4
|
+
|
|
5
|
+
# DSH PackyCode API
|
|
6
|
+
|
|
7
|
+
[简体中文](README.md) · **English**
|
|
8
|
+
|
|
9
|
+
**Pick models, rotate keys, and read balances in DSH with a PackyCode API key.**
|
|
10
|
+
|
|
11
|
+
The `PackyCode` model group ships with the plugin. No YAML edits and no separate provider setup.
|
|
12
|
+
|
|
13
|
+
[](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml)
|
|
14
|
+
[](https://www.npmjs.com/package/dsh-packycode-api)
|
|
15
|
+
[](https://www.npmjs.com/package/dsh-packycode-api)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://github.com/BaronCyrus/dsh-packycode-api/stargazers)
|
|
18
|
+
|
|
19
|
+
[Features](#features) · [Install](#install) · [Daily use](#daily-use) · [Models and groups](#models-and-groups) · [Update and uninstall](#update-and-uninstall)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
| Capability | What it does |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| **Built-in model route** | Adds a `PackyCode` group to the model picker. No provider configuration and no `cordis.patch.yml` edits |
|
|
28
|
+
| **Keys used in order** | An exhausted or rejected key is disabled and the next usable key takes over; the failed step is retried on that key |
|
|
29
|
+
| **Visible balance** | Settings lists each key's remaining, used, limit, and reset period, plus a total of those keys. The composer shows the active key's remaining balance |
|
|
30
|
+
| **Claude through the local CLI** | `claude-opus-5-5`, `claude-sonnet-5-5`, and `claude-haiku-5-5` run the official Claude Code CLI on this device. Settings can detect it and install it |
|
|
31
|
+
| **Credentials stay on the machine** | Keys live in the DSH credential file. The browser receives labels, state, and balances only |
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
You need **DeepSeek Harness** and a **PackyCode (PackyAPI) API key**. For DSH setup, see the [official instructions](https://github.com/deepseek-ai/deepseek-harness#run).
|
|
36
|
+
|
|
37
|
+
### 1. Install the plugin
|
|
38
|
+
|
|
39
|
+
Open the desktop application's **Plugins** page, click **Add plugin**, and enter this as the **package name or address**:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
dsh-packycode-api
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.0.3`.
|
|
46
|
+
|
|
47
|
+
Then **manually restart the desktop application**. Refreshing the page does not reload the plugin inside the Host process.
|
|
48
|
+
|
|
49
|
+
### 2. Add a key
|
|
50
|
+
|
|
51
|
+
Open **Settings → PackyCode**. Paste the API key under **Add a key**. A label is optional and only distinguishes keys. Click **Add**.
|
|
52
|
+
|
|
53
|
+
> Do not paste a key into a public issue, chat, or screenshot. The plugin does not read `PACKY_CODE_API_KEY` from the environment, and it does not silently switch to another provider when a request fails.
|
|
54
|
+
|
|
55
|
+
### 3. Select a model
|
|
56
|
+
|
|
57
|
+
Choose a model from the `PackyCode` group. `deepseek-flash` and `gpt-6.1-sol` work once a key is stored. The three Claude models also need the Claude Code CLI on this device; see below.
|
|
58
|
+
|
|
59
|
+
<details>
|
|
60
|
+
<summary>Verify the install / other profiles / a local checkout</summary>
|
|
61
|
+
|
|
62
|
+
The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
|
|
63
|
+
|
|
64
|
+
**For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.3`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
|
|
65
|
+
|
|
66
|
+
A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
|
|
67
|
+
|
|
68
|
+
A profile without a browser can still use the model route. It just has no settings panel and no composer balance.
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
## Daily use
|
|
73
|
+
|
|
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
|
+
|
|
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.com`; both can be changed under **Advanced: endpoints**.
|
|
77
|
+
|
|
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
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary>When a key is rotated</summary>
|
|
82
|
+
|
|
83
|
+
- **Rotates and disables the current key:** the gateway reports exhausted quota, or it rejects the key (HTTP 401 / invalid token). The harness retries the failed step, which is what actually sends it on the next key.
|
|
84
|
+
- **Does not rotate:** rate limits (429); a 400 that this gateway uses for a dead upstream channel; and a 403 that means the group accepts only the vendor's official client. That last case is explained directly instead of being shown as an invalid API key.
|
|
85
|
+
- When every stored key is disabled, the request fails and says so. There is no fallback credential.
|
|
86
|
+
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
## Models and groups
|
|
90
|
+
|
|
91
|
+
| Model | How it runs | Effort | Token group |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| `deepseek-flash` | HTTP, chat/completions | Can be off; `low` / `high` / `max` | `deepseek-officially`, `deepseek-sale` |
|
|
94
|
+
| `gpt-6.1-sol` | HTTP, Responses, images allowed | Cannot be off; `low` / `medium` / `high` / `xhigh` / `max` | `codex`, `codex-sale` |
|
|
95
|
+
| `claude-opus-5-5` | Local Claude Code CLI | The picker offers the same levels; the CLI chooses the actual effort | A group the official CLI is allowed to use; see below |
|
|
96
|
+
| `claude-sonnet-5-5` | Same | Same | Same |
|
|
97
|
+
| `claude-haiku-5-5` | Same; session titles always use this model | Same | Same |
|
|
98
|
+
|
|
99
|
+
A model in the list does not mean the current token group allows it. When changing groups, do not drop the group that serves the model you still want.
|
|
100
|
+
|
|
101
|
+
<details>
|
|
102
|
+
<summary>Protocol, group, and CLI constraints</summary>
|
|
103
|
+
|
|
104
|
+
- OpenAI / Codex traffic must use the Responses protocol. `gpt-6.1-sol` is pinned to `openai-responses` at `https://cf.api.fan/v1`. Do not put `/responses` in the base URL.
|
|
105
|
+
- Codex-group Responses requests must include `prompt_cache_key` or the gateway returns 403. The harness supplies the session id; the plugin adds a fallback when a caller does not.
|
|
106
|
+
- The Anthropic base URL has no `/v1` (the client appends `/v1/messages`). The three Claude models declare the Messages protocol in the catalog, but their requests are made by the local CLI.
|
|
107
|
+
- The `cc` group accepts only the official Claude CLI. A normal API client gets `403 This API endpoint is only accessible via the official Claude CLI`. This plugin runs the local `claude` binary for that group instead of switching the token to `cc-sale` or pretending to be the official client. If the token has no group the official CLI may use, the three Claude models still will not run.
|
|
108
|
+
- Selecting a Claude model when the CLI is missing fails that turn with an install hint. It does not surface as a missing binary name alone.
|
|
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
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- 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
|
+
- An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
|
|
115
|
+
|
|
116
|
+
</details>
|
|
117
|
+
|
|
118
|
+
## Update and uninstall
|
|
119
|
+
|
|
120
|
+
**Update:** on the desktop **Plugins** page, install `dsh-packycode-api` again (or `dsh-packycode-api@version`), then quit and reopen the desktop application. A page refresh does not reload the Host.
|
|
121
|
+
|
|
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
|
+
|
|
124
|
+
## FAQ
|
|
125
|
+
|
|
126
|
+
**One model fails, but the key is valid?** Check whether the token group covers that model. The `cc` group does not serve Claude to a normal API client; this plugin's Claude models need the local CLI. A group refusal does not disable the key or rotate to the next one.
|
|
127
|
+
|
|
128
|
+
**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
|
+
|
|
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
|
+
|
|
132
|
+
**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
|
+
|
|
134
|
+
## Security and scope
|
|
135
|
+
|
|
136
|
+
This is a community plugin, not affiliated with or endorsed by DeepSeek or PackyCode. Model requests go to the PackyCode host you configured. Keeping credentials out of browser responses does not mean inference is local. Claude models also start the official CLI on this device.
|
|
137
|
+
|
|
138
|
+
Keys stay in the local DSH credential file (`~/.dsh/.credentials.yaml`, owner-only as enforced by DSH). They are not written into settings, logs, or the transcript. Balance requests run only on the host, with a timeout and redirects refused.
|
|
139
|
+
|
|
140
|
+
Use [Issues](https://github.com/BaronCyrus/dsh-packycode-api/issues) for ordinary bugs. Never include a key, token, or credential-file contents. See [SECURITY.md](SECURITY.md).
|
|
141
|
+
|
|
142
|
+
## Local development
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
git clone https://github.com/BaronCyrus/dsh-packycode-api.git
|
|
146
|
+
cd dsh-packycode-api
|
|
147
|
+
pnpm install --frozen-lockfile
|
|
148
|
+
pnpm run check
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`pnpm run check` runs every test, rebuilds, and writes a tarball under `.artifacts/`. `lib/` is a committed build; regenerate it with `pnpm run build` after editing `src/`, and do not hand-edit it.
|
|
152
|
+
|
|
153
|
+
`node scripts/live-check.mjs` checks the live gateway. It reads a key already stored on this machine and spends a small amount of quota.
|
|
154
|
+
|
|
155
|
+
Read [AGENTS.md](AGENTS.md) before agent-assisted changes or a release. Publishing is the [Publish workflow](.github/workflows/publish.yml), triggered by pushing a `v*` tag. Do not run `npm publish` locally.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
[MIT](LICENSE)
|
package/README.md
CHANGED
|
@@ -1,101 +1,159 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="docs/assets/dsh-packycode-mascot.png" width="220" height="220" alt="DSH PackyCode API 吉祥物">
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# DSH PackyCode API
|
|
6
|
+
|
|
7
|
+
**简体中文** · [English](README.en.md)
|
|
8
|
+
|
|
9
|
+
**用 PackyCode 的 API Key,在 DSH 里选模型、轮换 Key、看余额。**
|
|
10
|
+
|
|
11
|
+
安装后自带 `PackyCode` 模型分组,不用改 YAML,也不用另配 provider。
|
|
12
|
+
|
|
13
|
+
[](https://github.com/BaronCyrus/dsh-packycode-api/actions/workflows/ci.yml)
|
|
14
|
+
[](https://www.npmjs.com/package/dsh-packycode-api)
|
|
15
|
+
[](https://www.npmjs.com/package/dsh-packycode-api)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://github.com/BaronCyrus/dsh-packycode-api/stargazers)
|
|
18
|
+
|
|
19
|
+
[功能](#功能) · [安装](#安装) · [日常使用](#日常使用) · [模型与分组](#模型与分组) · [更新与卸载](#更新与卸载)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
## 功能
|
|
24
|
+
|
|
25
|
+
| 能力 | 使用体验 |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| **自带模型路由** | 安装后模型选择器出现 `PackyCode` 分组,不需要配置 provider,也不用改 `cordis.patch.yml` |
|
|
28
|
+
| **多 Key 按顺序使用** | 额度用尽或 Key 被拒绝时自动切到下一个可用 Key;失败的那一步由重试接手 |
|
|
29
|
+
| **余额一眼可见** | 设置页列出每个 Key 的剩余、已用、额度和重置周期,并给出这些 Key 的合计;输入框右侧显示当前 Key 的剩余额度 |
|
|
30
|
+
| **Claude 走本机 CLI** | `claude-opus-5-5`、`claude-sonnet-5-5`、`claude-haiku-5-5` 由本机官方 Claude Code CLI 执行;设置页可以检测并一键安装 |
|
|
31
|
+
| **凭据不离开本机** | Key 只存在 DSH 凭据文件里。浏览器只能看到备注、状态和余额,拿不到 Key |
|
|
9
32
|
|
|
10
33
|
## 安装
|
|
11
34
|
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
先准备好 **DeepSeek Harness** 和 **PackyCode(PackyAPI)的 API Key**。尚未安装 DSH,可查看 [DSH 官方说明](https://github.com/deepseek-ai/deepseek-harness#run)。
|
|
36
|
+
|
|
37
|
+
### 1. 安装插件
|
|
38
|
+
|
|
39
|
+
打开桌面应用的**插件**页,点击**添加插件**,在**包名或地址**中填入:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
dsh-packycode-api
|
|
14
43
|
```
|
|
15
44
|
|
|
16
|
-
|
|
45
|
+
在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.0.3`。
|
|
17
46
|
|
|
18
|
-
|
|
47
|
+
完成后**手动重启桌面应用**。仅刷新页面不会重新加载 Host 里的插件。
|
|
19
48
|
|
|
20
|
-
|
|
21
|
-
2. 在「添加 Key」里粘贴 PackyCode 的 API Key(可填备注,便于区分多个 Key),点「添加」。
|
|
22
|
-
3. 回到对话,在模型选择器里选 `PackyCode` 分组下的模型,例如 `claude-opus-5-5`。
|
|
49
|
+
### 2. 添加 Key
|
|
23
50
|
|
|
24
|
-
|
|
51
|
+
打开 **设置 → PackyCode**,在「添加 Key」里粘贴 API Key。备注可选,用来区分多个 Key。点「添加」。
|
|
25
52
|
|
|
26
|
-
|
|
53
|
+
> 不要把 Key 粘贴到公开 Issue、聊天记录或截图里。插件不会读取环境变量 `PACKY_CODE_API_KEY`,也不会在失败时偷偷改用别的 provider。
|
|
27
54
|
|
|
28
|
-
|
|
29
|
-
- 同一个 Key 出现**额度用尽**(网关会报 `额度/余额/insufficient quota` 之类)或**被拒绝**(HTTP 401 / `无效的令牌`)时,插件会把该 Key 标记为「已停用」并自动切到下一个可用 Key;本轮失败的那一步由 harness 的重试重新执行,从而真正用上新 Key。
|
|
30
|
-
- 限速(429)不会切换 Key——那是瞬时问题,由重试吸收即可。
|
|
31
|
-
- 网关把上游渠道故障误报成 400(`当前令牌未覆盖供应商 …`)时也不会切换 Key,插件把它当作可重试的瞬时故障。
|
|
32
|
-
- Key 充值或更换后,点「重新启用」即可让它重新参与。
|
|
55
|
+
### 3. 选择模型
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
回到对话,在模型选择器里选 `PackyCode` 分组下的模型。`deepseek-flash` 和 `gpt-6.1-sol` 添加 Key 后即可用。三个 Claude 模型还需要本机装有 Claude Code CLI,见下文。
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| 每个 Key 的额度 | `GET {余额接口}/api/usage/token/` | `total_available` / `quota_limit` / `quota_reset_period`;`quota_limit = -1` 表示无周期上限,`unlimited_quota = true` 表示不限额度 |
|
|
39
|
-
| 账号额度 | `GET {模型接口}/v1/dashboard/billing/subscription` 与 `/usage` | `hard_limit_usd` 与 `total_usage`,剩余即两者之差 |
|
|
59
|
+
<details>
|
|
60
|
+
<summary>检查安装 / 其它 profile / 本地目录</summary>
|
|
40
61
|
|
|
41
|
-
|
|
62
|
+
桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
|
|
42
63
|
|
|
43
|
-
|
|
64
|
+
**仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.0.3`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
|
|
44
65
|
|
|
45
|
-
|
|
46
|
-
- **OpenAI / Codex 的 base_url 要带 `/v1`**,且不要把 `/responses` 这类端点路径写进根地址(客户端自己拼)。插件里是 `https://cf.api.fan/v1`。
|
|
47
|
-
- **Anthropic 的 base_url 不能带 `/v1`**(客户端自己拼 `/v1/messages`)—— `claude-opus-5-5` 用的是 `https://cf.api.fan`。
|
|
48
|
-
- Codex 分组的 Responses 请求**必须带 `prompt_cache_key`**(否则 403 反代校验)。harness 会带上会话 id;插件在调用方没给 session id 时补一个兜底值。
|
|
49
|
-
- 各模型的推理档位按厂商文档声明:`deepseek-flash` = `off/low/high/max`,`gpt-6.1-sol` 与 `claude-opus-5-5` = `low/medium/high/xhigh/max`(后两者无法关闭推理)。请求里声明了模型不支持的档位会被 harness 在发请求前拒绝,所以档位不要随手改。
|
|
50
|
-
- **分组决定"能不能用 API 调用"**(PackyCode 控制台里给令牌选的分组):
|
|
51
|
-
- `cc`(claude code 专用)**只接受官方 Claude CLI**,用 API 调 Claude 会 403 `This API endpoint is only accessible via the official Claude CLI`。要让本插件调 Claude,请选 `cc-sale` 或 `claude-officially`。
|
|
52
|
-
- `codex` / `codex-sale` 要求标准 Codex 客户端形态(插件已内置 `prompt_cache_key` 等必要条件),对应用 `gpt-6.1-sol`。
|
|
53
|
-
- `deepseek-officially` / `deepseek-sale` 对应用 `deepseek-flash`。
|
|
54
|
-
- 换分组时别把这三个分组一起去掉,否则对应模型会失效。
|
|
55
|
-
- 插件识别这类"只允许官方客户端"的拒绝:**不重试、不切换也不停用 Key**,并直接给出中文说明(而不是让你看到"API Key 无效")。
|
|
66
|
+
本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
没有浏览器的 profile 也能走模型路由,只是看不到设置页和输入框额度。
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
</details>
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
- 查找顺序:设置项 `claudeCliPath` → 官方安装器位置(`~/.local/bin/claude`,Windows 为 `%USERPROFILE%\.local\bin\claude.exe`)→ npm / Homebrew / Volta / nvm 的常见位置(Windows 为 `%APPDATA%\npm\claude.cmd`)→ `PATH` → `npm prefix -g` 给出的全局目录(从 Finder / 开始菜单启动的 DSH 拿到的 `PATH` 往往比终端短,所以要问 npm)。
|
|
63
|
-
- Windows 上 npm 装出来的是 `claude.cmd` 启动器。插件**不经过** `cmd.exe` 启动它,而是读出启动器指向的程序(`node …\cli.js` 或包内的 `.exe`)直接运行,所以提示词不会被 `cmd.exe` 解析。只有 `npm install` 本身经 `cmd.exe` 运行,它的参数都是固定的。Windows 上 Claude Code 另外需要 Git for Windows。
|
|
64
|
-
- 常见失败会给出对应提示:找不到 npm(先装 Node.js)、全局目录没有写权限(`EACCES`/`EPERM`,提示用 `sudo` 或管理员终端)、网络错误、超时;同时显示 npm 输出的最后几行。
|
|
65
|
-
- 没装 CLI 时选了 Claude 模型,该回合会直接报「Claude Code CLI is not installed on this device…」,并提示去设置页安装。
|
|
66
|
-
- **凭据**:子进程用**插件配置的 baseURL 与当前 Key**(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),并使用独立的 `CLAUDE_CONFIG_DIR`(`~/.dsh/packycode-claude-cli`)。隔离是必须的:`~/.claude/settings.json` 里的 `env` 会覆盖进程环境,不隔离就等于用你 CLI 自己配置里的 token,插件 Key 完全不生效。
|
|
67
|
-
- **设置项**:
|
|
68
|
-
- `claudeCliPath` — CLI 路径,空则按上面「查找顺序」自动查找。
|
|
69
|
-
- `claudeCliMode` — 默认 `auto`:**跟随当前会话的 DSH 权限预设**,不需要额外配置。`read-only` 预设 → `read-only`(Read/Grep/Glob);`workspace-write` 预设 → `edit`(另加 Edit/Write);`danger-full-access` 且审批为 `never` → `full`(`--dangerously-skip-permissions`,含 shell 命令)。CLI 无法向 DSH 用户发起审批,所以只要会话还需要审批,就不会给它 `full`。也可以显式填 `read-only` / `edit` / `full` 固定下来。
|
|
70
|
-
- `claudeCliCwd` — 工作目录,空则用**当前会话的工作区**(取不到时退回进程目录)。
|
|
71
|
-
- **语义**:一个 CLI 回合 = 一次委派。CLI 用**它自己的**工具与权限系统完成任务并给出最终答案;它无法调用 harness 的工具,所以插件**不会**把工具清单转发给它;系统提示也用 CLI 自己的(只把最近的对话作为上下文,过长按尾部截断)。因此对 `claude-opus-5-5` 而言:DSH 的沙箱/审批不约束子进程内部动作。
|
|
72
|
-
- **实时输出**:CLI 的思考过程与每一次工具调用(`▸ Bash · git status`、`▸ Read · …`,失败时追加 `✗ …`)会实时出现在该回合的「思考」区,回答正文实时流式输出。它们只是进度展示,不是 DSH 的工具调用,也不会带进下一回合的委派提示。
|
|
73
|
-
- **用量**:上下文占用与缓存命中取自 CLI 本回合**最后一次**请求(未缓存输入 / 缓存读 / 缓存写),输出 token 为整回合合计。CLI 几乎全部输入都命中缓存,所以缓存命中率通常很高。
|
|
74
|
-
- **会话标题**:会话跑在 Claude 模型上时,生成标题固定改用 `claude-haiku-5-5`,只读、在临时目录下运行(不加载项目记忆),只把标题请求本身作为提示词,不再为起个标题跑一整轮 Opus 委派。
|
|
75
|
-
- **代价**:每次数秒到数十秒,按 PackyCode 计费(实测单次约 **$0.19**,因 CLI 自带大量系统提示与工具说明)。
|
|
76
|
-
- 另有 `claude_code` **工具**:当会话由 `gpt-6.1-sol` / `deepseek-flash` 驱动时,可以让模型显式把某个任务委派给 Claude Code(参数含 `prompt` / `mode` / `model` / `cwd`),与上面共用同一个 runner。模型请求的 `mode` 不会超过会话权限预设允许的上限。
|
|
72
|
+
## 日常使用
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
**换 Key。** Key 按添加顺序使用。点「切到此 Key」可以指定当前 Key;「重命名」改备注(最多 60 字,不能为空);充值或更换后点「重新启用」。
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
- 浏览器只能拿到 Key 的**备注、状态和余额**,拿不到 Key 本身。
|
|
82
|
-
- 余额请求只在宿主侧发出,带超时并禁止重定向。
|
|
83
|
-
- 插件**不会**回退到环境变量里的 `PACKY_CODE_API_KEY`,也不会回退到其它 provider:只用你显式添加的 Key。
|
|
76
|
+
**看余额。** 设置页的合计是**已列出的这些 Key** 的剩余、已用与额度,不是 PackyCode 账号下全部令牌的账单。读失败的 Key 不计入合计,并会注明个数。输入框右侧只在选中 PackyCode 模型时显示当前 Key 的剩余额度。「刷新」会跳过约 60 秒的缓存。默认模型接口是 `https://cf.api.fan`,余额接口是 `https://www.packyapi.com`,可在「高级:接口地址」里改。
|
|
84
77
|
|
|
85
|
-
|
|
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)。
|
|
86
79
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
<details>
|
|
81
|
+
<summary>什么时候会自动切换 Key</summary>
|
|
82
|
+
|
|
83
|
+
- **会切换并停用当前 Key:** 额度用尽(网关报额度、余额、insufficient quota 之类),或 Key 被拒绝(HTTP 401 / `无效的令牌`)。失败的那一步由 harness 重试,从而用上新 Key。
|
|
84
|
+
- **不会切换:** 限速(429);网关把上游渠道故障误报成 400(`当前令牌未覆盖供应商 …`);以及「只允许官方客户端」的 403。后一种会直接说明原因,而不是显示成 API Key 无效。
|
|
85
|
+
- 全部 Key 都停用时,请求失败并说明没有可用 Key,不会回退到别的凭据。
|
|
86
|
+
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
## 模型与分组
|
|
90
|
+
|
|
91
|
+
| 模型 | 怎么调用 | 推理档位 | 适合的令牌分组 |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| `deepseek-flash` | HTTP,chat/completions | 可关;`low` / `high` / `max` | `deepseek-officially`、`deepseek-sale` |
|
|
94
|
+
| `gpt-6.1-sol` | HTTP,Responses,可带图 | 不能关;`low` / `medium` / `high` / `xhigh` / `max` | `codex`、`codex-sale` |
|
|
95
|
+
| `claude-opus-5-5` | 本机 Claude Code CLI | 选择器档位同上,实际档位由 CLI 决定 | 要能给官方 CLI 用的分组,见下 |
|
|
96
|
+
| `claude-sonnet-5-5` | 同上 | 同上 | 同上 |
|
|
97
|
+
| `claude-haiku-5-5` | 同上;会话标题也固定走它 | 同上 | 同上 |
|
|
98
|
+
|
|
99
|
+
列表里看得到模型,不代表当前令牌分组一定放行。换分组时不要把对应分组一起去掉。
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
<details>
|
|
102
|
+
<summary>协议、分组和 CLI 的具体约束</summary>
|
|
94
103
|
|
|
95
|
-
|
|
104
|
+
- OpenAI / Codex 必须走 Responses。`gpt-6.1-sol` 已固定为 `openai-responses`,base URL 是 `https://cf.api.fan/v1`,不要把 `/responses` 写进根地址。
|
|
105
|
+
- Codex 分组的 Responses 请求必须带 `prompt_cache_key`,否则 403。harness 会带会话 id;没有会话 id 时插件补一个兜底值。
|
|
106
|
+
- Anthropic 的 base URL 不带 `/v1`(客户端自己拼 `/v1/messages`)。三个 Claude 模型虽然目录里声明了 Messages 协议,实际请求由本机 CLI 发出。
|
|
107
|
+
- `cc`(Claude Code 专用)只接受官方 Claude CLI。用普通 API 客户端调 Claude 会 403:`This API endpoint is only accessible via the official Claude CLI`。本插件因此调用本机 `claude`,而不是改去 `cc-sale`,也不是伪造客户端身份。令牌如果没有开放给官方 CLI 的分组,这三个模型仍然不可用。
|
|
108
|
+
- 选了 Claude 模型但本机没有 CLI 时,这一回合会直接说明未安装,并指向设置页的安装按钮。
|
|
109
|
+
- CLI 使用插件配置的地址和当前 Key(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),以及独立配置目录 `~/.dsh/packycode-claude-cli`。不隔离的话,`~/.claude/settings.json` 里的 `env` 会盖掉插件 Key。
|
|
110
|
+
- CLI 权限默认跟随当前会话:`read-only` → 只读,`workspace-write` → 可编辑,`danger-full-access` 且不再审批 → `full`。CLI 不能向 DSH 弹审批,所以只要会话还要审批,就不会给它 `full`。也可把设置项 `claudeCliMode` 固定成 `read-only` / `edit` / `full`。`claudeCliPath` 留空则自动查找;`claudeCliCwd` 留空则用当前会话工作区。
|
|
111
|
+
- 思考过程和工具调用(`▸ Bash`、`▸ Read`,失败时带 `✗`)会出现在这一回合的思考区。它们是进度,不是 DSH 的工具调用。CLI 自己的工具不受 DSH 沙箱约束。
|
|
112
|
+
- 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
|
|
113
|
+
- 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限。
|
|
114
|
+
- 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
|
|
115
|
+
|
|
116
|
+
</details>
|
|
117
|
+
|
|
118
|
+
## 更新与卸载
|
|
119
|
+
|
|
120
|
+
**更新:** 在桌面应用的**插件**页重新安装 `dsh-packycode-api`(或 `dsh-packycode-api@版本`),然后完全退出并重新打开桌面应用。只刷新页面不会重载 Host。
|
|
121
|
+
|
|
122
|
+
**卸载:** 在**插件**页卸载本插件,然后重启桌面应用。卸载不会删除其它插件、整个 profile,也不会撤销 PackyCode 控制台里的令牌。
|
|
123
|
+
|
|
124
|
+
## 常见问题
|
|
125
|
+
|
|
126
|
+
**某个模型不可用,但 Key 是有效的?** 先看令牌分组是否覆盖这个模型。`cc` 分组不会把 Claude 交给普通 API 客户端;本插件的 Claude 模型要靠本机 CLI。分组拒绝不会停用 Key,也不会切到下一个 Key。
|
|
127
|
+
|
|
128
|
+
**余额对不上账号账单?** 设置页合计的是这里列出的 Key,不含同账号下没添加到本插件的其它令牌。个别 Key 读失败时会单独注明,不会被算成 0。
|
|
129
|
+
|
|
130
|
+
**限速之后 Key 被停用了?** 不该发生。429 不会切换 Key。额度用尽或 401 才会停用;充值后点「重新启用」。
|
|
131
|
+
|
|
132
|
+
**装了 CLI 还是不能用 Claude?** 从 Finder 或开始菜单启动时,`PATH` 往往比终端短。插件会再查官方安装位置、常见包管理器目录和 `npm prefix -g`。仍找不到就在设置里填 `claudeCliPath`,或点「重新检测」。改的是 Host 或重新安装了插件时,要完全退出并重启桌面应用。
|
|
133
|
+
|
|
134
|
+
## 安全边界
|
|
135
|
+
|
|
136
|
+
本项目是社区插件,与 DeepSeek、PackyCode 无隶属或背书关系。模型请求会发到你配置的 PackyCode 接口;凭据不返回浏览器,不代表推理在本地完成。Claude 模型还会在本机启动官方 CLI。
|
|
137
|
+
|
|
138
|
+
Key 存在本机 DSH 凭据文件(`~/.dsh/.credentials.yaml`,权限由 DSH 设为仅所有者可读),不进设置文档、日志或对话记录。余额请求只在宿主侧发出,带超时并禁止重定向。
|
|
139
|
+
|
|
140
|
+
普通问题请提交 [Issue](https://github.com/BaronCyrus/dsh-packycode-api/issues)。不要附带 Key、令牌或凭据文件内容。更多说明见 [SECURITY.md](SECURITY.md)。
|
|
141
|
+
|
|
142
|
+
## 本地开发
|
|
96
143
|
|
|
97
144
|
```sh
|
|
98
|
-
|
|
145
|
+
git clone https://github.com/BaronCyrus/dsh-packycode-api.git
|
|
146
|
+
cd dsh-packycode-api
|
|
147
|
+
pnpm install --frozen-lockfile
|
|
148
|
+
pnpm run check
|
|
99
149
|
```
|
|
100
150
|
|
|
101
|
-
|
|
151
|
+
`pnpm run check` 会跑全部测试、重新构建,并在 `.artifacts/` 打出 tarball。`lib/` 是已提交的构建产物,改 `src/` 后用 `pnpm run build` 生成,不要手改。
|
|
152
|
+
|
|
153
|
+
真机网关用 `node scripts/live-check.mjs` 验证。它会读本机已保存的 Key,并消耗少量额度。
|
|
154
|
+
|
|
155
|
+
使用 Agent 修改或发布时,先读 [AGENTS.md](AGENTS.md)。发布由推送 `v*` 标签触发 [Publish workflow](.github/workflows/publish.yml),不要在本机执行 `npm publish`。
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
[MIT](LICENSE)
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -106,12 +106,16 @@ window.__ModuleLoader__.load({
|
|
|
106
106
|
refresh: "刷新",
|
|
107
107
|
refreshing: "刷新中…",
|
|
108
108
|
keysTitle: "API Key",
|
|
109
|
-
keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个可用 Key
|
|
109
|
+
keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个可用 Key。上方的账户余额按备注名逐条列出,可用「重命名」修改备注。",
|
|
110
110
|
keyActive: "使用中",
|
|
111
111
|
keyDisabled: "已停用",
|
|
112
112
|
keyUse: "切到此 Key",
|
|
113
113
|
keyEnable: "重新启用",
|
|
114
114
|
keyRemove: "删除",
|
|
115
|
+
keyRename: "重命名",
|
|
116
|
+
keyRenameSave: "保存",
|
|
117
|
+
keyRenameCancel: "取消",
|
|
118
|
+
keyRenamePlaceholder: "新的备注名",
|
|
115
119
|
keyUnlimited: "不限额度",
|
|
116
120
|
keyNoBalance: "余额未知",
|
|
117
121
|
keyRemaining: "剩余",
|
|
@@ -173,12 +177,16 @@ window.__ModuleLoader__.load({
|
|
|
173
177
|
refresh: "Refresh",
|
|
174
178
|
refreshing: "Refreshing…",
|
|
175
179
|
keysTitle: "API keys",
|
|
176
|
-
keysHint: "Used in order: when the active key cannot serve a request, the next usable one takes over. The balances above are listed
|
|
180
|
+
keysHint: "Used in order: when the active key cannot serve a request, the next usable one takes over. The balances above are listed by these names; use Rename to change one.",
|
|
177
181
|
keyActive: "Active",
|
|
178
182
|
keyDisabled: "Disabled",
|
|
179
183
|
keyUse: "Use this key",
|
|
180
184
|
keyEnable: "Re-enable",
|
|
181
185
|
keyRemove: "Remove",
|
|
186
|
+
keyRename: "Rename",
|
|
187
|
+
keyRenameSave: "Save",
|
|
188
|
+
keyRenameCancel: "Cancel",
|
|
189
|
+
keyRenamePlaceholder: "New name",
|
|
182
190
|
keyUnlimited: "No quota limit",
|
|
183
191
|
keyNoBalance: "Balance unknown",
|
|
184
192
|
keyRemaining: "Remaining",
|
|
@@ -453,6 +461,7 @@ window.__ModuleLoader__.load({
|
|
|
453
461
|
key: "",
|
|
454
462
|
label: ""
|
|
455
463
|
});
|
|
464
|
+
const [renaming, setRenaming] = react.useState();
|
|
456
465
|
const generation = react.useRef(0);
|
|
457
466
|
const load = react.useCallback((force = false) => {
|
|
458
467
|
const current = ++generation.current;
|
|
@@ -493,6 +502,14 @@ window.__ModuleLoader__.load({
|
|
|
493
502
|
label: ""
|
|
494
503
|
}));
|
|
495
504
|
};
|
|
505
|
+
const submitRename = (event) => {
|
|
506
|
+
event.preventDefault();
|
|
507
|
+
if (renaming === void 0 || renaming.label.trim() === "") return;
|
|
508
|
+
mutate("keys/rename")({
|
|
509
|
+
id: renaming.id,
|
|
510
|
+
label: renaming.label
|
|
511
|
+
}).then(() => setRenaming(void 0));
|
|
512
|
+
};
|
|
496
513
|
const rows = balance?.keys ?? [];
|
|
497
514
|
const usageOf = (id) => rows.find((row) => row.id === id);
|
|
498
515
|
const keys = status?.keys ?? [];
|
|
@@ -506,6 +523,7 @@ window.__ModuleLoader__.load({
|
|
|
506
523
|
usage?.limitUsd === void 0 ? void 0 : `${t("accountLimit")} ${formatUsd(usage.limitUsd)}`,
|
|
507
524
|
resetLabel(usage?.resetPeriod, t) === void 0 ? void 0 : resetLabel(usage.resetPeriod, t)
|
|
508
525
|
].filter(Boolean);
|
|
526
|
+
const tokenName = usage?.name !== void 0 && usage.name !== row.label ? `${t("keyName")} ${usage.name}` : void 0;
|
|
509
527
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
510
528
|
className: "packyCodeRow",
|
|
511
529
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -513,7 +531,7 @@ window.__ModuleLoader__.load({
|
|
|
513
531
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
514
532
|
className: "packyCodeRowName",
|
|
515
533
|
children: [
|
|
516
|
-
|
|
534
|
+
row.label,
|
|
517
535
|
row.id === status?.activeId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
518
536
|
className: "packyCodeChip packyCodeChipActive",
|
|
519
537
|
children: t("keyActive")
|
|
@@ -525,7 +543,7 @@ window.__ModuleLoader__.load({
|
|
|
525
543
|
]
|
|
526
544
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
527
545
|
className: "packyCodeRowMeta",
|
|
528
|
-
children: figures.length > 0 ? figures.join(" · ") : row.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
|
|
546
|
+
children: figures.length > 0 ? [tokenName, ...figures].filter(Boolean).join(" · ") : row.error === void 0 ? t("keyNoBalance") : `${t("loadFailed")}: ${row.error}`
|
|
529
547
|
})]
|
|
530
548
|
})
|
|
531
549
|
}, row.id);
|
|
@@ -638,7 +656,37 @@ window.__ModuleLoader__.load({
|
|
|
638
656
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
639
657
|
className: "packyCodeRowName",
|
|
640
658
|
children: [
|
|
641
|
-
key.
|
|
659
|
+
renaming?.id === key.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
660
|
+
className: "packyCodeForm",
|
|
661
|
+
onSubmit: submitRename,
|
|
662
|
+
children: [
|
|
663
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
664
|
+
className: "packyCodeInput",
|
|
665
|
+
type: "text",
|
|
666
|
+
maxLength: 60,
|
|
667
|
+
autoFocus: true,
|
|
668
|
+
placeholder: t("keyRenamePlaceholder"),
|
|
669
|
+
value: renaming.label,
|
|
670
|
+
onChange: (event) => setRenaming({
|
|
671
|
+
id: key.id,
|
|
672
|
+
label: event.target.value
|
|
673
|
+
}),
|
|
674
|
+
onKeyDown: (event) => event.key === "Escape" && setRenaming(void 0)
|
|
675
|
+
}),
|
|
676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
677
|
+
type: "submit",
|
|
678
|
+
className: "packyCodeButton",
|
|
679
|
+
disabled: busy || renaming.label.trim() === "",
|
|
680
|
+
children: t("keyRenameSave")
|
|
681
|
+
}),
|
|
682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
683
|
+
type: "button",
|
|
684
|
+
className: "packyCodeButton",
|
|
685
|
+
onClick: () => setRenaming(void 0),
|
|
686
|
+
children: t("keyRenameCancel")
|
|
687
|
+
})
|
|
688
|
+
]
|
|
689
|
+
}) : key.label,
|
|
642
690
|
key.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
643
691
|
className: "packyCodeChip packyCodeChipActive",
|
|
644
692
|
children: t("keyActive")
|
|
@@ -669,6 +717,16 @@ window.__ModuleLoader__.load({
|
|
|
669
717
|
onClick: () => mutate("keys/enable")({ id: key.id }),
|
|
670
718
|
children: t("keyEnable")
|
|
671
719
|
}),
|
|
720
|
+
renaming?.id !== key.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
721
|
+
type: "button",
|
|
722
|
+
className: "packyCodeButton",
|
|
723
|
+
disabled: busy,
|
|
724
|
+
onClick: () => setRenaming({
|
|
725
|
+
id: key.id,
|
|
726
|
+
label: key.label
|
|
727
|
+
}),
|
|
728
|
+
children: t("keyRename")
|
|
729
|
+
}),
|
|
672
730
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
673
731
|
type: "button",
|
|
674
732
|
className: "packyCodeButton",
|
package/lib/index.js
CHANGED
|
@@ -1819,6 +1819,20 @@ var PackyCodeKeyRing = class {
|
|
|
1819
1819
|
};
|
|
1820
1820
|
}));
|
|
1821
1821
|
}
|
|
1822
|
+
async rename(id, label) {
|
|
1823
|
+
const name = text(label).trim().slice(0, 60);
|
|
1824
|
+
if (name === "") throw new Error("That PackyCode key name cannot be empty");
|
|
1825
|
+
return projection(await this.#write((state) => {
|
|
1826
|
+
if (!state.keys.some((candidate) => candidate.id === id)) throw new Error("Unknown PackyCode key");
|
|
1827
|
+
return {
|
|
1828
|
+
activeId: state.activeId,
|
|
1829
|
+
keys: state.keys.map((candidate) => candidate.id === id ? {
|
|
1830
|
+
...candidate,
|
|
1831
|
+
label: name
|
|
1832
|
+
} : candidate)
|
|
1833
|
+
};
|
|
1834
|
+
}));
|
|
1835
|
+
}
|
|
1822
1836
|
async remove(id) {
|
|
1823
1837
|
return projection(await this.#write((state) => {
|
|
1824
1838
|
const remaining = state.keys.filter((candidate) => candidate.id !== id);
|
|
@@ -2376,6 +2390,10 @@ function createPackyCodeRpc({ ring, balance, hosts, preference, claudeCli }) {
|
|
|
2376
2390
|
await ring.add(payload?.key, payload?.label);
|
|
2377
2391
|
balance.invalidate();
|
|
2378
2392
|
return ok(await status());
|
|
2393
|
+
case "keys/rename":
|
|
2394
|
+
await ring.rename(String(payload?.id ?? ""), payload?.label);
|
|
2395
|
+
balance.invalidate();
|
|
2396
|
+
return ok(await status());
|
|
2379
2397
|
case "keys/remove":
|
|
2380
2398
|
await ring.remove(String(payload?.id ?? ""));
|
|
2381
2399
|
balance.invalidate();
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-packycode-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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",
|
|
7
7
|
"main": "./lib/index.js",
|
|
8
|
+
"icon": "./icon-subscription.webp",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": "./lib/index.js",
|
|
10
11
|
"./client": "./lib/client.js",
|
|
@@ -12,9 +13,12 @@
|
|
|
12
13
|
"./package.json": "./package.json"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
16
|
+
"icon-subscription.webp",
|
|
15
17
|
"lib/*.js",
|
|
16
18
|
"cordis.patch.yml",
|
|
17
19
|
"README.md",
|
|
20
|
+
"README.en.md",
|
|
21
|
+
"docs/assets/dsh-packycode-mascot.png",
|
|
18
22
|
"AGENTS.md",
|
|
19
23
|
"CHANGELOG.md",
|
|
20
24
|
"SECURITY.md",
|