@mars-sea/dsh-commandcode-provider 0.6.0 → 0.6.2

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/lib/client.js CHANGED
@@ -59,8 +59,15 @@ window.__ModuleLoader__.load({
59
59
  }
60
60
  };
61
61
  }
62
- /** A whole-number field; an empty draft clears it, anything non-numeric blocks save. */
63
- function numberField$1(field) {
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
- return Number.isFinite(parsed) ? {
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
- } : { kind: "invalid" };
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 { kind: "invalid" };
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
- numberField$1("streamIdleTimeoutMs"),
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
  ];
@@ -134,8 +163,11 @@ window.__ModuleLoader__.load({
134
163
  labelDrafts = /* @__PURE__ */ new Map();
135
164
  /** Staged key drafts, by credential reference (blank = keep stored key). */
136
165
  keyDrafts = /* @__PURE__ */ new Map();
166
+ /** Credential references staged for removal on the next save. */
167
+ keyClears = /* @__PURE__ */ new Set();
137
168
  saving = false;
138
169
  failed = false;
170
+ savedCount = 0;
139
171
  /**
140
172
  * @param scope - bound scope for the `llm-commandcode` namespace.
141
173
  * @param api - credentials wire face.
@@ -206,8 +238,10 @@ window.__ModuleLoader__.load({
206
238
  text: this.staged.get("apiKey")?.text ?? "",
207
239
  clear: false,
208
240
  overridden: false,
209
- invalid: false
241
+ invalid: false,
242
+ invalidReason: void 0
210
243
  },
244
+ apiKeyClearStaged: this.keyClears.has(this.credentialRef),
211
245
  apiBase: this.field("apiBase"),
212
246
  workingDir: this.field("workingDir"),
213
247
  defaultWorkingDir: this.defaultWorkingDir,
@@ -220,7 +254,8 @@ window.__ModuleLoader__.load({
220
254
  dirty: plan.length > 0 || this.accountsDirty(),
221
255
  invalid: plan.some((item) => item.run === void 0),
222
256
  saving: this.saving,
223
- failed: this.failed
257
+ failed: this.failed,
258
+ savedCount: this.savedCount
224
259
  };
225
260
  }
226
261
  /** Stage a new extra account (saved on the next `save()`). */
@@ -265,15 +300,37 @@ window.__ModuleLoader__.load({
265
300
  /** Stage one extra account's key draft (blank keeps the stored key). */
266
301
  editAccountKey(id, text) {
267
302
  this.keyDrafts.set(id, text);
303
+ this.keyClears.delete(id);
268
304
  this.failed = false;
269
305
  this.publish();
270
306
  }
307
+ /**
308
+ * Toggle the staged removal of one account's stored key: the next save
309
+ * unsets the credential so the account reports unconfigured and falls back
310
+ * to its other key sources. Only meaningful while a key is actually
311
+ * stored. `target` is `'default'` (the implicit first account) or an extra
312
+ * account's credential reference.
313
+ */
314
+ toggleKeyClear(target) {
315
+ const ref = target === "default" ? this.credentialRef : target;
316
+ if (this.keyClears.has(ref)) this.keyClears.delete(ref);
317
+ else {
318
+ if (this.credentialStates.get(ref)?.configured !== true) return;
319
+ this.keyDrafts.delete(ref);
320
+ if (ref === this.credentialRef) this.staged.delete("apiKey");
321
+ this.keyClears.add(ref);
322
+ }
323
+ this.failed = false;
324
+ this.describeAll();
325
+ this.publish();
326
+ }
271
327
  /** Stage one field's draft text. */
272
328
  edit(field, text) {
273
329
  this.staged.set(field, {
274
330
  text,
275
331
  clear: false
276
332
  });
333
+ if (field === "apiKey") this.keyClears.delete(this.credentialRef);
277
334
  this.failed = false;
278
335
  this.publish();
279
336
  }
@@ -322,6 +379,7 @@ window.__ModuleLoader__.load({
322
379
  this.saving = false;
323
380
  this.failed = !landed;
324
381
  if (landed) {
382
+ this.savedCount += 1;
325
383
  this.staged.clear();
326
384
  this.clearAccountStaging();
327
385
  } else this.reconcileAccountStaging();
@@ -342,6 +400,7 @@ window.__ModuleLoader__.load({
342
400
  const storedLabel = this.storedExtras().find((extra) => extra.ref === ref)?.label;
343
401
  if (storedLabel === void 0 || storedLabel === text.trim()) this.labelDrafts.delete(ref);
344
402
  }
403
+ for (const ref of [...this.keyClears]) if (this.credentialStates.get(ref)?.configured !== true) this.keyClears.delete(ref);
345
404
  }
346
405
  spec(field) {
347
406
  const spec = this.specs.get(field);
@@ -356,14 +415,16 @@ window.__ModuleLoader__.load({
356
415
  text: spec.format(this.sectionValue(field)),
357
416
  clear: false,
358
417
  overridden: this.stored(field),
359
- invalid: false
418
+ invalid: false,
419
+ invalidReason: void 0
360
420
  };
361
421
  const parsed = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
362
422
  return {
363
423
  text: staged.text,
364
424
  clear: staged.clear,
365
425
  overridden: parsed.kind === "set",
366
- invalid: parsed.kind === "invalid"
426
+ invalid: parsed.kind === "invalid",
427
+ invalidReason: parsed.kind === "invalid" ? parsed.reason : void 0
367
428
  };
368
429
  }
369
430
  sectionValue(field) {
@@ -510,12 +571,13 @@ window.__ModuleLoader__.load({
510
571
  keyText: this.keyDrafts.get(extra.ref) ?? "",
511
572
  configured: this.credentialStates.get(extra.ref)?.configured ?? false,
512
573
  writable: this.credentialStates.get(extra.ref)?.writable ?? true,
513
- added: extra.added
574
+ added: extra.added,
575
+ clearStaged: this.keyClears.has(extra.ref)
514
576
  }));
515
577
  }
516
- /** Whether any account-level staging (add/remove/label/key) exists. */
578
+ /** Whether any account-level staging (add/remove/label/key/clear) exists. */
517
579
  accountsStaged() {
518
- return this.addedAccounts.length > 0 || this.removedRefs.size > 0 || this.labelDrafts.size > 0 || this.keyDrafts.size > 0;
580
+ return this.addedAccounts.length > 0 || this.removedRefs.size > 0 || this.labelDrafts.size > 0 || this.keyDrafts.size > 0 || this.keyClears.size > 0;
519
581
  }
520
582
  /** Whether the staged account edits differ from the stored section. */
521
583
  accountsDirty() {
@@ -525,6 +587,7 @@ window.__ModuleLoader__.load({
525
587
  if (base !== void 0 && text.trim() !== "" && text !== base) return true;
526
588
  }
527
589
  for (const text of this.keyDrafts.values()) if (text.trim() !== "") return true;
590
+ for (const ref of this.keyClears) if (this.credentialStates.get(ref)?.configured === true) return true;
528
591
  return false;
529
592
  }
530
593
  /** Reset every account-level staged edit. */
@@ -533,14 +596,26 @@ window.__ModuleLoader__.load({
533
596
  this.removedRefs.clear();
534
597
  this.labelDrafts.clear();
535
598
  this.keyDrafts.clear();
599
+ this.keyClears.clear();
600
+ }
601
+ /** Unset one stored credential, then re-read the Host's credential states. */
602
+ async unsetKey(ref) {
603
+ try {
604
+ if (!(await this.api.credentials.unset({ ref })).result.ok) return false;
605
+ } catch {
606
+ return false;
607
+ }
608
+ await this.describeAll();
609
+ return this.credentialStates.get(ref)?.configured !== true;
536
610
  }
537
611
  /** The account-level writes a save performs (empty when nothing staged). */
538
612
  accountPlan() {
539
613
  if (!this.accountsDirty()) return [];
540
614
  const runs = [];
615
+ for (const ref of this.keyClears) if (this.credentialStates.get(ref)?.configured === true) runs.push(() => this.unsetKey(ref));
541
616
  for (const [ref, text] of this.keyDrafts) {
542
617
  const value = text.trim();
543
- if (value !== "" && !this.removedRefs.has(ref)) runs.push(() => this.writeKeyTo(ref, value));
618
+ if (value !== "" && !this.removedRefs.has(ref) && !this.keyClears.has(ref)) runs.push(() => this.writeKeyTo(ref, value));
544
619
  }
545
620
  runs.push(() => this.writeAccounts());
546
621
  return runs;
@@ -726,6 +801,11 @@ window.__ModuleLoader__.load({
726
801
  const failures = source.failures;
727
802
  if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject("failures");
728
803
  const report = { failures };
804
+ if (source.blocked !== void 0) {
805
+ const blocked = source.blocked;
806
+ if (blocked !== "invalid-key" && blocked !== "service-unavailable" && blocked !== "network") reject("blocked");
807
+ report.blocked = blocked;
808
+ }
729
809
  if (source.account !== void 0) {
730
810
  const account = record(source.account, "account");
731
811
  report.account = {
@@ -809,6 +889,21 @@ window.__ModuleLoader__.load({
809
889
  }]
810
890
  };
811
891
  //#endregion
892
+ //#region src/client/version.ts
893
+ /**
894
+ * The plugin's own version, read from package.json at build time.
895
+ *
896
+ * The client bundle inlines the JSON import (rolldown resolves it during the
897
+ * tsdown build; node tests read it through tsx), so the rendered value always
898
+ * matches the published package version with no second constant to keep in
899
+ * sync. Rendered as a muted footer line on the settings page so a user can
900
+ * report the exact build they run.
901
+ *
902
+ * @module dsh-commandcode-provider/client/version
903
+ */
904
+ /** The published package version (e.g. `'0.6.0'`). */
905
+ const PLUGIN_VERSION = "0.6.2";
906
+ //#endregion
812
907
  //#region src/client/section.tsx
813
908
  /**
814
909
  * React component for the "Command Code" settings page (browser half).
@@ -861,11 +956,17 @@ window.__ModuleLoader__.load({
861
956
  }),
862
957
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
863
958
  className: state.invalid ? "cc-invalid" : "cc-hint",
864
- children: state.invalid ? t("invalidNumber") : hint
959
+ children: state.invalid ? invalidCopy(state.invalidReason, t) : hint
865
960
  })
866
961
  ]
867
962
  });
868
963
  }
964
+ /** The per-field error copy for a staged draft's failure reason. */
965
+ function invalidCopy(reason, t) {
966
+ if (reason === "tooSmall") return t("numberTooSmall");
967
+ if (reason === "tooLarge") return t("numberTooLarge");
968
+ return t("invalidNumber");
969
+ }
869
970
  /**
870
971
  * One boolean field row rendered as a toggle. The staged text is `'true'` /
871
972
  * `'false'` / `''` (unset → `defaultChecked`); toggling stages the string the
@@ -911,8 +1012,14 @@ window.__ModuleLoader__.load({
911
1012
  })]
912
1013
  });
913
1014
  }
914
- /** The API-key control: write-only, reports configured state, never echoes the key. */
915
- function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, onEdit }) {
1015
+ /**
1016
+ * The API-key control: write-only, reports configured state, never echoes the
1017
+ * key. The input is masked by default with a Show/Hide toggle so a pasted key
1018
+ * can be spot-checked without leaving the field, and a stored key can be
1019
+ * staged for removal (the next save unsets it) when it is bad or unwanted.
1020
+ */
1021
+ function SecretKeyField({ label, hint, state, disabled, configured, configuredLabel, unconfiguredLabel, clearStaged, showLabel, hideLabel, clearLabel, clearStagedLabel, undoClearLabel, onEdit, onToggleClear }) {
1022
+ const [visible, setVisible] = (0, react.useState)(false);
916
1023
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
917
1024
  className: "cc-field",
918
1025
  children: [
@@ -922,19 +1029,40 @@ window.__ModuleLoader__.load({
922
1029
  className: "cc-label",
923
1030
  htmlFor: "cc-api-key",
924
1031
  children: label
925
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1032
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
926
1033
  className: "cc-badges",
927
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
928
- className: configured ? "cc-badge" : "cc-badgeMuted",
929
- children: configured ? configuredLabel : unconfiguredLabel
930
- })
1034
+ children: [
1035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1036
+ className: configured ? "cc-badge" : "cc-badgeMuted",
1037
+ children: configured ? configuredLabel : unconfiguredLabel
1038
+ }),
1039
+ clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1040
+ className: "cc-badge cc-badgeWarn",
1041
+ children: clearStagedLabel
1042
+ }) : null,
1043
+ configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1044
+ type: "button",
1045
+ className: "cc-reset",
1046
+ disabled,
1047
+ onClick: onToggleClear,
1048
+ children: clearStaged ? undoClearLabel : clearLabel
1049
+ }) : null,
1050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1051
+ type: "button",
1052
+ className: "cc-reset",
1053
+ disabled,
1054
+ onClick: () => setVisible((value) => !value),
1055
+ children: visible ? hideLabel : showLabel
1056
+ })
1057
+ ]
931
1058
  })]
932
1059
  }),
