@mars-sea/dsh-commandcode-provider 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/lib/client.js +215 -34
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +19 -10
- package/lib/index.js +278 -202
- package/lib/index.js.map +1 -1
- package/package.json +19 -16
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.1] - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **New model: Ox Alpha (`stealth/ox-alpha`, command-code@1.31.0).** A free 1M-context reasoning model with vision input, included on every plan: it appears in the picker under the Go tier with `FREE` · `Image` · `1M` markers, is whitelisted as vision-capable and auto-thinking (it reasons automatically with no selectable effort levels), and carries a `FREE` deal while the stealth preview lasts.
|
|
12
|
+
- **The settings page now shows the plugin version** as a muted footer line ("Command Code Provider v0.6.1"), read from package.json at build time so it always matches the published release.
|
|
13
|
+
- **Settings-page input polish:** a brief "Saved ✓" confirmation flashes after each accepted save (failures already showed an error); the two millisecond timeout fields now validate their range while typing with specific messages ("must be at least 1" / "above the allowed maximum") instead of a generic save-time failure; and the API-key fields (default + every account) gained a Show/Hide toggle so a pasted key can be spot-checked.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Retries are now persistent (opencode-style) instead of two attempts.** Transient failures — rate limits, server errors, timeouts, transport drops, empty responses — retry up to 1000 times at the agent-step boundary, with waits doubling from 500 ms and capping at 15 minutes (±10% jitter). Permanent failures (an invalid key, unsupported content, plan rejections) still fail fast on the first attempt. Waits are smart: a 429's `Retry-After` header and the rotation pool's earliest known window-reset time are honored verbatim up to the 15-minute cap, so when every account's usage window is exhausted the session sleeps through the window and recovers in place instead of dying after two quick tries.
|
|
18
|
+
- **Synced with the official command-code@1.31.0 CLI** (upstream moved 1.28.4 → 1.29.0 → 1.30.0 → 1.30.1 → 1.31.0; the intermediate releases shipped BYOK provider support, model-traffic routing, and CLI UI fixes — none touch the Provider API). `COMMAND_CODE_CLI_VERSION` is now `1.31.0`. The only snapshot change across those releases is the new Ox Alpha model above; wire protocol, endpoints, effort map, plan maps, deals, and peak pricing are all unchanged.
|
|
19
|
+
- **Verified compatibility with DeepSeek Harness 0.1.0-rc.7 / 0.1.0-rc.8 / 0.1.1-rc.1**, including rc.8's reworked client boot protocol: the client bundle now declares its module-graph dependency (`dsh.client.external`), so client-module load order no longer relies on the platform seed table. The supported peer range widened to `^0.1.0-rc.6 || ^0.1.1-rc.1`, so 0.1.1 hosts install without unmet-peer-dependency warnings.
|
|
20
|
+
|
|
21
|
+
## [0.6.0] - 2026-08-19
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Synced with the official command-code@1.28.4 CLI** (upstream moved 1.28.1 → 1.28.4: 1.28.2 raised the Qwen 3.8 27B usage allowance on GOAT and Pro, 1.28.3 fixed a CLI UI overlay issue; 1.28.4 is not on the changelog page yet). `COMMAND_CODE_CLI_VERSION` is now `1.28.4`. No snapshot or wire changes: the 1.28.4 bundle's effort map, plan maps, ZA model table, endpoints, request fields, and stream event types are all identical to 1.28.1, and the live catalog still lists the same 56 models.
|
|
26
|
+
- **The settings page's account-usage card is now a carousel.** With several accounts configured, a tab strip (account label + status dot) switches between per-account reports instead of stacking them, so the page no longer grows with each account; the selector defaults to the currently serving account. Each account's report carries its own **remove** button (staged like every other edit, persisted on save) — offered only for accounts the settings document actually owns (the default account and composition-only literal-key accounts are not removable from the page), and the per-row remove buttons in the account-management card below were removed (unsaved additions keep theirs, since they never appear in the Host-side usage report). Accounts staged for removal disappear from the carousel immediately and stay hidden across the post-save refresh.
|
|
27
|
+
|
|
7
28
|
## [0.5.0] - 2026-08-19
|
|
8
29
|
|
|
9
30
|
### Added
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://www.npmjs.com/package/@mars-sea/dsh-commandcode-provider)
|
|
12
12
|
|
|
13
|
-
Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) LLM provider plugin for **Command Code**, ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT). It registers a `commandcode` provider whose requests are translated to Command Code's Provider API (`POST /alpha/generate`, reverse-engineered by the pi plugin, `command-code@1.28.
|
|
13
|
+
Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) LLM provider plugin for **Command Code**, ported from [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider) (MIT). It registers a `commandcode` provider whose requests are translated to Command Code's Provider API (`POST /alpha/generate`, reverse-engineered by the pi plugin, `command-code@1.28.4`).
|
|
14
14
|
|
|
15
15
|
> This is a community integration. You need your own Command Code account and API key or subscription, and Command Code's terms apply. This project is not affiliated with Command Code, Inc.
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@ Unofficial [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harnes
|
|
|
22
22
|
- **API key resolution order**: `config.apiKey` → credential ref `apiKeyEnv` (default `COMMANDCODE_API_KEY`) → launch environment → the official CLI auth file (`~/.commandcode/auth.json`, from `command-code login`).
|
|
23
23
|
- **Model-picker annotations**: every model shows the **minimum plan** that includes it (`KNOWN_PLANS`), an **active deal** or `FREE` badge (`KNOWN_DEALS`, expiry-aware so lapsed discounts hide themselves), the **current peak/off-peak state** (`Peak`/`Half`) for time-of-day-priced models, an **`Image`** marker for Vision models, and the **context window** (`1M` / `256K` / `262K`) — e.g. *"Go · 50% off · Image · 1M"*, *"Go · Half · 1M"*. The list is **sorted by plan tier** (Go → GOAT → Pro → Provider/Max), so the models your plan can use lead the picker.
|
|
24
24
|
- **Plan-aware picker filtering**: the picker **hides models above your subscription tier** outright (resolved live from your account's billing state). It fails open — an unreachable billing endpoint, an unknown plan, or a positive on-demand credit balance (which the official CLI treats as unlocking every model) all keep the full catalog visible — and the server stays the final gate. Set **Hide out-of-plan models** off on the settings page (or `filterModelsByPlan: false` in the `llm-commandcode` settings section) to always list every model.
|
|
25
|
-
- **Reasoning-effort support** for models the official catalog marks as such (`KNOWN_EFFORTS`, matching `command-code@1.28.
|
|
25
|
+
- **Reasoning-effort support** for models the official catalog marks as such (`KNOWN_EFFORTS`, matching `command-code@1.28.4`); reasoning models without effort levels still think automatically, exactly like the official CLI.
|
|
26
26
|
- **Image input for Vision-capable models** (sent in the official wire format via the dsh attachment service); text-only models refuse images loudly (`UNSUPPORTED_CONTENT`) rather than dropping them.
|
|
27
27
|
|
|
28
28
|
<img src="assets/screenshots/model-picker.png" alt="Model picker with plan, deal, image and context annotations" width="250">
|
package/README.zh-CN.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
[](https://www.npmjs.com/package/@mars-sea/dsh-commandcode-provider)
|
|
12
12
|
|
|
13
|
-
非官方 [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) 的 LLM provider 插件,用于 **Command Code**,移植自 [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider)(MIT 协议)。它注册了一个 `commandcode` provider,将请求转换为 Command Code 的 Provider API(`POST /alpha/generate`,由 pi 插件逆向工程,对应 `command-code@1.28.
|
|
13
|
+
非官方 [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) 的 LLM provider 插件,用于 **Command Code**,移植自 [pi-commandcode-provider](https://github.com/patlux/pi-commandcode-provider)(MIT 协议)。它注册了一个 `commandcode` provider,将请求转换为 Command Code 的 Provider API(`POST /alpha/generate`,由 pi 插件逆向工程,对应 `command-code@1.28.4`)。
|
|
14
14
|
|
|
15
15
|
> 这是一个社区集成。你需要自己的 Command Code 账号、API key 或订阅,并遵守 Command Code 的服务条款。本项目与 Command Code, Inc. 无关。
|
|
16
16
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **API key 解析顺序**:`config.apiKey` → 凭据引用 `apiKeyEnv`(默认 `COMMANDCODE_API_KEY`)→ 启动环境变量 → 官方 CLI 认证文件(`~/.commandcode/auth.json`,由 `command-code login` 写入)。
|
|
23
23
|
- **模型选择器标注**:每个模型显示包含它的**最低套餐**(`KNOWN_PLANS`)、**活动折扣**或 `FREE` 徽章(`KNOWN_DEALS`,到期自动隐藏已失效折扣)、峰谷定价模型的**当前时段状态**(`Peak`/`Half`,按当前 UTC 小时动态判断)、Vision 模型的 **`Image`** 标记,以及**上下文长度**(`1M` / `256K` / `262K`)——例如 *"Go · 50% off · Image · 1M"*、*"Go · Half · 1M"*。列表**按套餐排序**(Go → GOAT → Pro → Provider/Max),你当前套餐能用的模型总是排在最前。
|
|
24
24
|
- **按套餐过滤选择器**:选择器会**直接隐藏超出你订阅套餐的模型**(根据账户账单状态实时判断)。全程失败开放——账单接口不可用、套餐未知,或账户持有按需余额(官方 CLI 视为解锁全部模型)时都显示完整目录——服务端仍是最终闸门。在设置页关闭「隐藏套餐外模型」开关(或在 `llm-commandcode` 设置段中设 `filterModelsByPlan: false`)可始终列出全部模型。
|
|
25
|
-
- **推理强度(reasoning-effort)支持**:针对官方目录标为推理模型的模型(`KNOWN_EFFORTS`,与 `command-code@1.28.
|
|
25
|
+
- **推理强度(reasoning-effort)支持**:针对官方目录标为推理模型的模型(`KNOWN_EFFORTS`,与 `command-code@1.28.4` 一致);没有可选档位但仍支持思考的模型会自动思考,与官方 CLI 行为完全一致。
|
|
26
26
|
- **支持视觉模型的图片输入**(通过 dsh 附件服务、以官方 wire 格式发送);纯文本模型会明确拒绝图片(`UNSUPPORTED_CONTENT`)而非静默丢弃。
|
|
27
27
|
|
|
28
28
|
<img src="assets/screenshots/model-picker.png" alt="带套餐、折扣、图片与上下文标注的模型选择器" width="250">
|
package/lib/client.js
CHANGED
|
@@ -59,8 +59,15 @@ window.__ModuleLoader__.load({
|
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
62
|
+
/**
|
|
63
|
+
* A numeric field; an empty draft clears it, anything non-numeric blocks
|
|
64
|
+
* save, and an optional inclusive `bounds` range rejects out-of-range values
|
|
65
|
+
* with a specific reason (the Host schema would reject them at save time with
|
|
66
|
+
* only a generic failure — catching it here names the problem while typing).
|
|
67
|
+
* Decimals pass: the Host schema is `z.number()` too, and a fractional
|
|
68
|
+
* millisecond value is harmless even if pointless.
|
|
69
|
+
*/
|
|
70
|
+
function numberField$1(field, bounds) {
|
|
64
71
|
return {
|
|
65
72
|
field,
|
|
66
73
|
format: (value) => typeof value === "number" ? String(value) : "",
|
|
@@ -68,10 +75,22 @@ window.__ModuleLoader__.load({
|
|
|
68
75
|
const trimmed = text.trim();
|
|
69
76
|
if (trimmed === "") return { kind: "clear" };
|
|
70
77
|
const parsed = Number(trimmed);
|
|
71
|
-
|
|
78
|
+
if (!Number.isFinite(parsed)) return {
|
|
79
|
+
kind: "invalid",
|
|
80
|
+
reason: "format"
|
|
81
|
+
};
|
|
82
|
+
if (bounds?.min !== void 0 && parsed < bounds.min) return {
|
|
83
|
+
kind: "invalid",
|
|
84
|
+
reason: "tooSmall"
|
|
85
|
+
};
|
|
86
|
+
if (bounds?.max !== void 0 && parsed > bounds.max) return {
|
|
87
|
+
kind: "invalid",
|
|
88
|
+
reason: "tooLarge"
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
72
91
|
kind: "set",
|
|
73
92
|
value: parsed
|
|
74
|
-
}
|
|
93
|
+
};
|
|
75
94
|
}
|
|
76
95
|
};
|
|
77
96
|
}
|
|
@@ -95,16 +114,26 @@ window.__ModuleLoader__.load({
|
|
|
95
114
|
kind: "set",
|
|
96
115
|
value: false
|
|
97
116
|
};
|
|
98
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
kind: "invalid",
|
|
119
|
+
reason: "format"
|
|
120
|
+
};
|
|
99
121
|
}
|
|
100
122
|
};
|
|
101
123
|
}
|
|
124
|
+
const MAX_TIMEOUT_MS = 2147483647;
|
|
102
125
|
/** The fields this page edits inside the `llm-commandcode` namespace. */
|
|
103
126
|
const SECTION_FIELDS = [
|
|
104
127
|
textField("apiBase"),
|
|
105
128
|
textField("workingDir"),
|
|
106
|
-
numberField$1("requestTimeoutMs"
|
|
107
|
-
|
|
129
|
+
numberField$1("requestTimeoutMs", {
|
|
130
|
+
min: 1,
|
|
131
|
+
max: MAX_TIMEOUT_MS
|
|
132
|
+
}),
|
|
133
|
+
numberField$1("streamIdleTimeoutMs", {
|
|
134
|
+
min: 1,
|
|
135
|
+
max: MAX_TIMEOUT_MS
|
|
136
|
+
}),
|
|
108
137
|
booleanField$1("filterModelsByPlan"),
|
|
109
138
|
textField("activeAccount")
|
|
110
139
|
];
|
|
@@ -136,6 +165,7 @@ window.__ModuleLoader__.load({
|
|
|
136
165
|
keyDrafts = /* @__PURE__ */ new Map();
|
|
137
166
|
saving = false;
|
|
138
167
|
failed = false;
|
|
168
|
+
savedCount = 0;
|
|
139
169
|
/**
|
|
140
170
|
* @param scope - bound scope for the `llm-commandcode` namespace.
|
|
141
171
|
* @param api - credentials wire face.
|
|
@@ -206,7 +236,8 @@ window.__ModuleLoader__.load({
|
|
|
206
236
|
text: this.staged.get("apiKey")?.text ?? "",
|
|
207
237
|
clear: false,
|
|
208
238
|
overridden: false,
|
|
209
|
-
invalid: false
|
|
239
|
+
invalid: false,
|
|
240
|
+
invalidReason: void 0
|
|
210
241
|
},
|
|
211
242
|
apiBase: this.field("apiBase"),
|
|
212
243
|
workingDir: this.field("workingDir"),
|
|
@@ -216,10 +247,12 @@ window.__ModuleLoader__.load({
|
|
|
216
247
|
filterModelsByPlan: this.field("filterModelsByPlan"),
|
|
217
248
|
activeAccount: this.field("activeAccount"),
|
|
218
249
|
accounts,
|
|
250
|
+
accountsRemoving: [...this.removedRefs],
|
|
219
251
|
dirty: plan.length > 0 || this.accountsDirty(),
|
|
220
252
|
invalid: plan.some((item) => item.run === void 0),
|
|
221
253
|
saving: this.saving,
|
|
222
|
-
failed: this.failed
|
|
254
|
+
failed: this.failed,
|
|
255
|
+
savedCount: this.savedCount
|
|
223
256
|
};
|
|
224
257
|
}
|
|
225
258
|
/** Stage a new extra account (saved on the next `save()`). */
|
|
@@ -321,6 +354,7 @@ window.__ModuleLoader__.load({
|
|
|
321
354
|
this.saving = false;
|
|
322
355
|
this.failed = !landed;
|
|
323
356
|
if (landed) {
|
|
357
|
+
this.savedCount += 1;
|
|
324
358
|
this.staged.clear();
|
|
325
359
|
this.clearAccountStaging();
|
|
326
360
|
} else this.reconcileAccountStaging();
|
|
@@ -355,14 +389,16 @@ window.__ModuleLoader__.load({
|
|
|
355
389
|
text: spec.format(this.sectionValue(field)),
|
|
356
390
|
clear: false,
|
|
357
391
|
overridden: this.stored(field),
|
|
358
|
-
invalid: false
|
|
392
|
+
invalid: false,
|
|
393
|
+
invalidReason: void 0
|
|
359
394
|
};
|
|
360
395
|
const parsed = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
|
|
361
396
|
return {
|
|
362
397
|
text: staged.text,
|
|
363
398
|
clear: staged.clear,
|
|
364
399
|
overridden: parsed.kind === "set",
|
|
365
|
-
invalid: parsed.kind === "invalid"
|
|
400
|
+
invalid: parsed.kind === "invalid",
|
|
401
|
+
invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
|
|
366
402
|
};
|
|
367
403
|
}
|
|
368
404
|
sectionValue(field) {
|
|
@@ -808,6 +844,21 @@ window.__ModuleLoader__.load({
|
|
|
808
844
|
}]
|
|
809
845
|
};
|
|
810
846
|
//#endregion
|
|
847
|
+
//#region src/client/version.ts
|
|
848
|
+
/**
|
|
849
|
+
* The plugin's own version, read from package.json at build time.
|
|
850
|
+
*
|
|
851
|
+
* The client bundle inlines the JSON import (rolldown resolves it during the
|
|
852
|
+
* tsdown build; node tests read it through tsx), so the rendered value always
|
|
853
|
+
* matches the published package version with no second constant to keep in
|
|
854
|
+
* sync. Rendered as a muted footer line on the settings page so a user can
|
|
855
|
+
* report the exact build they run.
|
|
856
|
+
*
|
|
857
|
+
* @module dsh-commandcode-provider/client/version
|
|
858
|
+
*/
|
|
859
|
+
/** The published package version (e.g. `'0.6.0'`). */
|
|
860
|
+
const PLUGIN_VERSION = "0.6.1";
|
|
861
|
+
//#endregion
|
|
811
862
|
//#region src/client/section.tsx
|
|
812
863
|
/**
|
|
813
864
|
* React component for the "Command Code" settings page (browser half).
|
|
@@ -860,11 +911,17 @@ window.__ModuleLoader__.load({
|
|
|
860
911
|
}),
|
|
861
912
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
862
913
|
className: state.invalid ? "cc-invalid" : "cc-hint",
|
|
863
|
-
children: state.invalid ? t
|
|
914
|
+
children: state.invalid ? invalidCopy(state.invalidReason, t) : hint
|
|
864
915
|
})
|
|
865
916
|
]
|
|
866
917
|
});
|
|
867
918
|
}
|
|
919
|
+
/** The per-field error copy for a staged draft's failure reason. */
|
|
920
|
+
function invalidCopy(reason, t) {
|
|
921
|
+
if (reason === "tooSmall") return t("numberTooSmall");
|
|
922
|
+
if (reason === "tooLarge") return t("numberTooLarge");
|
|
923
|
+
return t("invalidNumber");
|
|
924
|
+
}
|
|
868
925
|
/**
|
|
869
926
|
* One boolean field row rendered as a toggle. The staged text is `'true'` /
|
|
870
927
|
* `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
|
|
@@ -910,8 +967,13 @@ window.__ModuleLoader__.load({
|
|
|
910
967
|
})]
|
|
911
968
|
});
|
|
912
969
|
}
|
|
913
|
-
/**
|
|
914
|
-
|
|
970
|
+
/**
|
|
971
|
+
* The API-key control: write-only, reports configured state, never echoes the
|
|
972
|
+
* key. The input is masked by default with a Show/Hide toggle so a pasted key
|
|
973
|
+
* can be spot-checked without leaving the field.
|
|
974
|
+
*/
|
|
975
|
+
function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, showLabel, hideLabel, onEdit }) {
|
|
976
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
915
977
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
916
978
|
className: "cc-field",
|
|
917
979
|
children: [
|
|
@@ -921,19 +983,26 @@ window.__ModuleLoader__.load({
|
|
|
921
983
|
className: "cc-label",
|
|
922
984
|
htmlFor: "cc-api-key",
|
|
923
985
|
children: label
|
|
924
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
986
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
925
987
|
className: "cc-badges",
|
|
926
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
988
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
927
989
|
className: configured ? "cc-badge" : "cc-badgeMuted",
|
|
928
990
|
children: configured ? configuredLabel : unconfiguredLabel
|
|
929
|
-
})
|
|
991
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
992
|
+
type: "button",
|
|
993
|
+
className: "cc-reset",
|
|
994
|
+
disabled,
|
|
995
|
+
onClick: () => setVisible((value) => !value),
|
|
996
|
+
children: visible ? hideLabel : showLabel
|
|
997
|
+
})]
|
|
930
998
|
})]
|
|
931
999
|
}),
|
|
932
1000
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
933
1001
|
id: "cc-api-key",
|
|
934
1002
|
className: "cc-input",
|
|
935
|
-
type: "password",
|
|
1003
|
+
type: visible ? "text" : "password",
|
|
936
1004
|
autoComplete: "off",
|
|
1005
|
+
spellCheck: false,
|
|
937
1006
|
value: state.text,
|
|
938
1007
|
disabled,
|
|
939
1008
|
onChange: (event) => onEdit(event.target.value)
|
|
@@ -1039,7 +1108,7 @@ window.__ModuleLoader__.load({
|
|
|
1039
1108
|
* One pool account's facts (identity, totals, credits, window limits)
|
|
1040
1109
|
* rendered inside the account-usage card.
|
|
1041
1110
|
*/
|
|
1042
|
-
function AccountReport({ entry, t }) {
|
|
1111
|
+
function AccountReport({ entry, t, onRemove }) {
|
|
1043
1112
|
const report = entry.report;
|
|
1044
1113
|
const account = report.account;
|
|
1045
1114
|
const accountName = account === void 0 ? "" : account.userName || account.name;
|
|
@@ -1072,6 +1141,13 @@ window.__ModuleLoader__.load({
|
|
|
1072
1141
|
planStatus !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1073
1142
|
className: "cc-usagePlanStatus",
|
|
1074
1143
|
children: planStatus
|
|
1144
|
+
}) : null,
|
|
1145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
|
|
1146
|
+
onRemove !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1147
|
+
type: "button",
|
|
1148
|
+
className: "cc-usageRefresh",
|
|
1149
|
+
onClick: onRemove,
|
|
1150
|
+
children: t("accountRemove")
|
|
1075
1151
|
}) : null
|
|
1076
1152
|
]
|
|
1077
1153
|
}),
|
|
@@ -1161,13 +1237,21 @@ window.__ModuleLoader__.load({
|
|
|
1161
1237
|
]
|
|
1162
1238
|
});
|
|
1163
1239
|
}
|
|
1240
|
+
/** The status dot on an account tab: cooling/invalid warn, everything else ok. */
|
|
1241
|
+
function AccountTabDot({ entry }) {
|
|
1242
|
+
const cls = entry.mark === "invalid-credential" ? "cc-tabDot cc-tabDotError" : entry.mark !== "" || entry.cooldownUntil > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk";
|
|
1243
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: cls });
|
|
1244
|
+
}
|
|
1164
1245
|
/**
|
|
1165
1246
|
* The account-usage card: the `/commandcode` dashboard's facts rendered as
|
|
1166
|
-
* a native settings card
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1247
|
+
* a native settings card. With several accounts the card is a carousel — a
|
|
1248
|
+
* tab strip (label + status dot) switches between accounts so the page stays
|
|
1249
|
+
* short; each account's report carries its own remove affordance (the
|
|
1250
|
+
* default account is not removable). Accounts staged for removal in the
|
|
1251
|
+
* management card are hidden here immediately. Data arrives through the
|
|
1252
|
+
* `commandcode/report` Remote; the API keys never leave the Host.
|
|
1169
1253
|
*/
|
|
1170
|
-
function UsageCard({ t, usage, apiKeyConfigured, onRefresh }) {
|
|
1254
|
+
function UsageCard({ t, usage, apiKeyConfigured, removingIds, removableIds, canManage, onRefresh, onRemoveAccount }) {
|
|
1171
1255
|
(0, react.useEffect)(() => {
|
|
1172
1256
|
if (apiKeyConfigured && usage.status === "idle") onRefresh();
|
|
1173
1257
|
}, [
|
|
@@ -1177,6 +1261,25 @@ window.__ModuleLoader__.load({
|
|
|
1177
1261
|
]);
|
|
1178
1262
|
const loading = usage.status === "loading";
|
|
1179
1263
|
const report = usage.report;
|
|
1264
|
+
const [locallyRemoved, setLocallyRemoved] = (0, react.useState)([]);
|
|
1265
|
+
(0, react.useEffect)(() => {
|
|
1266
|
+
setLocallyRemoved([]);
|
|
1267
|
+
}, [usage.fetchedAt]);
|
|
1268
|
+
const hidden = /* @__PURE__ */ new Set([...removingIds, ...locallyRemoved]);
|
|
1269
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1270
|
+
const entries = (report?.accounts ?? []).filter((entry) => {
|
|
1271
|
+
if (hidden.has(entry.id)) return false;
|
|
1272
|
+
if (seenIds.has(entry.id)) return false;
|
|
1273
|
+
seenIds.add(entry.id);
|
|
1274
|
+
return true;
|
|
1275
|
+
});
|
|
1276
|
+
const [selectedId, setSelectedId] = (0, react.useState)(void 0);
|
|
1277
|
+
const selected = entries.find((entry) => entry.id === selectedId) ?? entries.find((entry) => entry.active) ?? entries[0];
|
|
1278
|
+
const removeSelected = canManage && selected !== void 0 && removableIds.includes(selected.id) ? () => {
|
|
1279
|
+
const id = selected.id;
|
|
1280
|
+
setLocallyRemoved((prev) => [...prev, id]);
|
|
1281
|
+
onRemoveAccount(id);
|
|
1282
|
+
} : void 0;
|
|
1180
1283
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1181
1284
|
className: "cc-usageCard",
|
|
1182
1285
|
"aria-label": t("usageTitle"),
|
|
@@ -1211,10 +1314,24 @@ window.__ModuleLoader__.load({
|
|
|
1211
1314
|
role: "status",
|
|
1212
1315
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
|
|
1213
1316
|
}) : null,
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1317
|
+
entries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1318
|
+
className: "cc-tabs",
|
|
1319
|
+
role: "tablist",
|
|
1320
|
+
"aria-label": t("accountsTitle"),
|
|
1321
|
+
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1322
|
+
type: "button",
|
|
1323
|
+
role: "tab",
|
|
1324
|
+
"aria-selected": selected?.id === entry.id,
|
|
1325
|
+
className: selected?.id === entry.id ? "cc-tab cc-tabActive" : "cc-tab",
|
|
1326
|
+
onClick: () => setSelectedId(entry.id),
|
|
1327
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountTabDot, { entry }), entry.label]
|
|
1328
|
+
}, entry.id))
|
|
1329
|
+
}) : null,
|
|
1330
|
+
selected !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountReport, {
|
|
1331
|
+
entry: selected,
|
|
1332
|
+
t,
|
|
1333
|
+
onRemove: removeSelected
|
|
1334
|
+
}, selected.id) : null,
|
|
1218
1335
|
report !== void 0 && usage.fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1219
1336
|
className: "cc-usageMeta",
|
|
1220
1337
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
@@ -1229,9 +1346,16 @@ window.__ModuleLoader__.load({
|
|
|
1229
1346
|
]
|
|
1230
1347
|
});
|
|
1231
1348
|
}
|
|
1232
|
-
/**
|
|
1349
|
+
/**
|
|
1350
|
+
* One extra account row: label, key, configured badge. Saved accounts are
|
|
1351
|
+
* removed from the usage card above; a NOT-YET-SAVED addition never appears
|
|
1352
|
+
* there (the usage report is Host-side), so it keeps its own remove button —
|
|
1353
|
+
* otherwise the only way to undo a mistaken Add would be discarding every
|
|
1354
|
+
* other staged edit.
|
|
1355
|
+
*/
|
|
1233
1356
|
function AccountRow({ account, disabled, t, onLabel, onKey, onRemove }) {
|
|
1234
1357
|
const locked = !account.writable;
|
|
1358
|
+
const [keyVisible, setKeyVisible] = (0, react.useState)(false);
|
|
1235
1359
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1236
1360
|
className: "cc-field",
|
|
1237
1361
|
children: [
|
|
@@ -1253,12 +1377,19 @@ window.__ModuleLoader__.load({
|
|
|
1253
1377
|
children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
|
|
1254
1378
|
}),
|
|
1255
1379
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1380
|
+
type: "button",
|
|
1381
|
+
className: "cc-reset",
|
|
1382
|
+
disabled,
|
|
1383
|
+
onClick: () => setKeyVisible((value) => !value),
|
|
1384
|
+
children: keyVisible ? t("hide") : t("show")
|
|
1385
|
+
}),
|
|
1386
|
+
account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1256
1387
|
type: "button",
|
|
1257
1388
|
className: "cc-reset",
|
|
1258
1389
|
disabled,
|
|
1259
1390
|
onClick: onRemove,
|
|
1260
1391
|
children: t("accountRemove")
|
|
1261
|
-
})
|
|
1392
|
+
}) : null
|
|
1262
1393
|
]
|
|
1263
1394
|
})]
|
|
1264
1395
|
}),
|
|
@@ -1273,8 +1404,9 @@ window.__ModuleLoader__.load({
|
|
|
1273
1404
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1274
1405
|
id: `cc-account-key-${account.id}`,
|
|
1275
1406
|
className: "cc-input",
|
|
1276
|
-
type: "password",
|
|
1407
|
+
type: keyVisible ? "text" : "password",
|
|
1277
1408
|
autoComplete: "off",
|
|
1409
|
+
spellCheck: false,
|
|
1278
1410
|
placeholder: t("accountKey"),
|
|
1279
1411
|
value: account.keyText,
|
|
1280
1412
|
disabled: disabled || locked,
|
|
@@ -1287,7 +1419,7 @@ window.__ModuleLoader__.load({
|
|
|
1287
1419
|
]
|
|
1288
1420
|
});
|
|
1289
1421
|
}
|
|
1290
|
-
/** The multi-account card: extra accounts in rotation order + add button. */
|
|
1422
|
+
/** The multi-account card: the active-account selector + extra accounts in rotation order + add button. */
|
|
1291
1423
|
function AccountsCard({ t, state, disabled, onAdd, onRemove, onLabel, onKey, onActive, onActiveReset }) {
|
|
1292
1424
|
const active = state.activeAccount;
|
|
1293
1425
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1377,6 +1509,21 @@ window.__ModuleLoader__.load({
|
|
|
1377
1509
|
]
|
|
1378
1510
|
});
|
|
1379
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Show the "Saved ✓" affordance for a short window after each accepted save.
|
|
1514
|
+
* The controller only counts saves (`savedCount`); the flash timing lives
|
|
1515
|
+
* here so the state machine stays timer-free.
|
|
1516
|
+
*/
|
|
1517
|
+
function useSavedFlash(tick) {
|
|
1518
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
1519
|
+
(0, react.useEffect)(() => {
|
|
1520
|
+
if (tick === 0) return;
|
|
1521
|
+
setVisible(true);
|
|
1522
|
+
const timer = setTimeout(() => setVisible(false), 2500);
|
|
1523
|
+
return () => clearTimeout(timer);
|
|
1524
|
+
}, [tick]);
|
|
1525
|
+
return visible;
|
|
1526
|
+
}
|
|
1380
1527
|
/** The settings page body: connection facts for the Command Code provider. */
|
|
1381
1528
|
function CommandCodeSettingsPage(props) {
|
|
1382
1529
|
const { t } = props;
|
|
@@ -1384,6 +1531,7 @@ window.__ModuleLoader__.load({
|
|
|
1384
1531
|
const usage = props.useCommandCodeUsage((snapshot) => snapshot);
|
|
1385
1532
|
const disabled = !state.writable;
|
|
1386
1533
|
const keyLocked = !state.apiKeyWritable;
|
|
1534
|
+
const savedVisible = useSavedFlash(state.savedCount);
|
|
1387
1535
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1388
1536
|
className: "cc-section",
|
|
1389
1537
|
"aria-label": t("title"),
|
|
@@ -1405,7 +1553,11 @@ window.__ModuleLoader__.load({
|
|
|
1405
1553
|
t,
|
|
1406
1554
|
usage,
|
|
1407
1555
|
apiKeyConfigured: state.anyAccountConfigured,
|
|
1408
|
-
|
|
1556
|
+
removingIds: state.accountsRemoving,
|
|
1557
|
+
removableIds: state.accounts.map((account) => account.id),
|
|
1558
|
+
canManage: state.writable,
|
|
1559
|
+
onRefresh: props.refreshUsage,
|
|
1560
|
+
onRemoveAccount: props.removeAccount
|
|
1409
1561
|
}),
|
|
1410
1562
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
|
|
1411
1563
|
t,
|
|
@@ -1429,6 +1581,8 @@ window.__ModuleLoader__.load({
|
|
|
1429
1581
|
configured: state.apiKeyConfigured,
|
|
1430
1582
|
configuredLabel: t("apiKeySet"),
|
|
1431
1583
|
unconfiguredLabel: t("apiKeyUnset"),
|
|
1584
|
+
showLabel: t("show"),
|
|
1585
|
+
hideLabel: t("hide"),
|
|
1432
1586
|
onEdit: (text) => props.edit("apiKey", text)
|
|
1433
1587
|
}),
|
|
1434
1588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
@@ -1495,6 +1649,11 @@ window.__ModuleLoader__.load({
|
|
|
1495
1649
|
role: "status",
|
|
1496
1650
|
children: t("saveFailed")
|
|
1497
1651
|
}) : null,
|
|
1652
|
+
savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1653
|
+
className: "cc-saved",
|
|
1654
|
+
role: "status",
|
|
1655
|
+
children: t("saved")
|
|
1656
|
+
}) : null,
|
|
1498
1657
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1499
1658
|
variant: "ghost",
|
|
1500
1659
|
size: "sm",
|
|
@@ -1510,6 +1669,10 @@ window.__ModuleLoader__.load({
|
|
|
1510
1669
|
children: t(state.saving ? "saving" : "save")
|
|
1511
1670
|
})
|
|
1512
1671
|
]
|
|
1672
|
+
}),
|
|
1673
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1674
|
+
className: "cc-version",
|
|
1675
|
+
children: ["Command Code Provider v", PLUGIN_VERSION]
|
|
1513
1676
|
})
|
|
1514
1677
|
]
|
|
1515
1678
|
});
|
|
@@ -1549,13 +1712,18 @@ window.__ModuleLoader__.load({
|
|
|
1549
1712
|
overridden: "已覆盖",
|
|
1550
1713
|
reset: "重置",
|
|
1551
1714
|
invalidNumber: "无效数字",
|
|
1715
|
+
numberTooSmall: "不能小于 1(毫秒)",
|
|
1716
|
+
numberTooLarge: "超出允许上限(2147483647 毫秒)",
|
|
1552
1717
|
readOnly: "当前配置为只读。",
|
|
1553
1718
|
unsaved: "未保存",
|
|
1554
1719
|
save: "保存",
|
|
1555
1720
|
saving: "保存中",
|
|
1721
|
+
saved: "已保存 ✓",
|
|
1556
1722
|
saveFailed: "保存失败,请重试。",
|
|
1557
1723
|
discard: "放弃",
|
|
1558
1724
|
cancel: "取消",
|
|
1725
|
+
show: "显示",
|
|
1726
|
+
hide: "隐藏",
|
|
1559
1727
|
usageTitle: "账户用量",
|
|
1560
1728
|
usageRefresh: "刷新",
|
|
1561
1729
|
usageRefreshing: "刷新中…",
|
|
@@ -1617,13 +1785,18 @@ window.__ModuleLoader__.load({
|
|
|
1617
1785
|
overridden: "Overridden",
|
|
1618
1786
|
reset: "Reset",
|
|
1619
1787
|
invalidNumber: "Invalid number",
|
|
1788
|
+
numberTooSmall: "Must be at least 1 (ms)",
|
|
1789
|
+
numberTooLarge: "Above the allowed maximum (2147483647 ms)",
|
|
1620
1790
|
readOnly: "Settings are read-only.",
|
|
1621
1791
|
unsaved: "Unsaved",
|
|
1622
1792
|
save: "Save",
|
|
1623
1793
|
saving: "Saving",
|
|
1794
|
+
saved: "Saved ✓",
|
|
1624
1795
|
saveFailed: "Save failed, please retry.",
|
|
1625
1796
|
discard: "Discard",
|
|
1626
1797
|
cancel: "Cancel",
|
|
1798
|
+
show: "Show",
|
|
1799
|
+
hide: "Hide",
|
|
1627
1800
|
usageTitle: "Account usage",
|
|
1628
1801
|
usageRefresh: "Refresh",
|
|
1629
1802
|
usageRefreshing: "Refreshing…",
|
|
@@ -1713,11 +1886,19 @@ window.__ModuleLoader__.load({
|
|
|
1713
1886
|
.cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
|
|
1714
1887
|
.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1715
1888
|
.cc-usageMeta{align-items:center;gap:8px;display:flex}
|
|
1716
|
-
.cc-accountReport{
|
|
1717
|
-
.cc-
|
|
1889
|
+
.cc-accountReport{flex-direction:column;gap:12px;display:flex}
|
|
1890
|
+
.cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
|
|
1891
|
+
.cc-tab{align-items:center;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex;gap:6px}
|
|
1892
|
+
.cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
|
|
1893
|
+
.cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
1894
|
+
.cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
|
|
1895
|
+
.cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
|
|
1896
|
+
.cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
|
|
1718
1897
|
.cc-usageMetaSpacer{flex:1}
|
|
1719
1898
|
.cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
|
|
1720
1899
|
.cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
|
|
1900
|
+
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
1901
|
+
.cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
|
|
1721
1902
|
`;
|
|
1722
1903
|
/** Inject the page stylesheet once (idempotent per tag). */
|
|
1723
1904
|
function injectPageCss() {
|