dsh-keys-manager 0.0.0 → 0.2.0

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 ADDED
@@ -0,0 +1,2106 @@
1
+ /* Keys Manager Client bundle; generated by scripts/build-client.mjs. */
2
+ window.__ModuleLoader__.load({ id: "dsh-keys-manager", factory: (require) => {
3
+ var module = { exports: {} }; var exports = module.exports;
4
+ "use strict";
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/client/index.ts
34
+ var index_exports = {};
35
+ __export(index_exports, {
36
+ apply: () => apply,
37
+ inject: () => inject
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/client/locales.ts
42
+ var zh = {
43
+ nav: "\u5BC6\u94A5\u7BA1\u7406",
44
+ title: "\u5BC6\u94A5\u7BA1\u7406",
45
+ subtitle: "\u4E3A\u540C\u4E00\u4E2A Provider \u914D\u7F6E\u591A\u4E2A API Key\u3002\u5355\u4F1A\u8BDD\u56FA\u5B9A\u4F7F\u7528\u4E00\u4E2A Key\uFF0C\u65B0\u4F1A\u8BDD\u8F6E\u6362\uFF1B\u5931\u8D25\u540E\u81EA\u52A8\u51B7\u5374\u5E76\u6062\u590D\u3002",
46
+ search: "\u641C\u7D22 Provider\uFF08\u540D\u79F0\u3001ID \u6216\u73AF\u5883\u53D8\u91CF\uFF09",
47
+ managed: "\u5DF2\u63A5\u7BA1",
48
+ available: "\u53EF\u6DFB\u52A0 Providers",
49
+ managedEmpty: "\u8FD8\u6CA1\u6709\u63A5\u7BA1\u7684 Provider\u3002\u4ECE\u4E0B\u65B9\u9009\u62E9\u4E00\u4E2A\u5F00\u59CB\u3002",
50
+ availableEmpty: "\u6CA1\u6709\u5339\u914D\u7684 Provider\u3002",
51
+ keys: "\u5BC6\u94A5",
52
+ healthy: "\u53EF\u7528",
53
+ cooling: "\u51B7\u5374\u4E2D",
54
+ paused: "\u5DF2\u6682\u505C",
55
+ missing: "\u51ED\u8BC1\u7F3A\u5931",
56
+ active: "\u6B63\u5E38",
57
+ unmanaged: "\u672A\u63A5\u7BA1",
58
+ dshBuiltin: "DSH \u5185\u7F6E",
59
+ pluginBuiltin: "\u63D2\u4EF6\u5185\u7F6E",
60
+ customProvider: "\u81EA\u5B9A\u4E49",
61
+ configured: "\u5DF2\u914D\u7F6E",
62
+ notConfigured: "\u672A\u914D\u7F6E",
63
+ adopt: "\u63A5\u7BA1 Provider",
64
+ adopting: "\u63A5\u7BA1\u4E2D\u2026",
65
+ release: "\u505C\u6B62\u63A5\u7BA1",
66
+ releasing: "\u6B63\u5728\u91CA\u653E\u2026",
67
+ addKey: "\u6DFB\u52A0\u5BC6\u94A5",
68
+ addKeyTitle: "\u6DFB\u52A0 API Key",
69
+ alias: "\u522B\u540D",
70
+ aliasPlaceholder: "\u4F8B\u5982\uFF1A\u4E3B\u7528\u3001\u5907\u7528\u3001Team B",
71
+ secret: "API Key",
72
+ secretPlaceholder: "\u7C98\u8D34 Key\uFF1B\u5B83\u5C06\u4FDD\u5B58\u5728\u51ED\u8BC1\u5E93\u4E2D",
73
+ addKeyHint: "Key \u4F1A\u4FDD\u5B58\u5728 DSH \u51ED\u8BC1\u5E93\u4E2D\uFF0C\u4E4B\u540E\u4E0D\u4F1A\u518D\u663E\u793A\u660E\u6587\u3002",
74
+ show: "\u663E\u793A",
75
+ hide: "\u9690\u85CF",
76
+ save: "\u4FDD\u5B58",
77
+ saving: "\u4FDD\u5B58\u4E2D\u2026",
78
+ cancel: "\u53D6\u6D88",
79
+ rename: "\u6539\u522B\u540D",
80
+ pause: "\u6682\u505C",
81
+ resume: "\u6062\u590D",
82
+ delete: "\u5220\u9664",
83
+ deleting: "\u5220\u9664\u4E2D\u2026",
84
+ deleteTitle: "\u5220\u9664\u5BC6\u94A5\u201C{alias}\u201D\uFF1F",
85
+ deleteBody: "\u5220\u9664\u540E\u8BE5 Key \u4F1A\u4ECE\u51ED\u8BC1\u5E93\u79FB\u9664\u3002\u5982\u679C\u8FD9\u662F\u6700\u540E\u4E00\u4E2A Key\uFF0C\u8BE5 Provider \u5C06\u505C\u6B62\u63A5\u7BA1\u5E76\u6062\u590D\u539F\u6765\u7684 Key \u5F15\u7528\u3002",
86
+ deleteConfirm: "\u5220\u9664\u5BC6\u94A5",
87
+ releaseTitle: "\u505C\u6B62\u63A5\u7BA1\u201C{provider}\u201D\uFF1F",
88
+ releaseBody: "\u63D2\u4EF6\u4F1A\u6062\u590D\u8BE5 Provider \u539F\u6765\u7684 Key \u5F15\u7528\uFF0C\u5E76\u4ECE\u51ED\u8BC1\u5E93\u79FB\u9664\u6240\u6709\u6258\u7BA1 Key\u3002Provider \u914D\u7F6E\u4ECD\u4FDD\u7559\u5728\u6A21\u578B\u8BBE\u7F6E\u4E2D\u3002",
89
+ releaseBodyCreated: "\u63D2\u4EF6\u4F1A\u79FB\u9664\u5B83\u521B\u5EFA\u7684 Provider \u8DEF\u7531\uFF08\u5305\u62EC\u4E4B\u540E\u5728 Models \u4E2D\u505A\u7684\u4FEE\u6539\uFF09\uFF0C\u5E76\u4ECE\u51ED\u8BC1\u5E93\u79FB\u9664\u6240\u6709\u6258\u7BA1 Key\u3002",
90
+ releaseConfirm: "\u505C\u6B62\u63A5\u7BA1",
91
+ lastUsed: "\u6700\u540E\u4F7F\u7528",
92
+ never: "\u4ECE\u672A\u4F7F\u7528",
93
+ coolingUntil: "{time} \u6062\u590D",
94
+ remaining: "\u5269\u4F59 {time}",
95
+ imported: "\u5DF2\u5BFC\u5165",
96
+ uses: "\u4F7F\u7528 {n} \u6B21",
97
+ failures: "\u5931\u8D25 {n} \u6B21",
98
+ adoptHint: "\u63A5\u7BA1\u540E\uFF0C\u8BE5 Provider \u7684 API Key \u5C06\u7531\u5BC6\u94A5\u7BA1\u7406\u5668\u7EDF\u4E00\u63D0\u4F9B\u3002\u5F53\u524D\u51ED\u8BC1\u4F1A\u5BFC\u5165\u4E3A\u7B2C\u4E00\u4E2A Key\uFF0C\u539F\u5F15\u7528\u4F1A\u4FDD\u7559\u4EE5\u4FBF\u505C\u6B62\u63A5\u7BA1\u65F6\u6062\u590D\u3002",
99
+ adoptExtraHint: "\u8FD9\u4E2A\u6269\u5C55 Provider \u5C1A\u672A\u914D\u7F6E\u3002\u6DFB\u52A0 Key \u540E\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u88C5 llm-pi-ai \u8DEF\u7531\uFF0C\u5E76\u4F7F\u7528\u81EA\u52A8\u53D1\u73B0\u6216\u5185\u7F6E\u6A21\u578B\u5217\u8868\u3002",
100
+ adoptAccountTitle: "\u4F7F\u7528 OAuth \u8D26\u6237\u767B\u5F55\u540E\u5373\u53EF\u4F7F\u7528\u8BE5 Provider\u3002",
101
+ unsupported: "\u6682\u4E0D\u652F\u6301\u5BC6\u94A5\u6C60",
102
+ bindingBroken: "Provider \u914D\u7F6E\u5DF2\u4E0D\u518D\u6307\u5411\u5BC6\u94A5\u7BA1\u7406\u5668\u3002\u8BF7\u5148\u505C\u6B62\u63A5\u7BA1\u518D\u91CD\u65B0\u63A5\u7BA1\uFF0C\u6216\u5728\u6A21\u578B\u8BBE\u7F6E\u4E2D\u4FEE\u590D\u8BE5 Provider\u3002",
103
+ noKeys: "\u8FD8\u6CA1\u6709\u5BC6\u94A5\u3002\u6DFB\u52A0\u4E00\u4E2A\u540E\u5373\u53EF\u4F7F\u7528\u8BE5 Provider\u3002",
104
+ noKeysAccount: "\u8FD8\u6CA1\u6709\u8D26\u6237\u3002\u6DFB\u52A0\u4E00\u4E2A\u8D26\u6237\u540E\u5373\u53EF\u4F7F\u7528\u8BE5 Provider\u3002",
105
+ warning: "\u6CE8\u610F",
106
+ settingsTitle: "\u7B56\u7565",
107
+ settingsIntro: "\u4EE5\u4E0B\u8BBE\u7F6E\u5BF9\u6240\u6709\u5DF2\u63A5\u7BA1\u7684 Provider \u751F\u6548\u3002",
108
+ retryCount: "\u5207\u6362 Key \u524D\u91CD\u8BD5\u6B21\u6570",
109
+ retryHint: "\u540C\u4E00\u4E2A Key \u6700\u591A\u91CD\u8BD5\u8FD9\u4E48\u591A\u6B21\uFF1B\u4E4B\u540E\u8BE5 Key \u8FDB\u5165\u5F02\u5E38\u72B6\u6001\uFF0C\u4F1A\u8BDD\u5207\u6362\u5230\u53E6\u4E00\u4E2A Key\u3002",
110
+ baseCooldown: "\u521D\u59CB\u51B7\u5374\uFF08\u5206\u949F\uFF09",
111
+ baseHint: "\u9996\u6B21\u5931\u8D25\u51B7\u5374\u8FD9\u4E48\u4E45\uFF1B\u6062\u590D\u540E\u77ED\u65F6\u95F4\u5185\u518D\u6B21\u5931\u8D25\u4F1A\u7FFB\u500D\u3002",
112
+ grace: "\u6062\u590D\u89C2\u5BDF\u671F\uFF08\u5206\u949F\uFF09",
113
+ graceHint: "\u5728\u6062\u590D\u540E\u7684\u8FD9\u4E2A\u65F6\u95F4\u7A97\u53E3\u5185\u518D\u6B21\u5931\u8D25\uFF0C\u4F1A\u63D0\u5347\u4E0B\u4E00\u6B21\u51B7\u5374\u3002",
114
+ cap: "\u51B7\u5374\u4E0A\u9650\u4E3A 7 \u5929\u3002",
115
+ strategy: "\u9009\u62E9\u7B56\u7565",
116
+ strategyBody: "\u6BCF\u4E2A\u4F1A\u8BDD\u56FA\u5B9A\u4E00\u4E2A Key\uFF1B\u65B0\u4F1A\u8BDD\u6309 Round Robin \u53D6\u4E0B\u4E00\u4E2A\u53EF\u7528 Key\u3002\u6682\u505C\u548C\u51B7\u5374\u4E2D\u7684 Key \u4F1A\u88AB\u8DF3\u8FC7\uFF1B\u51B7\u5374\u7ED3\u675F\u540E\u81EA\u52A8\u6062\u590D\u3002",
117
+ saveSettings: "\u4FDD\u5B58\u7B56\u7565",
118
+ savingSettings: "\u4FDD\u5B58\u4E2D\u2026",
119
+ saved: "\u5DF2\u4FDD\u5B58\u3002",
120
+ writableFalse: "\u5F53\u524D\u914D\u7F6E\u4E3A\u53EA\u8BFB\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u5BC6\u94A5\u53D8\u66F4\u3002",
121
+ reload: "\u91CD\u8BD5",
122
+ refresh: "\u5237\u65B0",
123
+ moreActions: "\u66F4\u591A\u64CD\u4F5C",
124
+ showAll: "\u663E\u793A\u5168\u90E8 {n} \u4E2A Providers",
125
+ showLess: "\u6536\u8D77",
126
+ dismiss: "\u5173\u95ED",
127
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
128
+ loadError: "\u65E0\u6CD5\u52A0\u8F7D\u5BC6\u94A5\u7BA1\u7406\u5668\u3002",
129
+ actionError: "\u64CD\u4F5C\u5931\u8D25\u3002",
130
+ modelsBadge: "\u7531\u5BC6\u94A5\u7BA1\u7406\u5668\u63A5\u7BA1 \xB7 {n} \u4E2A\u5BC6\u94A5",
131
+ modelsHint: "\u5728\u63D2\u4EF6 \u2192 \u5BC6\u94A5\u7BA1\u7406\u4E2D\u7F16\u8F91\u5BC6\u94A5\u3002",
132
+ providerId: "Provider ID",
133
+ env: "\u73AF\u5883\u53D8\u91CF",
134
+ baseUrl: "\u7AEF\u70B9",
135
+ api: "\u534F\u8BAE",
136
+ models: "\u6A21\u578B",
137
+ keysSection: "\u5BC6\u94A5",
138
+ extraSection: "\u6269\u5C55 Providers",
139
+ noSearch: "\u6362\u4E2A\u5173\u952E\u8BCD\u8BD5\u8BD5\u3002",
140
+ managedBadge: "\u5DF2\u63A5\u7BA1",
141
+ timeJustNow: "\u521A\u521A",
142
+ timeMinutesAgo: "{n} \u5206\u949F\u524D",
143
+ timeHoursAgo: "{n} \u5C0F\u65F6\u524D",
144
+ timeDaysAgo: "{n} \u5929\u524D",
145
+ timeMinutes: "{n} \u5206\u949F",
146
+ timeHours: "{n} \u5C0F\u65F6",
147
+ timeDays: "{n} \u5929",
148
+ timeSeconds: "{n} \u79D2",
149
+ confirm: "\u786E\u8BA4",
150
+ close: "\u5173\u95ED",
151
+ keyStatusActive: "\u6B63\u5E38",
152
+ keyStatusCooling: "\u51B7\u5374\u4E2D",
153
+ keyStatusPaused: "\u5DF2\u6682\u505C",
154
+ keyStatusMissing: "\u51ED\u8BC1\u7F3A\u5931",
155
+ keyStatusEmpty: "\u6682\u65E0\u5BC6\u94A5",
156
+ keyStatusUnmanaged: "\u672A\u63A5\u7BA1",
157
+ providerKeys: "{n} \u4E2A\u5BC6\u94A5",
158
+ providerKeyOne: "1 \u4E2A\u5BC6\u94A5",
159
+ countModels: "{n} \u4E2A\u6A21\u578B",
160
+ storageNote: "Key \u4FDD\u5B58\u5728 DSH \u51ED\u8BC1\u5E93\u4E2D\uFF1B\u5BC6\u94A5\u7BA1\u7406\u5668\u72B6\u6001\u4F4D\u4E8E $DSH_HOME/keys-manager/state.json\uFF0C\u4EC5\u542B\u975E\u654F\u611F\u5143\u6570\u636E\u3002",
161
+ providerAdopted: "\u5DF2\u63A5\u7BA1\u3002\u539F\u6709\u51ED\u8BC1\u5DF2\u5BFC\u5165\u4E3A\u7B2C\u4E00\u4E2A Key\u3002",
162
+ summaryText: "\u5DF2\u63A5\u7BA1 {providers} \u4E2A Provider\uFF0C\u5171 {keys} \u4E2A\u5BC6\u94A5 \xB7 \u5355\u4F1A\u8BDD\u56FA\u5B9A + Round Robin",
163
+ summaryEmpty: "\u5C1A\u672A\u63A5\u7BA1\u4EFB\u4F55 Provider\u3002\u5728\u63D2\u4EF6\u8BE6\u60C5\u4E2D\u641C\u7D22\u5E76\u63A5\u7BA1\u3002",
164
+ addExtra: "\u6DFB\u52A0\u5E76\u5B89\u88C5",
165
+ adding: "\u6B63\u5728\u6DFB\u52A0\u2026",
166
+ discoverHint: "\u4F1A\u81EA\u52A8\u5C1D\u8BD5\u83B7\u53D6\u6A21\u578B\u5217\u8868\uFF1B\u82E5\u7AEF\u70B9\u4E0D\u53EF\u8FBE\uFF0C\u5219\u4F7F\u7528\u5185\u7F6E\u6A21\u578B\u3002",
167
+ openModels: "\u6253\u5F00\u6A21\u578B\u8BBE\u7F6E",
168
+ searchResults: "{n} \u4E2A\u5339\u914D",
169
+ reset: "\u91CD\u7F6E",
170
+ attention: "\u9700\u8981\u5904\u7406",
171
+ // OAuth account surface — same values as locale/zh.json (dual source
172
+ // until the host's dsh-client-locale merge semantics are confirmed).
173
+ addAccount: "\u6DFB\u52A0\u8D26\u6237",
174
+ accountIdentity: "\u8D26\u6237",
175
+ accountProjected: "\u670D\u52A1\u4E2D",
176
+ accountReauth: "\u91CD\u65B0\u8BA4\u8BC1",
177
+ accountActivate: "\u8BBE\u4E3A\u5F53\u524D",
178
+ loginTitle: "\u8D26\u6237\u767B\u5F55",
179
+ loginPending: "\u7B49\u5F85\u6388\u6743\u2026",
180
+ loginBrowserOpen: "\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00",
181
+ loginPromptTitle: "\u5B8C\u6210\u767B\u5F55",
182
+ loginPromptSubmit: "\u63D0\u4EA4",
183
+ loginFailed: "\u767B\u5F55\u5931\u8D25",
184
+ loginFlowEnded: "\u672C\u6B21\u767B\u5F55\u6D41\u7A0B\u5DF2\u7ED3\u675F\uFF0C\u8BF7\u5173\u95ED\u7A97\u53E3\u540E\u91CD\u65B0\u5F00\u59CB\u3002",
185
+ loginReady: "\u767B\u5F55\u6210\u529F\u3002",
186
+ deleteAccountTitle: "\u5220\u9664\u8D26\u6237\u201C{alias}\u201D\uFF1F",
187
+ deleteAccountBody: "\u8FD9\u4F1A\u4ECE\u51ED\u8BC1\u5E93\u4E2D\u5220\u9664\u8BE5\u8D26\u6237\u4FDD\u5B58\u7684 OAuth \u6388\u6743\u3002\u5982\u679C\u5B83\u662F\u5F53\u524D\u8D26\u6237\uFF0C\u8BE5 Provider \u8DEF\u7531\u5C06\u505C\u7528\u5B83\u5E76\u56DE\u843D\u5230 API-key \u6C60\uFF1BProvider \u4ECD\u4FDD\u6301\u63A5\u7BA1\u72B6\u6001\u3002",
188
+ deleteAccountBodyNoKeys: "\u8FD9\u4F1A\u4ECE\u51ED\u8BC1\u5E93\u4E2D\u5220\u9664\u8BE5\u8D26\u6237\u4FDD\u5B58\u7684 OAuth \u6388\u6743\u3002\u8BE5\u8DEF\u7531\u6CA1\u6709 API-key \u6C60\uFF0C\u5220\u9664\u540E\u5C06\u505C\u6B62\u670D\u52A1\uFF0C\u76F4\u5230\u91CD\u65B0\u767B\u5F55\u8D26\u6237\u3002"
189
+ };
190
+ var en = {
191
+ nav: "Keys Manager",
192
+ title: "Keys Manager",
193
+ subtitle: "Give one provider several API keys. A session keeps one key; new sessions rotate; failures cool down and recover automatically.",
194
+ search: "Search providers by name, id, or environment variable",
195
+ managed: "Managed",
196
+ available: "Available providers",
197
+ managedEmpty: "No managed providers yet. Adopt one below to start.",
198
+ availableEmpty: "No matching providers.",
199
+ keys: "keys",
200
+ healthy: "healthy",
201
+ cooling: "cooling",
202
+ paused: "paused",
203
+ missing: "missing",
204
+ active: "active",
205
+ unmanaged: "not managed",
206
+ dshBuiltin: "DSH built-in",
207
+ pluginBuiltin: "Plugin built-in",
208
+ customProvider: "Custom",
209
+ configured: "configured",
210
+ notConfigured: "not configured",
211
+ adopt: "Adopt provider",
212
+ adopting: "Adopting\u2026",
213
+ release: "Stop managing",
214
+ releasing: "Releasing\u2026",
215
+ addKey: "Add key",
216
+ addKeyTitle: "Add an API key",
217
+ alias: "Alias",
218
+ aliasPlaceholder: "e.g. Primary, Backup, Team B",
219
+ secret: "API key",
220
+ secretPlaceholder: "Paste the key; it is stored in the credential store",
221
+ addKeyHint: "The key is stored in the DSH credential store and is never shown again.",
222
+ show: "Show",
223
+ hide: "Hide",
224
+ save: "Save",
225
+ saving: "Saving\u2026",
226
+ cancel: "Cancel",
227
+ rename: "Rename",
228
+ pause: "Pause",
229
+ resume: "Resume",
230
+ delete: "Delete",
231
+ deleting: "Deleting\u2026",
232
+ deleteTitle: "Delete key \u201C{alias}\u201D?",
233
+ deleteBody: "This removes the stored key. If it is the last key, the provider stops being managed and its original key reference is restored.",
234
+ deleteConfirm: "Delete key",
235
+ releaseTitle: "Stop managing \u201C{provider}\u201D?",
236
+ releaseBody: "The plugin restores the provider\u2019s original key reference and removes every managed key from the credential store. The provider configuration stays in Models.",
237
+ releaseBodyCreated: "The plugin removes the provider route it created (including any later edits made in Models) and removes every managed key from the credential store.",
238
+ releaseConfirm: "Stop managing",
239
+ lastUsed: "Last used",
240
+ never: "Never",
241
+ coolingUntil: "Recovers {time}",
242
+ remaining: "{time} left",
243
+ imported: "Imported",
244
+ uses: "{n} uses",
245
+ failures: "{n} failures",
246
+ adoptHint: "Adopting turns this provider\u2019s API-key source over to Keys Manager. Its current credential is imported as the first key, and the original reference is kept for release.",
247
+ adoptExtraHint: "This extra provider is not configured yet. Add a key and the plugin installs an llm-pi-ai route with discovered or built-in models.",
248
+ adoptAccountTitle: "Sign in with an OAuth account to use this provider.",
249
+ unsupported: "Not available for key pools",
250
+ bindingBroken: "The provider profile no longer points at Keys Manager. Release and re-adopt it, or repair the provider in Models.",
251
+ noKeys: "No keys yet. Add one to make this provider usable.",
252
+ noKeysAccount: "No accounts yet. Add an account to make this provider usable.",
253
+ warning: "Warning",
254
+ settingsTitle: "Policy",
255
+ settingsIntro: "These values apply to every managed provider.",
256
+ retryCount: "Retries before switching key",
257
+ retryHint: "The same key is retried this many times; after that the key is marked abnormal and the session moves to another key.",
258
+ baseCooldown: "Initial cooldown (minutes)",
259
+ baseHint: "First failure cools a key for this long. Repeated failures shortly after recovery double it.",
260
+ grace: "Recovery grace (minutes)",
261
+ graceHint: "A failure within this window after recovery escalates the next cooldown.",
262
+ cap: "Cooldown is capped at 7 days.",
263
+ strategy: "Selection strategy",
264
+ strategyBody: "Each session pins one key. A new session takes the next healthy key in round-robin order. Paused and cooling keys are skipped; cooling keys become available again automatically.",
265
+ saveSettings: "Save policy",
266
+ savingSettings: "Saving\u2026",
267
+ saved: "Saved.",
268
+ writableFalse: "This profile is read-only. Key changes cannot be saved.",
269
+ reload: "Retry",
270
+ refresh: "Refresh",
271
+ moreActions: "More actions",
272
+ showAll: "Show all {n} providers",
273
+ showLess: "Show less",
274
+ dismiss: "Dismiss",
275
+ loading: "Loading\u2026",
276
+ loadError: "Could not load Keys Manager.",
277
+ actionError: "The operation failed.",
278
+ modelsBadge: "Managed by Keys Manager \xB7 {n} keys",
279
+ modelsHint: "Edit keys in Plugins \u2192 Keys Manager.",
280
+ providerId: "Provider ID",
281
+ env: "Env",
282
+ baseUrl: "Endpoint",
283
+ api: "Protocol",
284
+ models: "Models",
285
+ keysSection: "Keys",
286
+ extraSection: "Extra providers",
287
+ noSearch: "Try a different search.",
288
+ managedBadge: "Managed",
289
+ timeJustNow: "just now",
290
+ timeMinutesAgo: "{n} min ago",
291
+ timeHoursAgo: "{n} h ago",
292
+ timeDaysAgo: "{n} d ago",
293
+ timeMinutes: "{n} min",
294
+ timeHours: "{n} h",
295
+ timeDays: "{n} d",
296
+ timeSeconds: "{n} s",
297
+ confirm: "Confirm",
298
+ close: "Close",
299
+ keyStatusActive: "Active",
300
+ keyStatusCooling: "Cooling",
301
+ keyStatusPaused: "Paused",
302
+ keyStatusMissing: "Credential missing",
303
+ keyStatusEmpty: "No keys",
304
+ keyStatusUnmanaged: "Not managed",
305
+ providerKeys: "{n} keys",
306
+ providerKeyOne: "1 key",
307
+ countModels: "{n} models",
308
+ storageNote: "Keys live in the DSH credential store; manager state lives at $DSH_HOME/keys-manager/state.json and contains no secret values.",
309
+ providerAdopted: "Adopted. The existing credential was imported as the first key.",
310
+ summaryText: "{providers} managed providers \xB7 {keys} keys \xB7 sticky session + round robin",
311
+ summaryEmpty: "No providers managed yet. Search and adopt one in the plugin page.",
312
+ addExtra: "Add and install",
313
+ adding: "Adding\u2026",
314
+ discoverHint: "The plugin tries endpoint discovery first; if that fails it installs the built-in model list.",
315
+ openModels: "Open Models",
316
+ searchResults: "{n} matches",
317
+ reset: "Reset",
318
+ attention: "Needs attention",
319
+ // OAuth account surface — same values as locale/en.json (dual source
320
+ // until the host's dsh-client-locale merge semantics are confirmed).
321
+ addAccount: "Add account",
322
+ accountIdentity: "Account",
323
+ accountProjected: "Serving provider",
324
+ accountReauth: "Re-authenticate",
325
+ accountActivate: "Set active",
326
+ loginTitle: "Account sign-in",
327
+ loginPending: "Waiting for authorization\u2026",
328
+ loginBrowserOpen: "Open in browser",
329
+ loginPromptTitle: "Complete sign-in",
330
+ loginPromptSubmit: "Submit",
331
+ loginFailed: "Login failed",
332
+ loginFlowEnded: "This sign-in session has ended. Close the dialog and start again.",
333
+ loginReady: "Signed in.",
334
+ deleteAccountTitle: "Delete account \u201C{alias}\u201D?",
335
+ deleteAccountBody: "This removes the account\u2019s stored OAuth grant from the credential store. If it is the active account, the provider route stops serving it and falls back to its API-key pool; the provider stays managed.",
336
+ deleteAccountBodyNoKeys: "This removes the account\u2019s stored OAuth grant from the credential store. This route has no API-key pool, so it stops serving requests until an account is signed in again."
337
+ };
338
+ var LOCALE_DICTS = { zh, en };
339
+
340
+ // src/client/runtime.ts
341
+ var import_react = __toESM(require("react"), 1);
342
+
343
+ // src/client/styles.ts
344
+ var CSS = `
345
+ .km-page{max-width:920px;margin:0 auto;padding:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13.5px;line-height:1.5}
346
+ .km-embedded-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin:0 0 16px}
347
+ .km-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 16px;flex:1;min-width:0}
348
+ .km-embedded-head .km-summary{margin:0}
349
+ .km-embedded-actions{display:flex;gap:8px;flex-shrink:0;padding-top:2px}
350
+ .km-summary-line{color:var(--dsw-alias-label-secondary);font-size:12.5px;line-height:1.5}
351
+ .km-stat{display:flex;flex-direction:column;gap:2px;padding:11px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}
352
+ .km-stat strong{font-size:19px;line-height:1.15;font-weight:650;font-variant-numeric:tabular-nums}
353
+ .km-stat span{color:var(--dsw-alias-label-secondary);font-size:12px}
354
+ .km-search{position:relative;display:flex;align-items:center;margin:0 0 20px}
355
+ .km-search-icon{position:absolute;left:12px;color:var(--dsw-alias-label-secondary);pointer-events:none}
356
+ .km-search-input{width:100%;box-sizing:border-box;padding:10px 38px 10px 36px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;outline:none}
357
+ .km-search-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 20%,transparent)}
358
+ .km-search-clear{position:absolute;right:8px;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
359
+ .km-search-clear:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
360
+ .km-section{margin:0 0 22px}
361
+ .km-section-head{display:flex;align-items:baseline;gap:8px;margin:0 0 9px}
362
+ .km-section-title{font-size:14px;font-weight:650;margin:0;letter-spacing:.01em}
363
+ .km-section-count{font-size:12px;color:var(--dsw-alias-label-secondary)}
364
+ .km-provider-list{display:flex;flex-direction:column;gap:8px}
365
+ .km-show-more{display:flex;justify-content:center;padding:8px 0 0}
366
+ .km-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}
367
+ .km-provider-managed{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 26%,var(--dsw-alias-border-l1))}
368
+ .km-provider-open{overflow:visible}
369
+ .km-provider-head{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:0;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}
370
+ .km-provider-head:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 3%,transparent)}
371
+ .km-avatar{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex:0 0 32px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);font-weight:650;font-size:13px;color:var(--dsw-alias-label-secondary)}
372
+ .km-provider-main{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1}
373
+ .km-provider-title-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
374
+ .km-provider-name{font-weight:650;font-size:13.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}
375
+ .km-provider-meta{display:flex;align-items:center;gap:7px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:11.5px;min-width:0}
376
+ .km-provider-side{display:flex;align-items:center;gap:9px;flex-shrink:0}
377
+ .km-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
378
+ .km-chevron{color:var(--dsw-alias-label-secondary);transition:transform .16s ease}
379
+ .km-chevron-open{transform:rotate(180deg)}
380
+ .km-provider-body{border-top:1px solid var(--dsw-alias-border-l1);padding:13px 14px 14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 40%,transparent)}
381
+ .km-adopt{display:flex;flex-direction:column;gap:12px}
382
+ .km-adopt-copy{display:flex;flex-direction:column;gap:5px;color:var(--dsw-alias-label-secondary);font-size:12.5px}
383
+ .km-adopt-title{color:var(--dsw-alias-label-primary);font-size:12.5px}
384
+ .km-adopt-actions{display:flex;gap:8px;align-items:center}
385
+ .km-meta-line{display:flex;gap:6px;align-items:baseline;flex-wrap:wrap;font-size:11.5px}
386
+ .km-managed-body{display:flex;flex-direction:column;gap:10px}
387
+ .km-managed-toolbar{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap}
388
+ .km-managed-summary{display:flex;flex-direction:column;gap:2px;min-width:0;font-size:11.5px}
389
+ .km-managed-actions{display:flex;gap:6px;align-items:center;flex-shrink:0}
390
+ .km-keys{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1);overflow:visible}
391
+ .km-key-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px;border-top:1px solid var(--dsw-alias-border-l1)}
392
+ .km-key-row:first-child{border-top:0}
393
+ .km-account-row{position:relative;display:grid;grid-template-columns:minmax(0,1.35fr) minmax(150px,.8fr) minmax(150px,.9fr) 32px;gap:10px;align-items:center;padding:10px 11px 10px 14px;border-top:1px solid var(--dsw-alias-border-l1)}
394
+ .km-account-row:first-child{border-top:0}
395
+ .km-account-row::before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:2px;border-radius:1px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,transparent)}
396
+ .km-account-glyph{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;flex:0 0 22px;border-radius:50%;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 14%,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-business-primary)}
397
+ .km-key-identity{min-width:0;display:flex;flex-direction:column;gap:2px}
398
+ .km-key-alias-line{display:flex;align-items:center;gap:6px;min-width:0}
399
+ .km-key-alias{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}
400
+ .km-key-sub{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--dsw-alias-label-secondary);flex-wrap:wrap}
401
+ .km-key-status{display:flex;flex-direction:column;gap:2px;align-items:flex-start}
402
+ .km-key-last{display:flex;flex-direction:column;gap:2px;min-width:0}
403
+ .km-key-time{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
404
+ .km-key-time.km-warn{color:var(--dsw-alias-state-warn-primary)}
405
+ .km-key-actions{position:relative;display:flex;justify-content:flex-end}
406
+ .km-status{display:inline-flex;align-items:center;gap:5px;padding:2px 7px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-size:11px;white-space:nowrap;color:var(--dsw-alias-label-secondary)}
407
+ .km-status-success{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 26%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}
408
+ .km-status-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}
409
+ .km-status-error{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent)}
410
+ .km-status-idle{color:var(--dsw-alias-state-idle-primary)}
411
+ .km-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex:0 0 6px;background:var(--dsw-alias-state-idle-primary)}
412
+ .km-dot-success{background:var(--dsw-alias-state-success-primary)}
413
+ .km-dot-warn{background:var(--dsw-alias-state-warn-primary)}
414
+ .km-dot-error{background:var(--dsw-alias-state-error-primary)}
415
+ .km-dot-idle{background:var(--dsw-alias-state-idle-primary)}
416
+ .km-dot-sep{color:var(--dsw-alias-label-secondary)}
417
+ .km-tag{display:inline-flex;align-items:center;padding:1px 6px;border-radius:5px;font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);white-space:nowrap}
418
+ .km-tag-accent{color:var(--dsw-alias-state-business-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 34%,var(--dsw-alias-border-l1));background:var(--dsw-alias-state-business-tertiary)}
419
+ .km-tag-warn{color:var(--dsw-alias-state-warn-primary);border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 8%,transparent)}
420
+ .km-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;flex:0 0 auto}
421
+ .km-icon-btn:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary)}
422
+ .km-icon-btn:disabled{opacity:.5;cursor:default}
423
+ .km-menu{position:absolute;top:32px;right:0;z-index:20;min-width:138px;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-overlay);box-shadow:0 7px 22px color-mix(in srgb,var(--dsw-alias-label-primary) 14%,transparent)}
424
+ .km-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;text-align:left;cursor:pointer}
425
+ .km-menu-item:hover{background:var(--dsw-alias-bg-layer-2)}
426
+ .km-menu-item:disabled{opacity:.5;cursor:default}
427
+ .km-menu-danger{color:var(--dsw-alias-state-error-primary)}
428
+ .km-menu-sep{height:1px;margin:4px 2px;background:var(--dsw-alias-border-l1)}
429
+ .km-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12.5px;font-weight:550;line-height:1.2;cursor:pointer;white-space:nowrap;transition:opacity .15s ease,background .15s ease}
430
+ .km-btn:hover{background:var(--dsw-alias-bg-layer-2)}
431
+ .km-btn:disabled{opacity:.5;cursor:default}
432
+ .km-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-base);border-color:transparent}
433
+ .km-btn-primary:hover{opacity:.9;background:var(--dsw-alias-label-primary)}
434
+ .km-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary)}
435
+ .km-btn-ghost:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
436
+ .km-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-base);border-color:transparent}
437
+ .km-btn-danger:hover{opacity:.9;background:var(--dsw-alias-state-error-primary)}
438
+ .km-btn-small{padding:5px 8px;font-size:12px;border-radius:7px}
439
+ .km-input{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;outline:none}
440
+ .km-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}
441
+ .km-input:disabled{opacity:.6}
442
+ select.km-input{appearance:auto}
443
+ .km-form{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1)}
444
+ .km-form-lead{display:flex;flex-direction:column;gap:2px}
445
+ .km-form-title{font-weight:650;font-size:12.5px}
446
+ .km-form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:10px}
447
+ .km-field{display:flex;flex-direction:column;gap:5px;min-width:0}
448
+ .km-field-wide{min-width:0}
449
+ .km-field-label{font-size:11.5px;font-weight:550;color:var(--dsw-alias-label-secondary)}
450
+ .km-field-hint{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.45}
451
+ .km-secret{position:relative;display:flex;align-items:center}
452
+ .km-secret-input{padding-right:38px}
453
+ .km-secret-toggle{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
454
+ .km-secret-toggle:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
455
+ .km-form-actions{display:flex;justify-content:flex-end;gap:8px}
456
+ .km-rename{display:flex;align-items:center;gap:4px;min-width:0;flex:1 1 auto;max-width:100%}
457
+ .km-rename-input{flex:1 1 auto;width:auto;min-width:60px;padding:4px 7px;font-size:12.5px}
458
+ .km-inline-error{padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px;word-break:break-word}
459
+ .km-key-error{grid-column:1/-1;margin-top:-2px}
460
+ .km-notice{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:12px}
461
+ .km-notice-icon{flex:0 0 auto;margin-top:1px}
462
+ .km-notice-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}
463
+ .km-notice-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}
464
+ .km-notice-body{display:flex;flex-direction:column;gap:2px;min-width:0}
465
+ .km-notice-title{color:var(--dsw-alias-label-primary);font-weight:600;font-size:12px}
466
+ .km-notice-text{line-height:1.5}
467
+ .km-notice-action{flex:0 0 auto}
468
+ .km-empty-keys{padding:12px;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:12px}
469
+ .km-empty{display:flex;align-items:center;gap:8px;padding:14px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}
470
+ .km-provider-foot{display:flex;justify-content:space-between;gap:10px;align-items:center;margin-top:10px;padding-top:9px;border-top:1px dashed var(--dsw-alias-border-l1);font-size:11px;min-height:15px}
471
+ .km-settings{padding:14px}
472
+ .km-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
473
+ .km-settings-title{font-size:14px;font-weight:650;margin:0 0 3px}
474
+ .km-settings-intro{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px}
475
+ .km-settings-icon{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-brand-primary);border:1px solid var(--dsw-alias-border-l1)}
476
+ .km-settings-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);gap:12px;align-items:start}
477
+ .km-field-note{grid-column:1/-1}
478
+ .km-policy-note{padding:9px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
479
+ .km-policy-title{display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:2px}
480
+ .km-policy-note p{margin:0;font-size:11.5px;line-height:1.5}
481
+ .km-settings-actions{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:12px}
482
+ .km-saved{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary);font-size:11.5px}
483
+ .km-banner{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;margin:0 0 12px;font-size:12px;line-height:1.5;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1)}
484
+ .km-banner-error{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 7%,transparent)}
485
+ .km-banner-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 32%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}
486
+ .km-banner-text{flex:1;min-width:0;word-break:break-word}
487
+ .km-banner-close{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
488
+ .km-banner-close:hover{background:var(--dsw-alias-bg-layer-2)}
489
+ .km-loading{display:flex;align-items:center;gap:8px;padding:20px 0;color:var(--dsw-alias-label-secondary);font-size:12.5px}
490
+ .km-load-failure{display:flex;flex-direction:column;gap:10px;align-items:flex-start;padding:18px;border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;color:var(--dsw-alias-label-secondary);font-size:12.5px}
491
+ .km-empty-title{font-weight:600;color:var(--dsw-alias-label-primary)}
492
+ .km-footer{display:flex;align-items:flex-start;gap:6px;padding-top:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.5}
493
+ .km-muted{color:var(--dsw-alias-label-secondary)}
494
+ .km-warn{color:var(--dsw-alias-state-warn-primary)}
495
+ .km-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}
496
+ .km-truncate{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
497
+ .km-spin{animation:km-spin 1s linear infinite}
498
+ .km-models-note{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0 0;padding:7px 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-brand-primary) 24%,var(--dsw-alias-border-l1));border-radius:7px;background:color-mix(in srgb,var(--dsw-alias-brand-primary) 6%,transparent);font-size:11.5px;color:var(--dsw-alias-label-primary)}
499
+ .km-models-note-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 30%,var(--dsw-alias-border-l1));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 7%,transparent)}
500
+ .km-models-note-text{font-weight:600}
501
+ .km-models-note-hint{color:var(--dsw-alias-label-secondary);font-weight:400}
502
+ .km-modal-backdrop{position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;padding:24px;background:color-mix(in srgb,var(--dsw-alias-label-primary) 26%,transparent)}
503
+ .km-modal{width:min(440px,100%);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-overlay);padding:16px;box-shadow:0 18px 48px color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent)}
504
+ .km-modal-title{font-weight:650;font-size:14px;margin-bottom:7px}
505
+ .km-modal-body{color:var(--dsw-alias-label-secondary);font-size:12.5px}
506
+ .km-modal-copy{margin:0;line-height:1.55}
507
+ .km-login-flow{display:flex;flex-direction:column;gap:10px}
508
+ .km-login-pending{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:12.5px}
509
+ .km-login-notices{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;word-break:break-word}
510
+ .km-login-link{color:var(--dsw-alias-state-business-primary);text-decoration:underline;text-underline-offset:2px}
511
+ .km-login-link:hover{color:var(--dsw-alias-label-primary)}
512
+ .km-login-prompt{display:flex;flex-direction:column;gap:10px;padding-top:2px;border-top:1px dashed var(--dsw-alias-border-l1)}
513
+ .km-login-prompt-title{font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-primary)}
514
+ .km-modal-footer{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}
515
+ @keyframes km-spin{to{transform:rotate(360deg)}}
516
+ @media(max-width:720px){
517
+ .km-summary{grid-template-columns:repeat(2,minmax(0,1fr))}
518
+ .km-key-row,.km-account-row{grid-template-columns:minmax(0,1fr) 32px;gap:6px}
519
+ .km-key-status,.km-key-last{grid-column:1;padding-left:0}
520
+ .km-key-actions{grid-column:2;grid-row:1}
521
+ .km-settings-grid{grid-template-columns:1fr}
522
+ .km-form-grid{grid-template-columns:1fr}
523
+ .km-provider-side .km-status{display:none}
524
+ .km-embedded-head{flex-direction:column;gap:10px}
525
+ .km-embedded-actions{padding-top:0}
526
+ }
527
+ `;
528
+
529
+ // src/client/runtime.ts
530
+ var RuntimeContext = import_react.default.createContext(null);
531
+ function useRuntime() {
532
+ const runtime = import_react.default.useContext(RuntimeContext);
533
+ if (runtime === null) throw new Error("Keys Manager runtime is not mounted");
534
+ return runtime;
535
+ }
536
+ function useT() {
537
+ const runtime = useRuntime();
538
+ const ctx = runtime.ctx;
539
+ const subscribe = import_react.default.useCallback((listener) => ctx.locale.subscribe(listener), [ctx]);
540
+ const getSnapshot = import_react.default.useCallback(() => ctx.locale.getSnapshot(), [ctx]);
541
+ const locale = import_react.default.useSyncExternalStore(subscribe, getSnapshot);
542
+ return import_react.default.useMemo(() => ctx.locale.bind("dsh-keys-manager"), [ctx, locale.revision]);
543
+ }
544
+ function useLocaleId() {
545
+ const runtime = useRuntime();
546
+ const ctx = runtime.ctx;
547
+ const subscribe = import_react.default.useCallback((listener) => ctx.locale.subscribe(listener), [ctx]);
548
+ const getSnapshot = import_react.default.useCallback(() => ctx.locale.getSnapshot(), [ctx]);
549
+ return import_react.default.useSyncExternalStore(subscribe, getSnapshot).active;
550
+ }
551
+ function useStore() {
552
+ const runtime = useRuntime();
553
+ return import_react.default.useSyncExternalStore(runtime.store.subscribe, runtime.store.getSnapshot);
554
+ }
555
+ var styleRefs = 0;
556
+ function useStyles() {
557
+ import_react.default.useEffect(() => {
558
+ styleRefs += 1;
559
+ let element = document.getElementById("dsh-keys-manager-styles");
560
+ if (element === null) {
561
+ element = document.createElement("style");
562
+ element.id = "dsh-keys-manager-styles";
563
+ document.head.appendChild(element);
564
+ }
565
+ element.textContent = CSS;
566
+ return () => {
567
+ styleRefs -= 1;
568
+ if (styleRefs <= 0) {
569
+ styleRefs = 0;
570
+ element?.remove();
571
+ }
572
+ };
573
+ }, []);
574
+ }
575
+ function wrapComponent(Component, runtime) {
576
+ return function KeysManagerRuntime(props) {
577
+ useStyles();
578
+ return import_react.default.createElement(RuntimeContext.Provider, { value: runtime }, import_react.default.createElement(Component, props));
579
+ };
580
+ }
581
+
582
+ // src/client/store.ts
583
+ function messageOf(error) {
584
+ if (error instanceof Error && error.message.length > 0) return error.message;
585
+ return String(error);
586
+ }
587
+ function createStore(ctx) {
588
+ let snapshot = {
589
+ status: "idle",
590
+ state: null,
591
+ error: null,
592
+ busy: null,
593
+ now: Date.now()
594
+ };
595
+ let loadSequence = 0;
596
+ let mutationTail = Promise.resolve();
597
+ let queuedMutations = 0;
598
+ const listeners = /* @__PURE__ */ new Set();
599
+ const emit = () => {
600
+ for (const listener of [...listeners]) {
601
+ try {
602
+ listener();
603
+ } catch {
604
+ }
605
+ }
606
+ };
607
+ const update = (patch) => {
608
+ snapshot = { ...snapshot, ...patch };
609
+ emit();
610
+ };
611
+ const rpc = (method, args) => ctx.connection.rpc.call("/api", `keysManager/${method}`, { args });
612
+ const send = async (method, args, onSuccess) => {
613
+ queuedMutations += 1;
614
+ update({ busy: method, error: null });
615
+ const run = async () => {
616
+ ++loadSequence;
617
+ try {
618
+ const result = await rpc(method, args);
619
+ if (!result.ok) throw new Error(result.error.message);
620
+ const outcome = onSuccess(result.value);
621
+ if (queuedMutations === 1) update({ busy: null });
622
+ return outcome;
623
+ } catch (error) {
624
+ const message = messageOf(error);
625
+ if (queuedMutations === 1) update({ busy: null, error: message });
626
+ else update({ error: message });
627
+ return { ok: false, error: message };
628
+ } finally {
629
+ queuedMutations -= 1;
630
+ }
631
+ };
632
+ const next = mutationTail.then(run, run);
633
+ mutationTail = next.then(() => void 0, () => void 0);
634
+ return next;
635
+ };
636
+ const stateOutcome = (value) => {
637
+ const state = value;
638
+ if (queuedMutations === 1) update({ status: "ready", state, now: Date.now(), busy: null, error: null });
639
+ else update({ status: "ready", state, now: Date.now(), error: null });
640
+ return { ok: true, value: state };
641
+ };
642
+ const rpcRead = async (method, args) => {
643
+ try {
644
+ return await rpc(method, args);
645
+ } catch (error) {
646
+ return { ok: false, error: { message: messageOf(error) } };
647
+ }
648
+ };
649
+ const attemptOutcome = (value) => {
650
+ return { ok: true, value: value ?? null };
651
+ };
652
+ return {
653
+ getSnapshot: () => snapshot,
654
+ subscribe(listener) {
655
+ listeners.add(listener);
656
+ return () => listeners.delete(listener);
657
+ },
658
+ async load(options = {}) {
659
+ if (snapshot.busy !== null) return;
660
+ const request = ++loadSequence;
661
+ update({ busy: "load", error: options.keepError === true ? snapshot.error : null });
662
+ try {
663
+ const result = await rpc("getState", {});
664
+ if (request !== loadSequence) return;
665
+ if (!result.ok) throw new Error(result.error.message);
666
+ update({ status: "ready", state: result.value, now: Date.now(), busy: null, error: null });
667
+ } catch (error) {
668
+ if (request !== loadSequence) return;
669
+ update({ status: snapshot.state !== null ? snapshot.status : "error", busy: null, error: messageOf(error) });
670
+ }
671
+ },
672
+ call(method, args) {
673
+ return send(method, args, stateOutcome);
674
+ },
675
+ beginAccountLogin(provider, alias, targetAccountId) {
676
+ return send("beginAccountLogin", { provider, alias, targetAccountId }, attemptOutcome);
677
+ },
678
+ answerAccountPrompt(attemptId, value) {
679
+ return send("answerAccountPrompt", { attemptId, value }, () => ({ ok: true, value: null }));
680
+ },
681
+ async accountAttempt(attemptId) {
682
+ const result = await rpcRead("accountAttempt", { attemptId });
683
+ if (!result.ok) return { ok: false, error: result.error.message };
684
+ return attemptOutcome(result.value);
685
+ },
686
+ async listAccountAttempts() {
687
+ const result = await rpcRead("listAccountAttempts", {});
688
+ if (!result.ok) return { ok: false, error: result.error.message };
689
+ return { ok: true, value: result.value ?? [] };
690
+ },
691
+ activateAccount(provider, accountId) {
692
+ return send("activateAccount", { provider, accountId }, stateOutcome);
693
+ },
694
+ removeAccount(provider, accountId) {
695
+ return send("removeAccount", { provider, accountId }, stateOutcome);
696
+ },
697
+ tick() {
698
+ update({ now: Date.now() });
699
+ if (snapshot.status === "ready" && snapshot.busy === null) void this.load({ keepError: true });
700
+ },
701
+ dismissError() {
702
+ update({ error: null });
703
+ }
704
+ };
705
+ }
706
+
707
+ // src/client/ui.ts
708
+ var import_react2 = __toESM(require("react"), 1);
709
+
710
+ // src/client/format.ts
711
+ function cooldownMinutes(ms) {
712
+ return ms / 6e4;
713
+ }
714
+ function cooldownMs(minutes) {
715
+ return Math.max(0.5, minutes) * 6e4;
716
+ }
717
+ function graceMinutes(ms) {
718
+ return ms / 6e4;
719
+ }
720
+ function graceMs(minutes) {
721
+ return Math.max(0, minutes) * 6e4;
722
+ }
723
+
724
+ // src/client/ui.ts
725
+ var h = import_react2.default.createElement;
726
+ function cx(...values) {
727
+ return values.filter(Boolean).join(" ");
728
+ }
729
+ var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
730
+ function countText(t, count, oneKey, manyKey) {
731
+ return count === 1 ? t(oneKey) : t(manyKey, { n: count });
732
+ }
733
+ function formatClock(timestamp, locale, t) {
734
+ if (!timestamp) return t("never");
735
+ try {
736
+ return new Intl.DateTimeFormat(locale === "zh" ? "zh-CN" : "en", {
737
+ month: "2-digit",
738
+ day: "2-digit",
739
+ hour: "2-digit",
740
+ minute: "2-digit"
741
+ }).format(new Date(timestamp));
742
+ } catch {
743
+ return new Date(timestamp).toLocaleString();
744
+ }
745
+ }
746
+ function relativeTime(timestamp, now, t) {
747
+ if (!timestamp) return t("never");
748
+ const diff = Math.max(0, now - timestamp);
749
+ if (diff < 6e4) return t("timeJustNow");
750
+ if (diff < 36e5) return t("timeMinutesAgo", { n: Math.floor(diff / 6e4) });
751
+ if (diff < 864e5) return t("timeHoursAgo", { n: Math.floor(diff / 36e5) });
752
+ return t("timeDaysAgo", { n: Math.floor(diff / 864e5) });
753
+ }
754
+ function durationText(milliseconds, t) {
755
+ const ms = Math.max(0, milliseconds);
756
+ if (ms < 6e4) return t("timeSeconds", { n: Math.max(1, Math.ceil(ms / 1e3)) });
757
+ if (ms < 36e5) return t("timeMinutes", { n: Math.ceil(ms / 6e4) });
758
+ if (ms < 864e5) return t("timeHours", { n: Math.ceil(ms / 36e5) });
759
+ return t("timeDays", { n: Math.ceil(ms / 864e5) });
760
+ }
761
+ function providerInitial(provider) {
762
+ const source = String(provider?.displayName || provider?.provider || "?").trim();
763
+ return source.length === 0 ? "?" : source.slice(0, 1).toUpperCase();
764
+ }
765
+ function oauthCapable(provider) {
766
+ return provider.nativeAuth?.oauth === true && provider.accountSupported !== false;
767
+ }
768
+ function accountIdentityOf(key, provider) {
769
+ const oauth = key.oauth;
770
+ if (oauth !== null && oauth !== void 0) {
771
+ if (oauth.email !== void 0 && oauth.email.length > 0) return oauth.email;
772
+ if (oauth.accountId !== void 0 && oauth.accountId.length > 0) return oauth.accountId;
773
+ }
774
+ return provider.displayName || provider.provider;
775
+ }
776
+ function statusKey(status) {
777
+ switch (status) {
778
+ case "active":
779
+ return "keyStatusActive";
780
+ case "cooling":
781
+ return "keyStatusCooling";
782
+ case "paused":
783
+ return "keyStatusPaused";
784
+ case "missing":
785
+ return "keyStatusMissing";
786
+ // A managed route with zero keys (round-3 finding 7): "Not managed"
787
+ // would contradict the Managed badge rendered beside it.
788
+ case "empty":
789
+ return "keyStatusEmpty";
790
+ default:
791
+ return "keyStatusUnmanaged";
792
+ }
793
+ }
794
+ function statusTone(status) {
795
+ switch (status) {
796
+ case "active":
797
+ return "success";
798
+ case "cooling":
799
+ return "warn";
800
+ case "paused":
801
+ return "idle";
802
+ case "missing":
803
+ return "error";
804
+ case "empty":
805
+ return "idle";
806
+ default:
807
+ return "idle";
808
+ }
809
+ }
810
+ function Icon({ name, size = 16, className }) {
811
+ const common = {
812
+ width: size,
813
+ height: size,
814
+ viewBox: "0 0 24 24",
815
+ fill: "none",
816
+ stroke: "currentColor",
817
+ strokeWidth: 1.8,
818
+ strokeLinecap: "round",
819
+ strokeLinejoin: "round",
820
+ className,
821
+ "aria-hidden": true
822
+ };
823
+ if (name === "search") return h("svg", common, h("circle", { cx: 11, cy: 11, r: 6.5 }), h("path", { d: "M16 16l4 4" }));
824
+ if (name === "plus") return h("svg", common, h("path", { d: "M12 5v14M5 12h14" }));
825
+ if (name === "dots") return h("svg", common, h("circle", { cx: 5, cy: 12, r: 1.2, fill: "currentColor", stroke: "none" }), h("circle", { cx: 12, cy: 12, r: 1.2, fill: "currentColor", stroke: "none" }), h("circle", { cx: 19, cy: 12, r: 1.2, fill: "currentColor", stroke: "none" }));
826
+ if (name === "chevron") return h("svg", common, h("path", { d: "M6 9l6 6 6-6" }));
827
+ if (name === "eye") return h("svg", common, h("path", { d: "M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6z" }), h("circle", { cx: 12, cy: 12, r: 2.5 }));
828
+ if (name === "eyeOff") return h("svg", common, h("path", { d: "M3 3l18 18M10.6 6.2A9.8 9.8 0 0112 6c6 0 9.5 6 9.5 6a17 17 0 01-3.2 3.8M6.2 6.7A16.4 16.4 0 002.5 12s3.5 6 9.5 6a9.7 9.7 0 004.2-.9M9.9 9.9a3 3 0 004.2 4.2" }));
829
+ if (name === "key") return h("svg", common, h("circle", { cx: 8, cy: 12, r: 3.5 }), h("path", { d: "M11.5 12H21M17 12v3M20 12v2.5" }));
830
+ if (name === "check") return h("svg", common, h("path", { d: "M5 12.5l4.5 4.5L19 7.5" }));
831
+ if (name === "pause") return h("svg", common, h("path", { d: "M9 6v12M15 6v12" }));
832
+ if (name === "play") return h("svg", common, h("path", { d: "M8 5l11 7-11 7V5z" }));
833
+ if (name === "trash") return h("svg", common, h("path", { d: "M4 7h16M9 7V5h6v2M7 7l1 13h8l1-13M10 11v5M14 11v5" }));
834
+ if (name === "close") return h("svg", common, h("path", { d: "M6 6l12 12M18 6L6 18" }));
835
+ if (name === "refresh") return h("svg", common, h("path", { d: "M20 11a8 8 0 10-2.3 6M20 5v6h-6" }));
836
+ if (name === "warning") return h("svg", common, h("path", { d: "M12 3l9.5 17h-19L12 3zM12 9v5M12 17.2v.1" }));
837
+ if (name === "shield") return h("svg", common, h("path", { d: "M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3zM9 12l2 2 4-4" }));
838
+ if (name === "user") return h("svg", common, h("circle", { cx: 12, cy: 8, r: 3.6 }), h("path", { d: "M5 20a7 7 0 0114 0" }));
839
+ return h("svg", common);
840
+ }
841
+ function Button({ variant = "secondary", size = "normal", type = "button", disabled, onClick, children, title }) {
842
+ return h("button", {
843
+ type,
844
+ className: cx("km-btn", `km-btn-${variant}`, size === "small" && "km-btn-small"),
845
+ disabled,
846
+ onClick,
847
+ title
848
+ }, children);
849
+ }
850
+ function StatusPill({ status, t }) {
851
+ return h(
852
+ "span",
853
+ { className: cx("km-status", `km-status-${statusTone(status)}`) },
854
+ h("span", { className: cx("km-dot", `km-dot-${statusTone(status)}`) }),
855
+ h("span", null, t(statusKey(status)))
856
+ );
857
+ }
858
+ function Notice({ tone = "info", icon = "warning", title, children, action }) {
859
+ return h(
860
+ "div",
861
+ { className: cx("km-notice", `km-notice-${tone}`), role: tone === "error" ? "alert" : "note" },
862
+ h(Icon, { name: icon, size: 15, className: "km-notice-icon" }),
863
+ h(
864
+ "div",
865
+ { className: "km-notice-body" },
866
+ title ? h("div", { className: "km-notice-title" }, title) : null,
867
+ h("div", { className: "km-notice-text" }, children)
868
+ ),
869
+ action ? h("div", { className: "km-notice-action" }, action) : null
870
+ );
871
+ }
872
+ function Modal({ title, children, onClose, footer, busy }) {
873
+ const dialogRef = import_react2.default.useRef(null);
874
+ const onCloseRef = import_react2.default.useRef(onClose);
875
+ const busyRef = import_react2.default.useRef(busy);
876
+ onCloseRef.current = onClose;
877
+ busyRef.current = busy;
878
+ import_react2.default.useEffect(() => {
879
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
880
+ const dialog = dialogRef.current;
881
+ const inerted = [];
882
+ if (dialog !== null) {
883
+ let node = dialog.parentElement;
884
+ while (node !== null && node !== document.body) {
885
+ for (const child of [...node.children]) {
886
+ if (!(child instanceof HTMLElement) || child === dialog || child.contains(dialog)) continue;
887
+ if (!child.inert) {
888
+ child.inert = true;
889
+ inerted.push(child);
890
+ }
891
+ }
892
+ node = node.parentElement;
893
+ }
894
+ const first = dialog.querySelector(FOCUSABLE);
895
+ (first ?? dialog).focus();
896
+ }
897
+ const onKey = (event) => {
898
+ if (event.key === "Escape" && busyRef.current !== true) {
899
+ event.preventDefault();
900
+ onCloseRef.current();
901
+ return;
902
+ }
903
+ if (event.key !== "Tab" || dialog === null) return;
904
+ const nodes = [...dialog.querySelectorAll(FOCUSABLE)].filter((node) => node.offsetParent !== null);
905
+ if (nodes.length === 0) {
906
+ event.preventDefault();
907
+ dialog.focus();
908
+ return;
909
+ }
910
+ const first = nodes[0];
911
+ const last = nodes[nodes.length - 1];
912
+ if (event.shiftKey && document.activeElement === first) {
913
+ event.preventDefault();
914
+ last.focus();
915
+ } else if (!event.shiftKey && document.activeElement === last) {
916
+ event.preventDefault();
917
+ first.focus();
918
+ }
919
+ };
920
+ document.addEventListener("keydown", onKey);
921
+ return () => {
922
+ document.removeEventListener("keydown", onKey);
923
+ for (const element of inerted) element.inert = false;
924
+ if (previous !== null && previous.isConnected) previous.focus();
925
+ };
926
+ }, []);
927
+ return h("div", {
928
+ className: "km-modal-backdrop",
929
+ role: "presentation",
930
+ onMouseDown: (event) => {
931
+ if (event.target === event.currentTarget && !busy) onClose();
932
+ }
933
+ }, h(
934
+ "div",
935
+ {
936
+ className: "km-modal",
937
+ role: "dialog",
938
+ "aria-modal": "true",
939
+ "aria-label": title,
940
+ tabIndex: -1,
941
+ ref: dialogRef
942
+ },
943
+ h("div", { className: "km-modal-title" }, title),
944
+ h("div", { className: "km-modal-body" }, children),
945
+ h("div", { className: "km-modal-footer" }, footer)
946
+ ));
947
+ }
948
+ function KeyForm({ provider, onDone, autoFocus = false }) {
949
+ const runtime = useRuntime();
950
+ const t = useT();
951
+ const [alias, setAlias] = import_react2.default.useState("");
952
+ const [value, setValue] = import_react2.default.useState("");
953
+ const [show, setShow] = import_react2.default.useState(false);
954
+ const [busy, setBusy] = import_react2.default.useState(false);
955
+ const [error, setError] = import_react2.default.useState(null);
956
+ const isExtra = !provider.managed && provider.extra;
957
+ const disabled = busy || alias.trim().length === 0 || value.trim().length === 0;
958
+ async function submit(event) {
959
+ event.preventDefault();
960
+ if (disabled) return;
961
+ setBusy(true);
962
+ setError(null);
963
+ const method = provider.managed ? "addKey" : "addExtraProvider";
964
+ const args = provider.managed ? { provider: provider.provider, alias: alias.trim(), value } : { extraId: provider.extraId ?? provider.provider, alias: alias.trim(), value };
965
+ const result = await runtime.store.call(method, args);
966
+ setBusy(false);
967
+ if (!result.ok) {
968
+ setError(result.error);
969
+ return;
970
+ }
971
+ setAlias("");
972
+ setValue("");
973
+ setShow(false);
974
+ onDone?.();
975
+ }
976
+ return h(
977
+ "form",
978
+ { className: "km-form", onSubmit: submit },
979
+ h(
980
+ "div",
981
+ { className: "km-form-lead" },
982
+ h("div", { className: "km-form-title" }, t("addKeyTitle")),
983
+ h("div", { className: "km-muted" }, isExtra ? t("adoptExtraHint") : t("addKeyHint"))
984
+ ),
985
+ h(
986
+ "div",
987
+ { className: "km-form-grid" },
988
+ h(
989
+ "label",
990
+ { className: "km-field" },
991
+ h("span", { className: "km-field-label" }, t("alias")),
992
+ h("input", {
993
+ className: "km-input",
994
+ value: alias,
995
+ onChange: (event) => setAlias(event.target.value),
996
+ placeholder: t("aliasPlaceholder"),
997
+ maxLength: 80,
998
+ autoFocus: autoFocus || isExtra,
999
+ disabled: busy
1000
+ })
1001
+ ),
1002
+ h(
1003
+ "label",
1004
+ { className: "km-field km-field-wide" },
1005
+ h("span", { className: "km-field-label" }, t("secret")),
1006
+ h(
1007
+ "div",
1008
+ { className: "km-secret" },
1009
+ h("input", {
1010
+ className: "km-input km-secret-input",
1011
+ type: show ? "text" : "password",
1012
+ value,
1013
+ onChange: (event) => setValue(event.target.value),
1014
+ placeholder: t("secretPlaceholder"),
1015
+ autoComplete: "new-password",
1016
+ spellCheck: false,
1017
+ disabled: busy
1018
+ }),
1019
+ h("button", {
1020
+ type: "button",
1021
+ className: "km-secret-toggle",
1022
+ onClick: () => setShow((current) => !current),
1023
+ disabled: busy,
1024
+ title: show ? t("hide") : t("show")
1025
+ }, h(Icon, { name: show ? "eyeOff" : "eye", size: 15 }))
1026
+ )
1027
+ )
1028
+ ),
1029
+ error !== null ? h("div", { className: "km-inline-error", role: "alert" }, error) : null,
1030
+ h(
1031
+ "div",
1032
+ { className: "km-form-actions" },
1033
+ h(
1034
+ Button,
1035
+ { variant: "primary", type: "submit", disabled },
1036
+ busy ? h(Icon, { name: "refresh", size: 14, className: "km-spin" }) : h(Icon, { name: "plus", size: 14 }),
1037
+ h("span", null, busy ? isExtra ? t("adding") : t("saving") : isExtra ? t("addExtra") : t("save"))
1038
+ )
1039
+ )
1040
+ );
1041
+ }
1042
+ function KeyRow({ provider, keyRecord, now }) {
1043
+ const key = keyRecord;
1044
+ const runtime = useRuntime();
1045
+ const t = useT();
1046
+ const locale = useLocaleId();
1047
+ const [menu, setMenu] = import_react2.default.useState(false);
1048
+ const [editing, setEditing] = import_react2.default.useState(false);
1049
+ const [alias, setAlias] = import_react2.default.useState(key.alias);
1050
+ const [confirm, setConfirm] = import_react2.default.useState(false);
1051
+ const [busy, setBusy] = import_react2.default.useState(false);
1052
+ const [error, setError] = import_react2.default.useState(null);
1053
+ const rootRef = import_react2.default.useRef(null);
1054
+ import_react2.default.useEffect(() => {
1055
+ setAlias(key.alias);
1056
+ }, [key.alias]);
1057
+ import_react2.default.useEffect(() => {
1058
+ if (!menu) return void 0;
1059
+ const onDown = (event) => {
1060
+ if (rootRef.current !== null && !rootRef.current.contains(event.target)) setMenu(false);
1061
+ };
1062
+ const onKey = (event) => {
1063
+ if (event.key === "Escape") setMenu(false);
1064
+ };
1065
+ document.addEventListener("mousedown", onDown);
1066
+ document.addEventListener("keydown", onKey);
1067
+ return () => {
1068
+ document.removeEventListener("mousedown", onDown);
1069
+ document.removeEventListener("keydown", onKey);
1070
+ };
1071
+ }, [menu]);
1072
+ async function update(patch) {
1073
+ setBusy(true);
1074
+ setError(null);
1075
+ const result = await runtime.store.call("updateKey", { provider: provider.provider, keyId: key.id, patch });
1076
+ setBusy(false);
1077
+ if (!result.ok) setError(result.error);
1078
+ else setEditing(false);
1079
+ }
1080
+ async function submitRename(event) {
1081
+ event.preventDefault();
1082
+ const next = alias.trim();
1083
+ if (next.length === 0 || next === key.alias) {
1084
+ setEditing(false);
1085
+ return;
1086
+ }
1087
+ await update({ alias: next });
1088
+ }
1089
+ async function remove() {
1090
+ setBusy(true);
1091
+ setError(null);
1092
+ const result = await runtime.store.call("deleteKey", { provider: provider.provider, keyId: key.id });
1093
+ setBusy(false);
1094
+ if (!result.ok) setError(result.error);
1095
+ else setConfirm(false);
1096
+ }
1097
+ const cold = key.status === "cooling" && key.cooldownUntil > now;
1098
+ const meta = key.status === "missing" ? t("keyStatusMissing") : key.totalUses > 0 ? t("uses", { n: key.totalUses }) : "";
1099
+ return h(
1100
+ "div",
1101
+ { className: "km-key-row", ref: rootRef },
1102
+ h(
1103
+ "div",
1104
+ { className: "km-key-identity" },
1105
+ h(
1106
+ "div",
1107
+ { className: "km-key-alias-line" },
1108
+ editing ? h(
1109
+ "form",
1110
+ { className: "km-rename", onSubmit: submitRename },
1111
+ h("input", {
1112
+ className: "km-input km-rename-input",
1113
+ "aria-label": t("alias"),
1114
+ value: alias,
1115
+ onChange: (event) => setAlias(event.target.value),
1116
+ maxLength: 80,
1117
+ autoFocus: true,
1118
+ disabled: busy,
1119
+ onKeyDown: (event) => {
1120
+ if (event.key === "Escape") setEditing(false);
1121
+ }
1122
+ }),
1123
+ h("button", { type: "submit", className: "km-icon-btn", disabled: busy, title: t("save") }, h(Icon, { name: "check", size: 15 })),
1124
+ h("button", { type: "button", className: "km-icon-btn", onClick: () => {
1125
+ setEditing(false);
1126
+ setAlias(key.alias);
1127
+ }, title: t("cancel") }, h(Icon, { name: "close", size: 15 }))
1128
+ ) : h("span", { className: "km-key-alias" }, key.alias),
1129
+ key.importedFrom !== null ? h("span", { className: "km-tag" }, t("imported")) : null,
1130
+ key.kind === "oauth" ? h("span", { className: "km-tag" }, "OAuth") : null
1131
+ ),
1132
+ h(
1133
+ "div",
1134
+ { className: "km-key-sub" },
1135
+ h("span", { className: "km-mono km-muted" }, `\u2022\u2022\u2022\u2022 ${key.id.slice(-4)}`),
1136
+ key.totalFailures > 0 ? h("span", { className: "km-muted" }, t("failures", { n: key.totalFailures })) : null
1137
+ )
1138
+ ),
1139
+ h(
1140
+ "div",
1141
+ { className: "km-key-status" },
1142
+ h(StatusPill, { status: key.status, t }),
1143
+ cold ? h("div", { className: "km-key-time km-warn" }, t("coolingUntil", { time: formatClock(key.cooldownUntil, locale, t) })) : null,
1144
+ cold ? h("div", { className: "km-key-time" }, t("remaining", { time: durationText(key.cooldownUntil - now, t) })) : null
1145
+ ),
1146
+ h(
1147
+ "div",
1148
+ { className: "km-key-last" },
1149
+ h("div", { className: "km-key-time" }, `${t("lastUsed")}: ${relativeTime(key.lastUsedAt, now, t)}`),
1150
+ meta ? h("div", { className: "km-key-time km-muted" }, meta) : null
1151
+ ),
1152
+ h(
1153
+ "div",
1154
+ { className: "km-key-actions" },
1155
+ h("button", {
1156
+ type: "button",
1157
+ className: "km-icon-btn",
1158
+ "aria-haspopup": "menu",
1159
+ "aria-expanded": menu,
1160
+ title: t("moreActions"),
1161
+ "aria-label": t("moreActions"),
1162
+ onClick: () => setMenu((current) => !current),
1163
+ disabled: busy
1164
+ }, h(Icon, { name: "dots", size: 17 })),
1165
+ menu ? h(
1166
+ "div",
1167
+ { className: "km-menu", role: "menu" },
1168
+ h("button", { type: "button", className: "km-menu-item", role: "menuitem", onClick: () => {
1169
+ setMenu(false);
1170
+ setEditing(true);
1171
+ } }, h(Icon, { name: "key", size: 15 }), t("rename")),
1172
+ h("button", {
1173
+ type: "button",
1174
+ className: "km-menu-item",
1175
+ role: "menuitem",
1176
+ onClick: () => {
1177
+ setMenu(false);
1178
+ void update({ enabled: !key.enabled });
1179
+ },
1180
+ disabled: busy
1181
+ }, h(Icon, { name: key.enabled ? "pause" : "play", size: 15 }), key.enabled ? t("pause") : t("resume")),
1182
+ h("div", { className: "km-menu-sep" }),
1183
+ h("button", { type: "button", className: "km-menu-item km-menu-danger", role: "menuitem", onClick: () => {
1184
+ setMenu(false);
1185
+ setConfirm(true);
1186
+ }, disabled: busy }, h(Icon, { name: "trash", size: 15 }), t("delete"))
1187
+ ) : null
1188
+ ),
1189
+ error !== null ? h("div", { className: "km-key-error", role: "alert" }, error) : null,
1190
+ confirm ? h(Modal, {
1191
+ title: t("deleteTitle", { alias: key.alias }),
1192
+ onClose: () => setConfirm(false),
1193
+ busy,
1194
+ footer: [
1195
+ h(Button, { key: "cancel", onClick: () => setConfirm(false), disabled: busy }, t("cancel")),
1196
+ h(Button, { key: "delete", variant: "danger", onClick: remove, disabled: busy }, busy ? t("deleting") : t("deleteConfirm"))
1197
+ ]
1198
+ }, h("p", { className: "km-modal-copy" }, t("deleteBody"))) : null
1199
+ );
1200
+ }
1201
+ function AccountRow({ provider, keyRecord, now, onReauthenticate }) {
1202
+ const runtime = useRuntime();
1203
+ const t = useT();
1204
+ const locale = useLocaleId();
1205
+ const key = keyRecord;
1206
+ const identity = accountIdentityOf(key, provider);
1207
+ const [menu, setMenu] = import_react2.default.useState(false);
1208
+ const [confirm, setConfirm] = import_react2.default.useState(false);
1209
+ const [busy, setBusy] = import_react2.default.useState(false);
1210
+ const [error, setError] = import_react2.default.useState(null);
1211
+ const rootRef = import_react2.default.useRef(null);
1212
+ import_react2.default.useEffect(() => {
1213
+ if (!menu) return void 0;
1214
+ const onDown = (event) => {
1215
+ if (rootRef.current !== null && !rootRef.current.contains(event.target)) setMenu(false);
1216
+ };
1217
+ const onKey = (event) => {
1218
+ if (event.key === "Escape") setMenu(false);
1219
+ };
1220
+ document.addEventListener("mousedown", onDown);
1221
+ document.addEventListener("keydown", onKey);
1222
+ return () => {
1223
+ document.removeEventListener("mousedown", onDown);
1224
+ document.removeEventListener("keydown", onKey);
1225
+ };
1226
+ }, [menu]);
1227
+ async function activate() {
1228
+ setBusy(true);
1229
+ setError(null);
1230
+ const result = await runtime.store.activateAccount(provider.provider, key.id);
1231
+ setBusy(false);
1232
+ if (!result.ok) setError(result.error);
1233
+ }
1234
+ async function remove() {
1235
+ setBusy(true);
1236
+ setError(null);
1237
+ const result = await runtime.store.removeAccount(provider.provider, key.id);
1238
+ setBusy(false);
1239
+ if (!result.ok) setError(result.error);
1240
+ else setConfirm(false);
1241
+ }
1242
+ const cold = key.status === "cooling" && key.cooldownUntil > now;
1243
+ const metaParts = [
1244
+ key.totalUses > 0 ? t("uses", { n: key.totalUses }) : null,
1245
+ key.totalFailures > 0 ? t("failures", { n: key.totalFailures }) : null
1246
+ ].filter((value) => value !== null);
1247
+ return h(
1248
+ "div",
1249
+ { className: "km-account-row", ref: rootRef },
1250
+ h(
1251
+ "div",
1252
+ { className: "km-key-identity" },
1253
+ h(
1254
+ "div",
1255
+ { className: "km-key-alias-line" },
1256
+ h("span", { className: "km-account-glyph", "aria-hidden": true }, h(Icon, { name: "user", size: 13 })),
1257
+ h("span", { className: "km-key-alias" }, identity),
1258
+ h("span", { className: "km-tag km-tag-accent" }, t("accountIdentity")),
1259
+ // Step 6: the row whose grant IS the live projection is badged.
1260
+ key.projected === true ? h("span", { className: "km-tag km-tag-accent" }, t("accountProjected")) : null
1261
+ ),
1262
+ h(
1263
+ "div",
1264
+ { className: "km-key-sub" },
1265
+ h("span", { className: "km-mono km-muted" }, `\u2022\u2022\u2022\u2022 ${key.id.slice(-4)}`)
1266
+ )
1267
+ ),
1268
+ h(
1269
+ "div",
1270
+ { className: "km-key-status" },
1271
+ h(StatusPill, { status: key.status, t }),
1272
+ cold ? h("div", { className: "km-key-time km-warn" }, t("coolingUntil", { time: formatClock(key.cooldownUntil, locale, t) })) : null,
1273
+ cold ? h("div", { className: "km-key-time" }, t("remaining", { time: durationText(key.cooldownUntil - now, t) })) : null
1274
+ ),
1275
+ h(
1276
+ "div",
1277
+ { className: "km-key-last" },
1278
+ h("div", { className: "km-key-time" }, `${t("lastUsed")}: ${relativeTime(key.lastUsedAt, now, t)}`),
1279
+ metaParts.length > 0 ? h(
1280
+ "div",
1281
+ { className: "km-key-time km-muted" },
1282
+ metaParts.map((value, index) => [
1283
+ index > 0 ? h("span", { key: `sep-${index}`, className: "km-dot-sep", "aria-hidden": true }, "\xB7") : null,
1284
+ h("span", { key: `part-${index}` }, value)
1285
+ ])
1286
+ ) : null
1287
+ ),
1288
+ h(
1289
+ "div",
1290
+ { className: "km-key-actions" },
1291
+ h("button", {
1292
+ type: "button",
1293
+ className: "km-icon-btn",
1294
+ "aria-haspopup": "menu",
1295
+ "aria-expanded": menu,
1296
+ title: t("moreActions"),
1297
+ "aria-label": t("moreActions"),
1298
+ onClick: () => setMenu((current) => !current),
1299
+ disabled: busy
1300
+ }, h(Icon, { name: "dots", size: 17 })),
1301
+ menu ? h(
1302
+ "div",
1303
+ { className: "km-menu", role: "menu" },
1304
+ h("button", {
1305
+ type: "button",
1306
+ className: "km-menu-item",
1307
+ role: "menuitem",
1308
+ onClick: () => {
1309
+ setMenu(false);
1310
+ void activate();
1311
+ },
1312
+ // Step 6: the projected row already serves the route; setting it
1313
+ // active again is a no-op, so the entry stays disabled.
1314
+ disabled: busy || key.projected === true
1315
+ }, h(Icon, { name: "check", size: 15 }), t("accountActivate")),
1316
+ h("button", {
1317
+ type: "button",
1318
+ className: "km-menu-item",
1319
+ role: "menuitem",
1320
+ onClick: () => {
1321
+ setMenu(false);
1322
+ onReauthenticate();
1323
+ },
1324
+ disabled: busy
1325
+ }, h(Icon, { name: "refresh", size: 15 }), t("accountReauth")),
1326
+ h("div", { className: "km-menu-sep" }),
1327
+ h("button", { type: "button", className: "km-menu-item km-menu-danger", role: "menuitem", onClick: () => {
1328
+ setMenu(false);
1329
+ setConfirm(true);
1330
+ }, disabled: busy }, h(Icon, { name: "trash", size: 15 }), t("delete"))
1331
+ ) : null
1332
+ ),
1333
+ error !== null ? h("div", { className: "km-key-error", role: "alert" }, error) : null,
1334
+ confirm ? h(Modal, {
1335
+ // SF4: account-specific copy — the grant removal consequence, not a
1336
+ // key-deletion/provider-release promise. Step 8: the key-pool
1337
+ // fallback clause appears only when the route actually has key
1338
+ // support; an OAuth-only route states the real consequence instead.
1339
+ title: t("deleteAccountTitle", { alias: identity }),
1340
+ onClose: () => setConfirm(false),
1341
+ busy,
1342
+ footer: [
1343
+ h(Button, { key: "cancel", onClick: () => setConfirm(false), disabled: busy }, t("cancel")),
1344
+ h(Button, { key: "delete", variant: "danger", onClick: remove, disabled: busy }, busy ? t("deleting") : t("deleteConfirm"))
1345
+ ]
1346
+ }, h("p", { className: "km-modal-copy" }, t(provider.supported === true ? "deleteAccountBody" : "deleteAccountBodyNoKeys"))) : null
1347
+ );
1348
+ }
1349
+ function ProviderCard({ provider, expanded, onToggle, now }) {
1350
+ const runtime = useRuntime();
1351
+ const t = useT();
1352
+ const [adding, setAdding] = import_react2.default.useState(false);
1353
+ const [adopting, setAdopting] = import_react2.default.useState(false);
1354
+ const [releasing, setReleasing] = import_react2.default.useState(false);
1355
+ const [confirmRelease, setConfirmRelease] = import_react2.default.useState(false);
1356
+ const [login, setLogin] = import_react2.default.useState(null);
1357
+ const [startingLogin, setStartingLogin] = import_react2.default.useState(false);
1358
+ const [error, setError] = import_react2.default.useState(null);
1359
+ const managed = provider.managed;
1360
+ const broken = managed && provider.bindingHealthy === false;
1361
+ const keyCount = provider.keyCount;
1362
+ const warning = provider.warning;
1363
+ const isExtra = provider.extra;
1364
+ const origin = provider.origin ?? (provider.extra ? "plugin" : "dsh");
1365
+ const originLabel = origin === "plugin" ? t("pluginBuiltin") : origin === "dsh" ? t("dshBuiltin") : t("customProvider");
1366
+ const providerMeta = [
1367
+ provider.provider,
1368
+ provider.active ? t("active") : provider.configured ? t("configured") : t("notConfigured"),
1369
+ provider.api ?? null
1370
+ ].filter((value) => Boolean(value));
1371
+ async function adopt() {
1372
+ setAdopting(true);
1373
+ setError(null);
1374
+ const result = await runtime.store.call("adoptProvider", { provider: provider.provider });
1375
+ setAdopting(false);
1376
+ if (!result.ok) setError(result.error);
1377
+ }
1378
+ async function release() {
1379
+ setReleasing(true);
1380
+ setError(null);
1381
+ const result = await runtime.store.call("releaseProvider", { provider: provider.provider });
1382
+ setReleasing(false);
1383
+ if (!result.ok) {
1384
+ setError(result.error);
1385
+ return;
1386
+ }
1387
+ setConfirmRelease(false);
1388
+ }
1389
+ async function openLogin(targetAccountId, alias) {
1390
+ if (startingLogin) return;
1391
+ setStartingLogin(true);
1392
+ setError(null);
1393
+ const result = await runtime.store.beginAccountLogin(provider.provider, alias, targetAccountId);
1394
+ setStartingLogin(false);
1395
+ if (!result.ok) {
1396
+ setError(result.error);
1397
+ return;
1398
+ }
1399
+ if (result.value !== null) setLogin({ attemptId: result.value.id });
1400
+ }
1401
+ async function adoptThenLogin() {
1402
+ if (provider.managed) {
1403
+ await openLogin();
1404
+ return;
1405
+ }
1406
+ setAdopting(true);
1407
+ setError(null);
1408
+ const result = await runtime.store.call("adoptProvider", { provider: provider.provider });
1409
+ setAdopting(false);
1410
+ if (!result.ok) {
1411
+ setError(result.error);
1412
+ return;
1413
+ }
1414
+ await openLogin();
1415
+ }
1416
+ const accountAdoptable = oauthCapable(provider);
1417
+ const adoptable = provider.supported || accountAdoptable;
1418
+ return h(
1419
+ "div",
1420
+ { className: cx("km-card", "km-provider", expanded && "km-provider-open", managed && "km-provider-managed") },
1421
+ h(
1422
+ "button",
1423
+ {
1424
+ type: "button",
1425
+ className: "km-provider-head",
1426
+ onClick: onToggle,
1427
+ "aria-expanded": expanded
1428
+ },
1429
+ h("span", { className: "km-avatar", "aria-hidden": true }, providerInitial(provider)),
1430
+ h(
1431
+ "span",
1432
+ { className: "km-provider-main" },
1433
+ h(
1434
+ "span",
1435
+ { className: "km-provider-title-line" },
1436
+ h("span", { className: "km-provider-name" }, provider.displayName || provider.provider),
1437
+ h("span", { className: "km-tag" }, originLabel),
1438
+ managed ? h("span", { className: "km-tag km-tag-accent" }, t("managedBadge")) : null,
1439
+ !managed && !provider.supported && !accountAdoptable ? h("span", { className: "km-tag km-tag-warn" }, t("unsupported")) : null
1440
+ ),
1441
+ h(
1442
+ "span",
1443
+ { className: "km-provider-meta" },
1444
+ h("span", { className: "km-mono" }, provider.provider),
1445
+ h("span", { className: "km-dot-sep", "aria-hidden": true }, "\xB7"),
1446
+ h("span", null, providerMeta.slice(1).join(" \xB7 ")),
1447
+ provider.env.length > 0 ? h("span", { className: "km-mono km-muted" }, provider.env.join(", ")) : null
1448
+ )
1449
+ ),
1450
+ h(
1451
+ "span",
1452
+ { className: "km-provider-side" },
1453
+ managed ? h("span", { className: "km-count" }, countText(t, keyCount, "providerKeyOne", "providerKeys")) : h("span", { className: "km-count km-muted" }, adoptable ? t("unmanaged") : t("unsupported")),
1454
+ managed ? h(StatusPill, { status: provider.status, t }) : null,
1455
+ h(Icon, { name: "chevron", size: 16, className: cx("km-chevron", expanded && "km-chevron-open") })
1456
+ )
1457
+ ),
1458
+ expanded ? h(
1459
+ "div",
1460
+ { className: "km-provider-body" },
1461
+ error !== null ? h("div", { className: "km-inline-error", role: "alert" }, error) : null,
1462
+ warning !== void 0 || broken ? h(Notice, { tone: "warn", title: t("warning") }, broken ? t("bindingBroken") : warning) : null,
1463
+ !managed ? h(
1464
+ "div",
1465
+ { className: "km-adopt" },
1466
+ h(
1467
+ "div",
1468
+ { className: "km-adopt-copy" },
1469
+ // I-000063: an OAuth-only route is account-adoptable, so its
1470
+ // adopt panel headline must invite sign-in instead of claiming
1471
+ // the provider is unsupported. The unsupported headline remains
1472
+ // only for providers that are neither key- nor account-capable.
1473
+ h(
1474
+ "div",
1475
+ { className: "km-adopt-title" },
1476
+ provider.supported ? t(isExtra ? "adoptExtraHint" : "adoptHint") : accountAdoptable ? t("adoptAccountTitle") : t("unsupported")
1477
+ ),
1478
+ !provider.supported && provider.reason !== void 0 ? h("div", { className: "km-muted" }, provider.reason) : null,
1479
+ provider.apiKeyEnv !== null ? h("div", { className: "km-meta-line" }, h("span", { className: "km-muted" }, `${t("secret")}:`), h("span", { className: "km-mono" }, provider.apiKeyEnv)) : null,
1480
+ provider.baseURL !== null ? h("div", { className: "km-meta-line" }, h("span", { className: "km-muted" }, `${t("baseUrl")}:`), h("span", { className: "km-mono km-truncate" }, provider.baseURL)) : null,
1481
+ provider.models.length > 0 ? h("div", { className: "km-meta-line" }, h("span", { className: "km-muted" }, `${t("models")}:`), h("span", null, t("countModels", { n: provider.models.length }))) : null
1482
+ ),
1483
+ isExtra ? h(KeyForm, { provider, autoFocus: true }) : h(
1484
+ "div",
1485
+ { className: "km-adopt-actions" },
1486
+ h(
1487
+ Button,
1488
+ { variant: "primary", onClick: adopt, disabled: adopting || !adoptable },
1489
+ adopting ? h(Icon, { name: "refresh", size: 14, className: "km-spin" }) : h(Icon, { name: "shield", size: 14 }),
1490
+ h("span", null, adopting ? t("adopting") : t("adopt"))
1491
+ ),
1492
+ // MF1: Add account is reachable from the unmanaged/adopt flow —
1493
+ // adopt (zero keys for OAuth-only routes) then sign in.
1494
+ oauthCapable(provider) ? h(
1495
+ Button,
1496
+ { variant: "secondary", onClick: () => void adoptThenLogin(), disabled: adopting || startingLogin },
1497
+ startingLogin ? h(Icon, { name: "refresh", size: 14, className: "km-spin" }) : h(Icon, { name: "user", size: 14 }),
1498
+ h("span", null, startingLogin ? t("adopting") : t("addAccount"))
1499
+ ) : null
1500
+ )
1501
+ ) : h(
1502
+ "div",
1503
+ { className: "km-managed-body" },
1504
+ h(
1505
+ "div",
1506
+ { className: "km-managed-toolbar" },
1507
+ h(
1508
+ "div",
1509
+ { className: "km-managed-summary" },
1510
+ h("span", { className: "km-mono km-muted" }, `${t("secret")}: ${provider.brokerRef ?? "\u2014"}`),
1511
+ provider.baseURL !== null ? h("span", { className: "km-mono km-muted km-truncate" }, provider.baseURL) : null
1512
+ ),
1513
+ h(
1514
+ "div",
1515
+ { className: "km-managed-actions" },
1516
+ h(
1517
+ Button,
1518
+ { variant: "secondary", size: "small", onClick: () => setAdding((current) => !current), disabled: releasing || !provider.supported, title: provider.supported ? void 0 : provider.reason },
1519
+ h(Icon, { name: "plus", size: 14 }),
1520
+ h("span", null, t("addKey"))
1521
+ ),
1522
+ oauthCapable(provider) ? h(
1523
+ Button,
1524
+ { variant: "secondary", size: "small", onClick: () => void openLogin(), disabled: releasing || startingLogin },
1525
+ h(Icon, { name: "user", size: 14 }),
1526
+ h("span", null, t("addAccount"))
1527
+ ) : null,
1528
+ h(
1529
+ Button,
1530
+ { variant: "ghost", size: "small", onClick: () => setConfirmRelease(true), disabled: releasing },
1531
+ releasing ? t("releasing") : t("release")
1532
+ )
1533
+ )
1534
+ ),
1535
+ adding ? h(KeyForm, { provider, onDone: () => setAdding(false), autoFocus: true }) : null,
1536
+ provider.keys.length === 0 ? h(
1537
+ "div",
1538
+ { className: "km-empty-keys" },
1539
+ h("div", null, provider.supported ? t("noKeys") : oauthCapable(provider) ? t("noKeysAccount") : t("unsupported")),
1540
+ !provider.supported && provider.reason !== void 0 ? h("div", { className: "km-muted" }, provider.reason) : null
1541
+ ) : h("div", { className: "km-keys" }, provider.keys.map((key) => key.kind === "oauth" ? h(AccountRow, { key: key.id, provider, keyRecord: key, now, onReauthenticate: () => void openLogin(key.id, key.alias) }) : h(KeyRow, { key: key.id, provider, keyRecord: key, now })))
1542
+ ),
1543
+ h(
1544
+ "div",
1545
+ { className: "km-provider-foot" },
1546
+ h("span", { className: "km-muted" }, provider.extra ? `${t("api")}: ${provider.api ?? "\u2014"} \xB7 ${t("models")}: ${provider.models.length}` : null),
1547
+ provider.defaultBaseURL !== null ? h("span", { className: "km-mono km-muted km-truncate" }, provider.defaultBaseURL) : null
1548
+ )
1549
+ ) : null,
1550
+ confirmRelease ? h(Modal, {
1551
+ title: t("releaseTitle", { provider: provider.displayName || provider.provider }),
1552
+ onClose: () => setConfirmRelease(false),
1553
+ busy: releasing,
1554
+ footer: [
1555
+ h(Button, { key: "cancel", onClick: () => setConfirmRelease(false), disabled: releasing }, t("cancel")),
1556
+ h(Button, { key: "release", variant: "danger", onClick: release, disabled: releasing }, releasing ? t("releasing") : t("releaseConfirm"))
1557
+ ]
1558
+ }, h("p", { className: "km-modal-copy" }, t(provider.createdProfile === true ? "releaseBodyCreated" : "releaseBody"))) : null,
1559
+ login !== null ? h(LoginDialog, {
1560
+ provider,
1561
+ attemptId: login.attemptId,
1562
+ onClose: () => setLogin(null)
1563
+ }) : null
1564
+ );
1565
+ }
1566
+ var LOGIN_POLL_MS = 1e3;
1567
+ function safeNoticeUrl(url) {
1568
+ if (url === void 0 || url.length === 0) return null;
1569
+ try {
1570
+ const parsed = new URL(url);
1571
+ return parsed.protocol === "http:" || parsed.protocol === "https:" ? url : null;
1572
+ } catch {
1573
+ return null;
1574
+ }
1575
+ }
1576
+ var LOGIN_POLL_FAILURE_LIMIT = 3;
1577
+ function LoginDialog({ provider, attemptId, onClose }) {
1578
+ const runtime = useRuntime();
1579
+ const t = useT();
1580
+ const [attempt, setAttempt] = import_react2.default.useState(null);
1581
+ const [flowEnded, setFlowEnded] = import_react2.default.useState(false);
1582
+ const [pollError, setPollError] = import_react2.default.useState(null);
1583
+ const [answer, setAnswer] = import_react2.default.useState("");
1584
+ const [answering, setAnswering] = import_react2.default.useState(false);
1585
+ const [error, setError] = import_react2.default.useState(null);
1586
+ const pollNowRef = import_react2.default.useRef(null);
1587
+ const onCloseRef = import_react2.default.useRef(onClose);
1588
+ onCloseRef.current = onClose;
1589
+ const prompt = attempt?.prompt;
1590
+ const promptKey = prompt === void 0 ? "" : `${prompt.kind}\0${prompt.message}\0${(prompt.options ?? []).map((option) => option.id).join("\0")}`;
1591
+ import_react2.default.useEffect(() => {
1592
+ const options = prompt !== void 0 && prompt.kind === "select" ? prompt.options : void 0;
1593
+ if (options !== void 0 && options.length > 0) {
1594
+ setAnswer((current) => options.some((option) => option.id === current) ? current : options[0].id);
1595
+ return;
1596
+ }
1597
+ setAnswer("");
1598
+ }, [promptKey]);
1599
+ import_react2.default.useEffect(() => {
1600
+ let stopped = false;
1601
+ let timer;
1602
+ let failures = 0;
1603
+ let pending = 0;
1604
+ let latestUpdatedAt = 0;
1605
+ const isTerminal = (status) => status === "ready" || status === "failed" || status === "cancelled";
1606
+ const poll = async () => {
1607
+ if (stopped) return;
1608
+ pending += 1;
1609
+ try {
1610
+ const result = await runtime.store.accountAttempt(attemptId);
1611
+ if (stopped) return;
1612
+ if (!result.ok) {
1613
+ failures += 1;
1614
+ if (failures >= LOGIN_POLL_FAILURE_LIMIT) {
1615
+ if (timer !== void 0) clearInterval(timer);
1616
+ setPollError(result.error);
1617
+ }
1618
+ return;
1619
+ }
1620
+ failures = 0;
1621
+ if (result.value === null) {
1622
+ if (timer !== void 0) clearInterval(timer);
1623
+ setFlowEnded(true);
1624
+ return;
1625
+ }
1626
+ const view = result.value;
1627
+ if (view.updatedAt < latestUpdatedAt) return;
1628
+ latestUpdatedAt = view.updatedAt;
1629
+ setAttempt((current) => {
1630
+ if (current === null) return view;
1631
+ if (isTerminal(current.status) && !isTerminal(view.status)) return current;
1632
+ if (current.updatedAt > view.updatedAt) return current;
1633
+ if (current.updatedAt === view.updatedAt && current.status === view.status) return current;
1634
+ return view;
1635
+ });
1636
+ if (view.status === "ready") {
1637
+ if (timer !== void 0) clearInterval(timer);
1638
+ await runtime.store.load();
1639
+ if (!stopped) onCloseRef.current();
1640
+ return;
1641
+ }
1642
+ if (view.status === "failed" || view.status === "cancelled") {
1643
+ if (timer !== void 0) clearInterval(timer);
1644
+ }
1645
+ } finally {
1646
+ pending -= 1;
1647
+ }
1648
+ };
1649
+ const pollNow = () => {
1650
+ void poll();
1651
+ };
1652
+ pollNowRef.current = pollNow;
1653
+ void poll();
1654
+ timer = setInterval(() => {
1655
+ if (pending === 0) void poll();
1656
+ }, LOGIN_POLL_MS);
1657
+ return () => {
1658
+ stopped = true;
1659
+ if (timer !== void 0) clearInterval(timer);
1660
+ };
1661
+ }, [attemptId, runtime]);
1662
+ async function submitAnswer(event) {
1663
+ event.preventDefault();
1664
+ const prompt2 = attempt?.prompt;
1665
+ if (prompt2 === void 0 || answering) return;
1666
+ const value = answer.trim();
1667
+ if (value.length === 0) return;
1668
+ setAnswering(true);
1669
+ setError(null);
1670
+ const result = await runtime.store.answerAccountPrompt(attemptId, value);
1671
+ setAnswering(false);
1672
+ if (!result.ok) {
1673
+ setError(result.error);
1674
+ return;
1675
+ }
1676
+ setAnswer("");
1677
+ pollNowRef.current?.();
1678
+ }
1679
+ const lastNotice = attempt !== null && attempt.notices.length > 0 ? attempt.notices[attempt.notices.length - 1] : null;
1680
+ const safeUrl = attempt !== null ? attempt.notices.map((notice) => safeNoticeUrl(notice.url)).reverse().find((url) => url !== null) ?? null : null;
1681
+ const answerable = prompt !== void 0 && !answering && answer.trim().length > 0;
1682
+ let body;
1683
+ if (pollError !== null) {
1684
+ body = h(Notice, { tone: "error", icon: "warning", title: t("loginFailed") }, pollError);
1685
+ } else if (flowEnded) {
1686
+ body = h(Notice, { tone: "warn", icon: "warning" }, t("loginFlowEnded"));
1687
+ } else if (attempt === null || attempt.status === "pending") {
1688
+ body = h(
1689
+ "div",
1690
+ { className: "km-login-flow" },
1691
+ h("div", { className: "km-login-pending" }, h(Icon, { name: "refresh", size: 15, className: "km-spin" }), h("span", null, t("loginPending"))),
1692
+ lastNotice !== null ? h("div", { className: "km-login-notices" }, lastNotice.message) : null,
1693
+ safeUrl !== null ? h(
1694
+ "div",
1695
+ { className: "km-login-notices" },
1696
+ h("a", { className: "km-login-link", href: safeUrl, target: "_blank", rel: "noreferrer" }, t("loginBrowserOpen"))
1697
+ ) : null
1698
+ );
1699
+ } else if (attempt.status === "ready") {
1700
+ body = h("div", { className: "km-login-pending" }, h(Icon, { name: "check", size: 15 }), h("span", null, t("loginReady")));
1701
+ } else if (attempt.status === "failed" || attempt.status === "cancelled") {
1702
+ body = h(Notice, { tone: "error", icon: "warning", title: t("loginFailed") }, attempt.error ?? lastNotice?.message ?? provider.displayName);
1703
+ } else {
1704
+ body = h(
1705
+ "div",
1706
+ { className: "km-login-flow" },
1707
+ lastNotice !== null ? h("div", { className: "km-login-notices" }, lastNotice.message) : null,
1708
+ safeUrl !== null ? h(
1709
+ "div",
1710
+ { className: "km-login-notices" },
1711
+ h("a", { className: "km-login-link", href: safeUrl, target: "_blank", rel: "noreferrer" }, t("loginBrowserOpen"))
1712
+ ) : null,
1713
+ prompt !== void 0 ? h(
1714
+ "form",
1715
+ { className: "km-login-prompt", onSubmit: submitAnswer },
1716
+ h("div", { className: "km-login-prompt-title" }, t("loginPromptTitle")),
1717
+ h(
1718
+ "div",
1719
+ { className: "km-field" },
1720
+ h("span", { className: "km-field-label" }, prompt.message),
1721
+ prompt.kind === "select" && prompt.options !== void 0 ? h("select", {
1722
+ className: "km-input",
1723
+ value: answer,
1724
+ onChange: (event) => setAnswer(event.target.value),
1725
+ disabled: answering,
1726
+ autoFocus: true
1727
+ }, prompt.options.map((option) => h("option", { key: option.id, value: option.id }, option.label))) : h("input", {
1728
+ className: "km-input",
1729
+ type: prompt.kind === "secret" ? "password" : "text",
1730
+ value: answer,
1731
+ onChange: (event) => setAnswer(event.target.value),
1732
+ placeholder: prompt.placeholder,
1733
+ spellCheck: false,
1734
+ disabled: answering,
1735
+ autoFocus: true
1736
+ })
1737
+ ),
1738
+ error !== null ? h("div", { className: "km-inline-error", role: "alert" }, error) : null,
1739
+ h(
1740
+ "div",
1741
+ { className: "km-form-actions" },
1742
+ h(
1743
+ Button,
1744
+ { variant: "primary", type: "submit", disabled: !answerable },
1745
+ answering ? h(Icon, { name: "refresh", size: 14, className: "km-spin" }) : h(Icon, { name: "check", size: 14 }),
1746
+ h("span", null, answering ? t("loginPending") : t("loginPromptSubmit"))
1747
+ )
1748
+ )
1749
+ ) : null
1750
+ );
1751
+ }
1752
+ return h(Modal, {
1753
+ title: t("loginTitle"),
1754
+ onClose,
1755
+ busy: false,
1756
+ footer: h(Button, { onClick: onClose, disabled: answering }, t("close"))
1757
+ }, body);
1758
+ }
1759
+ function SettingsCard() {
1760
+ const runtime = useRuntime();
1761
+ const t = useT();
1762
+ const snapshot = useStore();
1763
+ const state = snapshot.state;
1764
+ const settings = state?.settings ?? null;
1765
+ const [retry, setRetry] = import_react2.default.useState("2");
1766
+ const [base, setBase] = import_react2.default.useState("5");
1767
+ const [grace, setGrace] = import_react2.default.useState("10");
1768
+ const [busy, setBusy] = import_react2.default.useState(false);
1769
+ const [saved, setSaved] = import_react2.default.useState(false);
1770
+ const [error, setError] = import_react2.default.useState(null);
1771
+ const dirty = import_react2.default.useRef(false);
1772
+ import_react2.default.useEffect(() => {
1773
+ if (settings === null || dirty.current) return;
1774
+ setRetry(String(settings.retryCount));
1775
+ setBase(String(cooldownMinutes(settings.baseCooldownMs)));
1776
+ setGrace(String(graceMinutes(settings.recoveryGraceMs)));
1777
+ }, [settings]);
1778
+ function change(setter) {
1779
+ return (event) => {
1780
+ dirty.current = true;
1781
+ setSaved(false);
1782
+ setter(event.target.value);
1783
+ };
1784
+ }
1785
+ async function save(event) {
1786
+ event.preventDefault();
1787
+ setBusy(true);
1788
+ setError(null);
1789
+ setSaved(false);
1790
+ const result = await runtime.store.call("setSettings", {
1791
+ patch: {
1792
+ retryCount: Number(retry),
1793
+ baseCooldownMs: cooldownMs(Number(base)),
1794
+ recoveryGraceMs: graceMs(Number(grace))
1795
+ }
1796
+ });
1797
+ setBusy(false);
1798
+ if (!result.ok) {
1799
+ setError(result.error);
1800
+ return;
1801
+ }
1802
+ dirty.current = false;
1803
+ setSaved(true);
1804
+ }
1805
+ return h(
1806
+ "form",
1807
+ { className: "km-card km-settings", onSubmit: save },
1808
+ h(
1809
+ "div",
1810
+ { className: "km-settings-head" },
1811
+ h(
1812
+ "div",
1813
+ null,
1814
+ h("h3", { className: "km-settings-title" }, t("settingsTitle")),
1815
+ h("p", { className: "km-settings-intro" }, t("settingsIntro"))
1816
+ ),
1817
+ h("span", { className: "km-settings-icon" }, h(Icon, { name: "shield", size: 20 }))
1818
+ ),
1819
+ h(
1820
+ "div",
1821
+ { className: "km-settings-grid" },
1822
+ h(
1823
+ "label",
1824
+ { className: "km-field" },
1825
+ h("span", { className: "km-field-label" }, t("retryCount")),
1826
+ h(
1827
+ "select",
1828
+ { className: "km-input", value: retry, onChange: change(setRetry), disabled: busy || state?.writable === false },
1829
+ ["1", "2", "3"].map((value) => h("option", { key: value, value }, value))
1830
+ ),
1831
+ h("span", { className: "km-field-hint" }, t("retryHint"))
1832
+ ),
1833
+ h(
1834
+ "label",
1835
+ { className: "km-field" },
1836
+ h("span", { className: "km-field-label" }, t("baseCooldown")),
1837
+ h("input", { className: "km-input", type: "number", min: 0.5, step: "any", value: base, onChange: change(setBase), disabled: busy || state?.writable === false }),
1838
+ h("span", { className: "km-field-hint" }, t("baseHint"))
1839
+ ),
1840
+ h(
1841
+ "label",
1842
+ { className: "km-field" },
1843
+ h("span", { className: "km-field-label" }, t("grace")),
1844
+ h("input", { className: "km-input", type: "number", min: 0, step: "any", value: grace, onChange: change(setGrace), disabled: busy || state?.writable === false }),
1845
+ h("span", { className: "km-field-hint" }, t("graceHint"))
1846
+ ),
1847
+ h(
1848
+ "div",
1849
+ { className: "km-field km-field-note" },
1850
+ h(
1851
+ "div",
1852
+ { className: "km-policy-note" },
1853
+ h("div", { className: "km-policy-title" }, h(Icon, { name: "warning", size: 14 }), t("cap")),
1854
+ h("p", { className: "km-muted" }, t("strategyBody"))
1855
+ )
1856
+ )
1857
+ ),
1858
+ error !== null ? h("div", { className: "km-inline-error", role: "alert" }, error) : null,
1859
+ h(
1860
+ "div",
1861
+ { className: "km-settings-actions" },
1862
+ saved ? h("span", { className: "km-saved" }, h(Icon, { name: "check", size: 14 }), t("saved")) : null,
1863
+ h(
1864
+ Button,
1865
+ { variant: "primary", type: "submit", disabled: busy || state?.writable === false || snapshot.busy !== null },
1866
+ busy ? h(Icon, { name: "refresh", size: 14, className: "km-spin" }) : null,
1867
+ busy ? t("savingSettings") : t("saveSettings")
1868
+ )
1869
+ )
1870
+ );
1871
+ }
1872
+ function KeysManagerBody() {
1873
+ const runtime = useRuntime();
1874
+ const t = useT();
1875
+ const snapshot = useStore();
1876
+ const state = snapshot.state;
1877
+ const now = snapshot.now;
1878
+ const [query, setQuery] = import_react2.default.useState("");
1879
+ const [showAllAvailable, setShowAllAvailable] = import_react2.default.useState(false);
1880
+ const [expanded, setExpanded] = import_react2.default.useState(() => /* @__PURE__ */ new Set());
1881
+ const loadedRef = import_react2.default.useRef(false);
1882
+ import_react2.default.useEffect(() => {
1883
+ if (loadedRef.current) return;
1884
+ loadedRef.current = true;
1885
+ if (snapshot.status === "idle") void runtime.store.load();
1886
+ }, [runtime, snapshot.status]);
1887
+ const providers = state?.providers ?? [];
1888
+ const normalizedQuery = query.trim().toLowerCase();
1889
+ const matches = (provider) => {
1890
+ if (normalizedQuery.length === 0) return true;
1891
+ const haystack = [
1892
+ provider.displayName,
1893
+ provider.provider,
1894
+ provider.extraId,
1895
+ provider.api,
1896
+ provider.baseURL,
1897
+ ...provider.env
1898
+ ].filter((value) => Boolean(value)).join(" ").toLowerCase();
1899
+ return haystack.includes(normalizedQuery);
1900
+ };
1901
+ const managed = providers.filter((provider) => provider.managed && matches(provider));
1902
+ const available = providers.filter((provider) => !provider.managed && matches(provider));
1903
+ const visibleAvailable = showAllAvailable || normalizedQuery.length > 0 ? available : available.slice(0, 12);
1904
+ function toggle(provider) {
1905
+ setExpanded((current) => {
1906
+ const next = new Set(current);
1907
+ if (next.has(provider)) next.delete(provider);
1908
+ else next.add(provider);
1909
+ return next;
1910
+ });
1911
+ }
1912
+ const loading = snapshot.status === "loading" || snapshot.status === "idle" && state === null;
1913
+ const failed = snapshot.status === "error" || snapshot.status === "ready" && state === null;
1914
+ return h(
1915
+ "div",
1916
+ { className: "km-page" },
1917
+ snapshot.error !== null ? h(
1918
+ "div",
1919
+ { className: "km-banner km-banner-error", role: "alert" },
1920
+ h(Icon, { name: "warning", size: 16 }),
1921
+ h("span", { className: "km-banner-text" }, snapshot.error),
1922
+ h("button", { type: "button", className: "km-banner-close", onClick: () => runtime.store.dismissError(), "aria-label": t("dismiss") }, h(Icon, { name: "close", size: 14 }))
1923
+ ) : null,
1924
+ state !== null && state.warnings.length > 0 ? h(
1925
+ "div",
1926
+ { className: "km-banner km-banner-warn", role: "note" },
1927
+ h(Icon, { name: "warning", size: 16 }),
1928
+ h("div", { className: "km-banner-text" }, state.warnings.map((warning, index) => h("div", { key: index }, warning)))
1929
+ ) : null,
1930
+ state !== null && !state.writable ? h(
1931
+ "div",
1932
+ { className: "km-banner km-banner-warn", role: "note" },
1933
+ h(Icon, { name: "shield", size: 16 }),
1934
+ h("span", { className: "km-banner-text" }, t("writableFalse"))
1935
+ ) : null,
1936
+ h(
1937
+ "div",
1938
+ { className: "km-embedded-head" },
1939
+ h(
1940
+ "div",
1941
+ { className: "km-summary" },
1942
+ h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.managedCount ?? 0), h("span", null, t("managed"))),
1943
+ h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.keyCount ?? 0), h("span", null, t("keys"))),
1944
+ h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.healthyKeyCount ?? 0), h("span", null, t("healthy"))),
1945
+ h("div", { className: "km-stat" }, h("strong", null, loading ? "\u2014" : state?.coolingKeyCount ?? 0), h("span", null, t("cooling")))
1946
+ ),
1947
+ h(
1948
+ "div",
1949
+ { className: "km-embedded-actions" },
1950
+ h(Button, {
1951
+ variant: "ghost",
1952
+ size: "small",
1953
+ onClick: () => void runtime.store.load(),
1954
+ disabled: snapshot.busy !== null,
1955
+ title: t("refresh")
1956
+ }, h(Icon, { name: "refresh", size: 14 }), t("refresh"))
1957
+ )
1958
+ ),
1959
+ h(
1960
+ "div",
1961
+ { className: "km-search" },
1962
+ h(Icon, { name: "search", size: 17, className: "km-search-icon" }),
1963
+ h("input", {
1964
+ className: "km-search-input",
1965
+ value: query,
1966
+ onChange: (event) => setQuery(event.target.value),
1967
+ placeholder: t("search"),
1968
+ type: "search",
1969
+ autoFocus: true,
1970
+ "aria-label": t("search")
1971
+ }),
1972
+ query.length > 0 ? h("button", { type: "button", className: "km-search-clear", onClick: () => setQuery(""), "aria-label": t("reset") }, h(Icon, { name: "close", size: 14 })) : null
1973
+ ),
1974
+ loading ? h("div", { className: "km-loading" }, h(Icon, { name: "refresh", size: 18, className: "km-spin" }), t("loading")) : null,
1975
+ failed ? h(
1976
+ "div",
1977
+ { className: "km-load-failure" },
1978
+ h("div", { className: "km-empty-title" }, t("loadError")),
1979
+ h(Button, { variant: "primary", onClick: () => void runtime.store.load() }, t("reload"))
1980
+ ) : null,
1981
+ !loading && !failed ? h(
1982
+ "section",
1983
+ { className: "km-section" },
1984
+ h(
1985
+ "div",
1986
+ { className: "km-section-head" },
1987
+ h("h2", { className: "km-section-title" }, t("managed")),
1988
+ h("span", { className: "km-section-count" }, managed.length)
1989
+ ),
1990
+ managed.length === 0 ? h("div", { className: "km-empty" }, h(Icon, { name: "key", size: 20 }), h("span", null, t("managedEmpty"))) : h("div", { className: "km-provider-list" }, managed.map((provider) => h(ProviderCard, {
1991
+ key: provider.provider,
1992
+ provider,
1993
+ now,
1994
+ expanded: expanded.has(provider.provider),
1995
+ onToggle: () => toggle(provider.provider)
1996
+ })))
1997
+ ) : null,
1998
+ !loading && !failed ? h(
1999
+ "section",
2000
+ { className: "km-section" },
2001
+ h(
2002
+ "div",
2003
+ { className: "km-section-head" },
2004
+ h("h2", { className: "km-section-title" }, t("available")),
2005
+ h("span", { className: "km-section-count" }, available.length)
2006
+ ),
2007
+ available.length === 0 ? h("div", { className: "km-empty" }, h(Icon, { name: "search", size: 20 }), h("span", null, normalizedQuery.length > 0 ? t("noSearch") : t("availableEmpty"))) : h("div", { className: "km-provider-list" }, visibleAvailable.map((provider) => h(ProviderCard, {
2008
+ key: provider.provider,
2009
+ provider,
2010
+ now,
2011
+ expanded: expanded.has(provider.provider),
2012
+ onToggle: () => toggle(provider.provider)
2013
+ }))),
2014
+ available.length > visibleAvailable.length ? h(
2015
+ "div",
2016
+ { className: "km-show-more" },
2017
+ h(Button, { variant: "ghost", size: "small", onClick: () => setShowAllAvailable(true) }, t("showAll", { n: available.length }))
2018
+ ) : showAllAvailable && available.length > 12 ? h(
2019
+ "div",
2020
+ { className: "km-show-more" },
2021
+ h(Button, { variant: "ghost", size: "small", onClick: () => setShowAllAvailable(false) }, t("showLess"))
2022
+ ) : null
2023
+ ) : null,
2024
+ !loading && !failed ? h("section", { className: "km-section" }, h(SettingsCard)) : null,
2025
+ h(
2026
+ "footer",
2027
+ { className: "km-footer" },
2028
+ h(Icon, { name: "shield", size: 14 }),
2029
+ h("span", null, t("storageNote"))
2030
+ )
2031
+ );
2032
+ }
2033
+ function KeysManagerCard({ view }) {
2034
+ const t = useT();
2035
+ const snapshot = useStore();
2036
+ if (view === "summary") {
2037
+ const state = snapshot.state;
2038
+ if (state === null) return h("span", { className: "km-summary-line" }, snapshot.status === "error" ? t("loadError") : t("loading"));
2039
+ if (state.managedCount === 0) return h("span", { className: "km-summary-line" }, t("summaryEmpty"));
2040
+ return h("span", { className: "km-summary-line" }, t("summaryText", { providers: state.managedCount, keys: state.keyCount }));
2041
+ }
2042
+ return h(KeysManagerBody);
2043
+ }
2044
+ function ManagedProviderBadge(props) {
2045
+ const t = useT();
2046
+ const snapshot = useStore();
2047
+ const state = snapshot.state;
2048
+ const providerId = props.provider?.provider;
2049
+ if (state === null || providerId === void 0) return null;
2050
+ const managed = state.providers.find((provider) => provider.provider === providerId && provider.managed);
2051
+ if (managed === void 0) return null;
2052
+ const broken = managed.bindingHealthy === false;
2053
+ return h(
2054
+ "div",
2055
+ { className: cx("km-models-note", broken && "km-models-note-warn"), role: "note" },
2056
+ h("span", { className: cx("km-dot", broken ? "km-dot-warn" : "km-dot-success") }),
2057
+ h("span", { className: "km-models-note-text" }, t("modelsBadge", { n: managed.keyCount })),
2058
+ h("span", { className: "km-models-note-hint" }, broken ? t("bindingBroken") : t("modelsHint"))
2059
+ );
2060
+ }
2061
+
2062
+ // src/client/index.ts
2063
+ var inject = ["slots", "locale", "connection"];
2064
+ var NS = "dsh-keys-manager";
2065
+ function apply(ctx) {
2066
+ ctx.effect(() => ctx.locale.register(NS, LOCALE_DICTS), "keys-manager: dictionaries");
2067
+ const store = createStore(ctx);
2068
+ const runtime = { ctx, store };
2069
+ ctx.effect(() => {
2070
+ void store.load();
2071
+ let disposed = false;
2072
+ const retry = async () => {
2073
+ for (let attempt = 0; attempt < 4 && !disposed; attempt += 1) {
2074
+ if (store.getSnapshot().status === "ready") return;
2075
+ await new Promise((resolve) => setTimeout(resolve, 600));
2076
+ if (disposed) return;
2077
+ await store.load();
2078
+ }
2079
+ };
2080
+ void retry();
2081
+ const off = ctx.on("connection/reset", () => {
2082
+ void store.load();
2083
+ });
2084
+ const timer = setInterval(() => store.tick(), 3e4);
2085
+ return () => {
2086
+ disposed = true;
2087
+ off();
2088
+ clearInterval(timer);
2089
+ };
2090
+ }, "keys-manager: lifecycle");
2091
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
2092
+ name: "plugins.bundle.config",
2093
+ key: "dsh-keys-manager"
2094
+ }, wrapComponent(KeysManagerCard, runtime)));
2095
+ ctx.slots.inject("settings.models.provider-card", () => {
2096
+ const disposers = [
2097
+ ctx.slots.register({ name: "settings.models.provider-card", key: "llm-pi-ai" }, wrapComponent(ManagedProviderBadge, runtime)),
2098
+ ctx.slots.register({ name: "settings.models.provider-card", key: "llm-deepseek-api-key" }, wrapComponent(ManagedProviderBadge, runtime)),
2099
+ ctx.slots.register({ name: "settings.models.provider-card", key: "llm-deepseek" }, wrapComponent(ManagedProviderBadge, runtime))
2100
+ ];
2101
+ return () => {
2102
+ for (const dispose of disposers) dispose();
2103
+ };
2104
+ });
2105
+ }
2106
+ return module.exports; } });