933
1060
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
934
1061
  id: "cc-api-key",
935
1062
  className: "cc-input",
936
- type: "password",
1063
+ type: visible ? "text" : "password",
937
1064
  autoComplete: "off",
1065
+ spellCheck: false,
938
1066
  value: state.text,
939
1067
  disabled,
940
1068
  onChange: (event) => onEdit(event.target.value)
@@ -1087,6 +1215,17 @@ window.__ModuleLoader__.load({
1087
1215
  className: "cc-usageHint",
1088
1216
  children: t("usageUnconfigured")
1089
1217
  }) : null,
1218
+ report.blocked !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1219
+ className: "cc-usageBlocked",
1220
+ role: "alert",
1221
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1222
+ className: "cc-usageBlockedTitle",
1223
+ children: blockedTitle(report.blocked, t)
1224
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1225
+ className: "cc-usageBlockedHint",
1226
+ children: blockedHint(report.blocked, t)
1227
+ })]
1228
+ }) : null,
1090
1229
  report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1091
1230
  className: "cc-usageStats",
1092
1231
  children: [
@@ -1152,13 +1291,13 @@ window.__ModuleLoader__.load({
1152
1291
  ]
1153
1292
  }),
1154
1293
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
1155
- report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1294
+ report.failures.length > 0 && report.blocked === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1156
1295
  className: "cc-usagePartial",
1157
1296
  title: report.failures.join("; "),
1158
1297
  children: t("usagePartial")
1159
1298
  }) : null
1160
1299
  ]
1161
- }) : report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1300
+ }) : report.failures.length > 0 && report.blocked === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1162
1301
  className: "cc-usageMeta",
1163
1302
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1164
1303
  className: "cc-usagePartial",
@@ -1169,6 +1308,18 @@ window.__ModuleLoader__.load({
1169
1308
  ]
1170
1309
  });
1171
1310
  }
1311
+ /** The headline copy for a report whose every endpoint failed the same way. */
1312
+ function blockedTitle(reason, t) {
1313
+ if (reason === "invalid-key") return t("usageKeyInvalid");
1314
+ if (reason === "service-unavailable") return t("usageServiceUnavailable");
1315
+ return t("usageNetworkError");
1316
+ }
1317
+ /** The actionable hint under a blocked report's headline. */
1318
+ function blockedHint(reason, t) {
1319
+ if (reason === "invalid-key") return t("usageKeyInvalidHint");
1320
+ if (reason === "service-unavailable") return t("usageServiceUnavailableHint");
1321
+ return t("usageNetworkHint");
1322
+ }
1172
1323
  /** The status dot on an account tab: cooling/invalid warn, everything else ok. */
1173
1324
  function AccountTabDot({ entry }) {
1174
1325
  const cls = entry.mark === "invalid-credential" ? "cc-tabDot cc-tabDotError" : entry.mark !== "" || entry.cooldownUntil > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk";
@@ -1285,8 +1436,9 @@ window.__ModuleLoader__.load({
1285
1436
  * otherwise the only way to undo a mistaken Add would be discarding every
1286
1437
  * other staged edit.
1287
1438
  */
1288
- function AccountRow({ account, disabled, t, onLabel, onKey, onRemove }) {
1439
+ function AccountRow({ account, disabled, t, onLabel, onKey, onToggleClear, onRemove }) {
1289
1440
  const locked = !account.writable;
1441
+ const [keyVisible, setKeyVisible] = (0, react.useState)(false);
1290
1442
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1291
1443
  className: "cc-field",
1292
1444
  children: [
@@ -1307,6 +1459,31 @@ window.__ModuleLoader__.load({
1307
1459
  className: account.configured ? "cc-badge" : "cc-badgeMuted",
1308
1460
  children: account.configured ? t("apiKeySet") : t("apiKeyUnset")
1309
1461
  }),
1462
+ account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1463
+ className: "cc-badge cc-badgeWarn",
1464
+ children: t("usageKeyClearStaged")
1465
+ }) : null,
1466
+ account.configured && !account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1467
+ type: "button",
1468
+ className: "cc-reset",
1469
+ disabled,
1470
+ onClick: onToggleClear,
1471
+ children: t("usageKeyClear")
1472
+ }) : null,
1473
+ account.clearStaged ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1474
+ type: "button",
1475
+ className: "cc-reset",
1476
+ disabled,
1477
+ onClick: onToggleClear,
1478
+ children: t("usageUndoKeyClear")
1479
+ }) : null,
1480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1481
+ type: "button",
1482
+ className: "cc-reset",
1483
+ disabled,
1484
+ onClick: () => setKeyVisible((value) => !value),
1485
+ children: keyVisible ? t("hide") : t("show")
1486
+ }),
1310
1487
  account.added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1311
1488
  type: "button",
1312
1489
  className: "cc-reset",
@@ -1328,8 +1505,9 @@ window.__ModuleLoader__.load({
1328
1505
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1329
1506
  id: `cc-account-key-${account.id}`,
1330
1507
  className: "cc-input",
1331
- type: "password",
1508
+ type: keyVisible ? "text" : "password",
1332
1509
  autoComplete: "off",
1510
+ spellCheck: false,
1333
1511
  placeholder: t("accountKey"),
1334
1512
  value: account.keyText,
1335
1513
  disabled: disabled || locked,
@@ -1343,7 +1521,7 @@ window.__ModuleLoader__.load({
1343
1521
  });
1344
1522
  }
1345
1523
  /** The multi-account card: the active-account selector + extra accounts in rotation order + add button. */
1346
- function AccountsCard({ t, state, disabled, onAdd, onRemove, onLabel, onKey, onActive, onActiveReset }) {
1524
+ function AccountsCard({ t, state, disabled, onAdd, onRemove, onLabel, onKey, onToggleClear, onActive, onActiveReset }) {
1347
1525
  const active = state.activeAccount;
1348
1526
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1349
1527
  className: "cc-card",
@@ -1427,11 +1605,27 @@ window.__ModuleLoader__.load({
1427
1605
  t,
1428
1606
  onLabel: (text) => onLabel(account.id, text),
1429
1607
  onKey: (text) => onKey(account.id, text),
1608
+ onToggleClear: () => onToggleClear(account.id),
1430
1609
  onRemove: () => onRemove(account.id)
1431
1610
  }, account.id))
1432
1611
  ]
1433
1612
  });
1434
1613
  }
1614
+ /**
1615
+ * Show the "Saved ✓" affordance for a short window after each accepted save.
1616
+ * The controller only counts saves (`savedCount`); the flash timing lives
1617
+ * here so the state machine stays timer-free.
1618
+ */
1619
+ function useSavedFlash(tick) {
1620
+ const [visible, setVisible] = (0, react.useState)(false);
1621
+ (0, react.useEffect)(() => {
1622
+ if (tick === 0) return;
1623
+ setVisible(true);
1624
+ const timer = setTimeout(() => setVisible(false), 2500);
1625
+ return () => clearTimeout(timer);
1626
+ }, [tick]);
1627
+ return visible;
1628
+ }
1435
1629
  /** The settings page body: connection facts for the Command Code provider. */
1436
1630
  function CommandCodeSettingsPage(props) {
1437
1631
  const { t } = props;
@@ -1439,6 +1633,7 @@ window.__ModuleLoader__.load({
1439
1633
  const usage = props.useCommandCodeUsage((snapshot) => snapshot);
1440
1634
  const disabled = !state.writable;
1441
1635
  const keyLocked = !state.apiKeyWritable;
1636
+ const savedVisible = useSavedFlash(state.savedCount);
1442
1637
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1443
1638
  className: "cc-section",
1444
1639
  "aria-label": t("title"),
@@ -1474,6 +1669,7 @@ window.__ModuleLoader__.load({
1474
1669
  onRemove: props.removeAccount,
1475
1670
  onLabel: props.editAccountLabel,
1476
1671
  onKey: props.editAccountKey,
1672
+ onToggleClear: (id) => props.toggleKeyClear(id),
1477
1673
  onActive: (text) => props.edit("activeAccount", text),
1478
1674
  onActiveReset: () => props.resetField("activeAccount")
1479
1675
  }),
@@ -1488,7 +1684,14 @@ window.__ModuleLoader__.load({
1488
1684
  configured: state.apiKeyConfigured,
1489
1685
  configuredLabel: t("apiKeySet"),
1490
1686
  unconfiguredLabel: t("apiKeyUnset"),
1491
- onEdit: (text) => props.edit("apiKey", text)
1687
+ clearStaged: state.apiKeyClearStaged,
1688
+ showLabel: t("show"),
1689
+ hideLabel: t("hide"),
1690
+ clearLabel: t("usageKeyClear"),
1691
+ clearStagedLabel: t("usageKeyClearStaged"),
1692
+ undoClearLabel: t("usageUndoKeyClear"),
1693
+ onEdit: (text) => props.edit("apiKey", text),
1694
+ onToggleClear: () => props.toggleKeyClear("default")
1492
1695
  }),
1493
1696
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1494
1697
  id: "cc-api-base",
@@ -1554,6 +1757,11 @@ window.__ModuleLoader__.load({
1554
1757
  role: "status",
1555
1758
  children: t("saveFailed")
1556
1759
  }) : null,
1760
+ savedVisible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1761
+ className: "cc-saved",
1762
+ role: "status",
1763
+ children: t("saved")
1764
+ }) : null,
1557
1765
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1558
1766
  variant: "ghost",
1559
1767
  size: "sm",
@@ -1569,6 +1777,10 @@ window.__ModuleLoader__.load({
1569
1777
  children: t(state.saving ? "saving" : "save")
1570
1778
  })
1571
1779
  ]
1780
+ }),
1781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1782
+ className: "cc-version",
1783
+ children: ["Command Code Provider v", PLUGIN_VERSION]
1572
1784
  })
1573
1785
  ]
1574
1786
  });
@@ -1608,13 +1820,18 @@ window.__ModuleLoader__.load({
1608
1820
  overridden: "已覆盖",
1609
1821
  reset: "重置",
1610
1822
  invalidNumber: "无效数字",
1823
+ numberTooSmall: "不能小于 1(毫秒)",
1824
+ numberTooLarge: "超出允许上限(2147483647 毫秒)",
1611
1825
  readOnly: "当前配置为只读。",
1612
1826
  unsaved: "未保存",
1613
1827
  save: "保存",
1614
1828
  saving: "保存中",
1829
+ saved: "已保存 ✓",
1615
1830
  saveFailed: "保存失败,请重试。",
1616
1831
  discard: "放弃",
1617
1832
  cancel: "取消",
1833
+ show: "显示",
1834
+ hide: "隐藏",
1618
1835
  usageTitle: "账户用量",
1619
1836
  usageRefresh: "刷新",
1620
1837
  usageRefreshing: "刷新中…",
@@ -1636,6 +1853,15 @@ window.__ModuleLoader__.load({
1636
1853
  usageExceeded: "已超限",
1637
1854
  usageReset: "重置于",
1638
1855
  usagePartial: "部分端点数据不可用",
1856
+ usageKeyClear: "清除已存密钥",
1857
+ usageKeyClearStaged: "将清除(保存后生效)",
1858
+ usageUndoKeyClear: "撤销清除",
1859
+ usageKeyInvalid: "API 密钥无效或已过期",
1860
+ usageKeyInvalidHint: "服务端拒绝了全部请求(401)。请检查该账户配置的密钥,或到 commandcode.ai 控制台重新生成。",
1861
+ usageServiceUnavailable: "Command Code 服务暂时不可用",
1862
+ usageServiceUnavailableHint: "服务端返回了错误(5xx),稍后点击刷新重试。",
1863
+ usageNetworkError: "无法连接 Command Code 服务",
1864
+ usageNetworkHint: "所有请求都没有到达服务端。请检查网络连接或 API 地址设置。",
1639
1865
  usageUpdated: "更新于",
1640
1866
  usagePeriodEnd: "账期截止",
1641
1867
  usageActive: "当前使用",
@@ -1676,13 +1902,18 @@ window.__ModuleLoader__.load({
1676
1902
  overridden: "Overridden",
1677
1903
  reset: "Reset",
1678
1904
  invalidNumber: "Invalid number",
1905
+ numberTooSmall: "Must be at least 1 (ms)",
1906
+ numberTooLarge: "Above the allowed maximum (2147483647 ms)",
1679
1907
  readOnly: "Settings are read-only.",
1680
1908
  unsaved: "Unsaved",
1681
1909
  save: "Save",
1682
1910
  saving: "Saving",
1911
+ saved: "Saved ✓",
1683
1912
  saveFailed: "Save failed, please retry.",
1684
1913
  discard: "Discard",
1685
1914
  cancel: "Cancel",
1915
+ show: "Show",
1916
+ hide: "Hide",
1686
1917
  usageTitle: "Account usage",
1687
1918
  usageRefresh: "Refresh",
1688
1919
  usageRefreshing: "Refreshing…",
@@ -1704,6 +1935,15 @@ window.__ModuleLoader__.load({
1704
1935
  usageExceeded: "Exceeded",
1705
1936
  usageReset: "Resets",
1706
1937
  usagePartial: "Some endpoint data unavailable",
1938
+ usageKeyClear: "Clear stored key",
1939
+ usageKeyClearStaged: "Will be cleared on save",
1940
+ usageUndoKeyClear: "Undo clear",
1941
+ usageKeyInvalid: "API key invalid or expired",
1942
+ usageKeyInvalidHint: "The server rejects every request (401). Check the key configured for this account, or generate a new one in the commandcode.ai console.",
1943
+ usageServiceUnavailable: "The Command Code service is temporarily unavailable",
1944
+ usageServiceUnavailableHint: "The server returned errors (5xx); try Refresh again later.",
1945
+ usageNetworkError: "Could not reach the Command Code service",
1946
+ usageNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
1707
1947
  usageUpdated: "Updated",
1708
1948
  usagePeriodEnd: "Period ends",
1709
1949
  usageActive: "Active",
@@ -1783,6 +2023,12 @@ window.__ModuleLoader__.load({
1783
2023
  .cc-usageMetaSpacer{flex:1}
1784
2024
  .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
1785
2025
  .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
2026
+ .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
2027
+ .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
2028
+ .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
2029
+ .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
2030
+ .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}
2031
+ .cc-badgeWarn{background:var(--dsw-alias-state-warning-secondary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary))}
1786
2032
  `;
1787
2033
  /** Inject the page stylesheet once (idempotent per tag). */
1788
2034
  function injectPageCss() {
@@ -1868,7 +2114,8 @@ window.__ModuleLoader__.load({
1868
2114
  addAccount: () => controller.addAccount(),
1869
2115
  removeAccount: (id) => controller.removeAccount(id),
1870
2116
  editAccountLabel: (id, text) => controller.editAccountLabel(id, text),
1871
- editAccountKey: (id, text) => controller.editAccountKey(id, text)
2117
+ editAccountKey: (id, text) => controller.editAccountKey(id, text),
2118
+ toggleKeyClear: (id) => controller.toggleKeyClear(id)
1872
2119
  });
1873
2120
  ctx.slots.inject("settings.section", () => ctx.slots.register({
1874
2121
  name: "settings.section",