@pentoshi/clai 4.11.21 → 4.11.22

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.
Files changed (205) hide show
  1. package/dist/agent/evidence/task-selection.d.ts +0 -15
  2. package/dist/agent/evidence/task-selection.js +1 -33
  3. package/dist/agent/evidence/task-selection.js.map +1 -1
  4. package/dist/agent/plan/actions/plan-clear.js.map +1 -1
  5. package/dist/agent/plan/actions/plan-create.js.map +1 -1
  6. package/dist/agent/plan/actions/task-add.js.map +1 -1
  7. package/dist/agent/plan/actions/task-move.js.map +1 -1
  8. package/dist/agent/plan/handle-plan-tool.js +5 -154
  9. package/dist/agent/plan/handle-plan-tool.js.map +1 -1
  10. package/dist/agent/runner.js +1 -13
  11. package/dist/agent/runner.js.map +1 -1
  12. package/dist/agent/session-policy.d.ts +0 -6
  13. package/dist/agent/session-policy.js +0 -2
  14. package/dist/agent/session-policy.js.map +1 -1
  15. package/dist/agent/task-evidence.d.ts +2 -2
  16. package/dist/agent/task-evidence.js +1 -1
  17. package/dist/agent/task-evidence.js.map +1 -1
  18. package/dist/agent/turn/compaction-admission.d.ts +5 -0
  19. package/dist/agent/turn/compaction-admission.js +24 -0
  20. package/dist/agent/turn/compaction-admission.js.map +1 -1
  21. package/dist/agent/turn/compaction-coordinator.d.ts +1 -0
  22. package/dist/agent/turn/compaction-coordinator.js +2 -0
  23. package/dist/agent/turn/compaction-coordinator.js.map +1 -1
  24. package/dist/agent/turn/loop/run-rounds.js +17 -24
  25. package/dist/agent/turn/loop/run-rounds.js.map +1 -1
  26. package/dist/agent/turn/loop/state.d.ts +1 -2
  27. package/dist/agent/turn/loop/state.js +1 -2
  28. package/dist/agent/turn/loop/state.js.map +1 -1
  29. package/dist/agent/turn/setup/compaction-services.d.ts +1 -0
  30. package/dist/agent/turn/setup/compaction-services.js +1 -0
  31. package/dist/agent/turn/setup/compaction-services.js.map +1 -1
  32. package/dist/agent/turn/setup/task-gate-setup.d.ts +1 -11
  33. package/dist/agent/turn/setup/task-gate-setup.js +4 -19
  34. package/dist/agent/turn/setup/task-gate-setup.js.map +1 -1
  35. package/dist/agent/turn/tool-execution/deps.d.ts +1 -5
  36. package/dist/agent/turn/tool-execution/guards.d.ts +0 -4
  37. package/dist/agent/turn/tool-execution/guards.js +3 -12
  38. package/dist/agent/turn/tool-execution/guards.js.map +1 -1
  39. package/dist/agent/turn/tool-execution/meta-tools.d.ts +0 -2
  40. package/dist/agent/turn/tool-execution/meta-tools.js +0 -17
  41. package/dist/agent/turn/tool-execution/meta-tools.js.map +1 -1
  42. package/dist/agent/turn/tool-execution/single-tool.js +0 -14
  43. package/dist/agent/turn/tool-execution/single-tool.js.map +1 -1
  44. package/dist/app/commands/catalog.js +32 -1
  45. package/dist/app/commands/catalog.js.map +1 -1
  46. package/dist/classic/panels/keys-panel.js +8 -1
  47. package/dist/classic/panels/keys-panel.js.map +1 -1
  48. package/dist/commands/providers.d.ts +17 -0
  49. package/dist/commands/providers.js +317 -28
  50. package/dist/commands/providers.js.map +1 -1
  51. package/dist/index.js +31 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/llm/agentrouter.js +1 -1
  54. package/dist/llm/anthropic.d.ts +4 -2
  55. package/dist/llm/anthropic.js +186 -178
  56. package/dist/llm/anthropic.js.map +1 -1
  57. package/dist/llm/bynara.js +1 -1
  58. package/dist/llm/capability/state.d.ts +1 -1
  59. package/dist/llm/capability/tool-dialect.js +3 -0
  60. package/dist/llm/capability/tool-dialect.js.map +1 -1
  61. package/dist/llm/capability/vision-patterns.js +3 -0
  62. package/dist/llm/capability/vision-patterns.js.map +1 -1
  63. package/dist/llm/catalog-facts.js +50 -2
  64. package/dist/llm/catalog-facts.js.map +1 -1
  65. package/dist/llm/catalog-prefetch.js +1 -1
  66. package/dist/llm/cline-auth.d.ts +34 -0
  67. package/dist/llm/cline-auth.js +248 -0
  68. package/dist/llm/cline-auth.js.map +1 -0
  69. package/dist/llm/cline.d.ts +2 -0
  70. package/dist/llm/cline.js +147 -0
  71. package/dist/llm/cline.js.map +1 -0
  72. package/dist/llm/codex-auth.d.ts +53 -0
  73. package/dist/llm/codex-auth.js +512 -0
  74. package/dist/llm/codex-auth.js.map +1 -0
  75. package/dist/llm/codex.d.ts +4 -0
  76. package/dist/llm/codex.js +239 -0
  77. package/dist/llm/codex.js.map +1 -0
  78. package/dist/llm/copilot-auth.d.ts +38 -0
  79. package/dist/llm/copilot-auth.js +258 -0
  80. package/dist/llm/copilot-auth.js.map +1 -0
  81. package/dist/llm/copilot.d.ts +4 -0
  82. package/dist/llm/copilot.js +226 -0
  83. package/dist/llm/copilot.js.map +1 -0
  84. package/dist/llm/custom-providers.js +1 -1
  85. package/dist/llm/deepseek.js +1 -1
  86. package/dist/llm/explabs.js +1 -1
  87. package/dist/llm/fireworks.js +1 -1
  88. package/dist/llm/free-default-model.js +9 -4
  89. package/dist/llm/free-default-model.js.map +1 -1
  90. package/dist/llm/free.js +1 -6
  91. package/dist/llm/free.js.map +1 -1
  92. package/dist/llm/glm.js +1 -1
  93. package/dist/llm/hetzner.js +1 -1
  94. package/dist/llm/kimi.js +1 -1
  95. package/dist/llm/lightning.js +1 -1
  96. package/dist/llm/merge-gateway.js +1 -1
  97. package/dist/llm/meta.js +1 -1
  98. package/dist/llm/minimax.js +1 -1
  99. package/dist/llm/model-families.js +1 -1
  100. package/dist/llm/model-families.js.map +1 -1
  101. package/dist/llm/model-layers/model-rules.js +6 -0
  102. package/dist/llm/model-layers/model-rules.js.map +1 -1
  103. package/dist/llm/nvidia.js +1 -1
  104. package/dist/llm/openrouter.js +1 -1
  105. package/dist/llm/orcarouter.js +1 -1
  106. package/dist/llm/provider-identity.d.ts +2 -0
  107. package/dist/llm/provider-identity.js +35 -0
  108. package/dist/llm/provider-identity.js.map +1 -1
  109. package/dist/llm/provider-info-text.js +110 -8
  110. package/dist/llm/provider-info-text.js.map +1 -1
  111. package/dist/llm/provider-profile-layers.js +101 -0
  112. package/dist/llm/provider-profile-layers.js.map +1 -1
  113. package/dist/llm/provider-profiles.js +2 -1
  114. package/dist/llm/provider-profiles.js.map +1 -1
  115. package/dist/llm/provider.d.ts +6 -4
  116. package/dist/llm/provider.js +2 -2
  117. package/dist/llm/provider.js.map +1 -1
  118. package/dist/llm/qwen-cloud.js +1 -1
  119. package/dist/llm/reasoning-capability.js +14 -0
  120. package/dist/llm/reasoning-capability.js.map +1 -1
  121. package/dist/llm/responses/item-events.js +10 -0
  122. package/dist/llm/responses/item-events.js.map +1 -1
  123. package/dist/llm/responses-config.d.ts +4 -0
  124. package/dist/llm/responses-config.js.map +1 -1
  125. package/dist/llm/responses-parse.js +23 -11
  126. package/dist/llm/responses-parse.js.map +1 -1
  127. package/dist/llm/responses-request.js +31 -8
  128. package/dist/llm/responses-request.js.map +1 -1
  129. package/dist/llm/responses-stream-events.js +21 -5
  130. package/dist/llm/responses-stream-events.js.map +1 -1
  131. package/dist/llm/routing/attempt-complete.js +1 -1
  132. package/dist/llm/routing/attempt-complete.js.map +1 -1
  133. package/dist/llm/routing/attempt-request.js +1 -1
  134. package/dist/llm/routing/attempt-request.js.map +1 -1
  135. package/dist/llm/routing/attempt-stream.js +2 -2
  136. package/dist/llm/routing/attempt-stream.js.map +1 -1
  137. package/dist/llm/routing/key-rotation.js +9 -4
  138. package/dist/llm/routing/key-rotation.js.map +1 -1
  139. package/dist/llm/routing/provider-selection.d.ts +2 -1
  140. package/dist/llm/routing/provider-selection.js +18 -4
  141. package/dist/llm/routing/provider-selection.js.map +1 -1
  142. package/dist/llm/tokenrouter.js +1 -1
  143. package/dist/llm/vercel.js +1 -1
  144. package/dist/llm/wire/chat-body.js +5 -3
  145. package/dist/llm/wire/chat-body.js.map +1 -1
  146. package/dist/llm/wire/openai-complete.d.ts +1 -0
  147. package/dist/llm/wire/openai-complete.js +9 -0
  148. package/dist/llm/wire/openai-complete.js.map +1 -1
  149. package/dist/store/config.js +3 -0
  150. package/dist/store/config.js.map +1 -1
  151. package/dist/store/keys/search-providers.d.ts +2 -0
  152. package/dist/store/keys/search-providers.js +6 -0
  153. package/dist/store/keys/search-providers.js.map +1 -1
  154. package/dist/store/keys.d.ts +6 -1
  155. package/dist/store/keys.js +46 -17
  156. package/dist/store/keys.js.map +1 -1
  157. package/dist/store/plan/mutation.d.ts +0 -1
  158. package/dist/store/plan/mutation.js +1 -22
  159. package/dist/store/plan/mutation.js.map +1 -1
  160. package/dist/store/plan.d.ts +1 -1
  161. package/dist/store/plan.js +1 -4
  162. package/dist/store/plan.js.map +1 -1
  163. package/dist/tui-v2/components/modal/keys-modal-pick.d.ts +5 -0
  164. package/dist/tui-v2/components/modal/keys-modal-pick.js +13 -0
  165. package/dist/tui-v2/components/modal/keys-modal-pick.js.map +1 -1
  166. package/dist/tui-v2/components/modal/keys-modal.js +15 -13
  167. package/dist/tui-v2/components/modal/keys-modal.js.map +1 -1
  168. package/dist/types.d.ts +1 -1
  169. package/dist/types.js +3 -0
  170. package/dist/types.js.map +1 -1
  171. package/dist/ui-core/commands/key-commands.d.ts +6 -3
  172. package/dist/ui-core/commands/key-commands.js +506 -15
  173. package/dist/ui-core/commands/key-commands.js.map +1 -1
  174. package/dist/ui-core/commands/keys/editors.js +10 -4
  175. package/dist/ui-core/commands/keys/editors.js.map +1 -1
  176. package/dist/ui-core/commands/picker-commands.js +86 -32
  177. package/dist/ui-core/commands/picker-commands.js.map +1 -1
  178. package/dist/ui-core/commands/subagent-models-editor.js +12 -4
  179. package/dist/ui-core/commands/subagent-models-editor.js.map +1 -1
  180. package/dist/ui-core/controllers/overlay-controller.d.ts +4 -0
  181. package/dist/ui-core/controllers/overlay-controller.js.map +1 -1
  182. package/dist/ui-core/rendering/format-keys.js +4 -2
  183. package/dist/ui-core/rendering/format-keys.js.map +1 -1
  184. package/dist/ui-core/rendering/format-usage.js +3 -1
  185. package/dist/ui-core/rendering/format-usage.js.map +1 -1
  186. package/dist/ui-core/rendering/intro-header.js +4 -1
  187. package/dist/ui-core/rendering/intro-header.js.map +1 -1
  188. package/dist/version.generated.d.ts +2 -2
  189. package/dist/version.generated.js +2 -2
  190. package/package.json +1 -1
  191. package/dist/agent/task-sync.d.ts +0 -40
  192. package/dist/agent/task-sync.js +0 -105
  193. package/dist/agent/task-sync.js.map +0 -1
  194. package/dist/agent/turn/task-batch-guard.d.ts +0 -18
  195. package/dist/agent/turn/task-batch-guard.js +0 -83
  196. package/dist/agent/turn/task-batch-guard.js.map +0 -1
  197. package/dist/agent/turn/task-gate.d.ts +0 -13
  198. package/dist/agent/turn/task-gate.js +0 -34
  199. package/dist/agent/turn/task-gate.js.map +0 -1
  200. package/dist/agent/turn/task-update-gate.d.ts +0 -13
  201. package/dist/agent/turn/task-update-gate.js +0 -40
  202. package/dist/agent/turn/task-update-gate.js.map +0 -1
  203. package/dist/store/task-transitions.d.ts +0 -12
  204. package/dist/store/task-transitions.js +0 -40
  205. package/dist/store/task-transitions.js.map +0 -1
@@ -1,6 +1,3 @@
1
- /**
2
- * Credential management: /set, /unset, /keys, /info (multi-key per provider).
3
- */
4
1
  import { getProvider } from "../../llm/router.js";
5
2
  import { assertProvider, getProviderInfoText, maskSecret, normalizeEndpointUrl, } from "../../llm/provider.js";
6
3
  import { appendProviderEndpoint, getConfig, getProviderEndpoints, providerUsesEndpoints, setProviderEndpoints, updateConfig } from "../../store/config.js";
@@ -8,6 +5,11 @@ import { appendSearchProviderKey, getProviderKeys, getSearchProviderKeys, listPr
8
5
  import { searchProviderIds } from "../../tools/web/types.js";
9
6
  import { formatKeyStatus } from "../rendering/format-keys.js";
10
7
  import { notice, openEndpointsEditor, openSearchKeysEditor, resolveEditorRowsDetailed } from "./keys/editors.js";
8
+ import { pollClineDeviceAuth, startClineDeviceAuth, } from "../../llm/cline-auth.js";
9
+ import { codexKeyFromAccessToken, encodeCodexKey, pollCodexDeviceAuth, startCodexBrowserAuth, startCodexDeviceAuth, } from "../../llm/codex-auth.js";
10
+ import { openSystemBrowser } from "../../mcp/auth/loopback.js";
11
+ import { pollCopilotDeviceAuth, startCopilotDeviceAuth, } from "../../llm/copilot-auth.js";
12
+ import { appendProviderKey, replaceProviderKey } from "../../store/keys.js";
11
13
  import { MAX_PROVIDER_KEYS } from "../../llm/key-rotation.js";
12
14
  const SEARCH_IDS = new Set(["brave", "tavily", "duckduckgo", "exa"]);
13
15
  async function getSearchKeyStatuses() {
@@ -114,7 +116,7 @@ async function openSetPicker(services) {
114
116
  : `✓ ${count} keys`;
115
117
  const row = {
116
118
  value: `llm:${status.provider}`,
117
- label: `${status.provider} ${keyLabel}${status.active ? " (active)" : ""}`,
119
+ label: `${getProvider(status.provider).displayName} ${keyLabel}${status.active ? " (active)" : ""}`,
118
120
  };
119
121
  if (!status.endpoints)
120
122
  return [row];
@@ -123,7 +125,7 @@ async function openSetPicker(services) {
123
125
  row,
124
126
  {
125
127
  value: `endpoint:${status.provider}`,
126
- label: `${status.provider} endpoints ${urlCount === 0 ? "✗ none" : `✓ ${urlCount} URL${urlCount === 1 ? "" : "s"}`}`,
128
+ label: `${getProvider(status.provider).displayName} endpoints ${urlCount === 0 ? "✗ none" : `✓ ${urlCount} URL${urlCount === 1 ? "" : "s"}`}`,
127
129
  description: status.note ?? "endpoint URLs",
128
130
  },
129
131
  ];
@@ -175,7 +177,7 @@ async function openUnsetPicker(services) {
175
177
  : `✓ ${count} keys — reset all`;
176
178
  const row = {
177
179
  value: `llm:${status.provider}`,
178
- label: `${status.provider} ${keyLabel}${status.active ? " (active)" : ""}`,
180
+ label: `${getProvider(status.provider).displayName} ${keyLabel}${status.active ? " (active)" : ""}`,
179
181
  };
180
182
  if (!status.endpoints || status.endpoints.length === 0)
181
183
  return [row];
@@ -183,7 +185,7 @@ async function openUnsetPicker(services) {
183
185
  row,
184
186
  {
185
187
  value: `endpoint:${status.provider}`,
186
- label: `${status.provider} endpoints ✓ ${status.endpoints.length} URL${status.endpoints.length === 1 ? "" : "s"} — clear all`,
188
+ label: `${getProvider(status.provider).displayName} endpoints ✓ ${status.endpoints.length} URL${status.endpoints.length === 1 ? "" : "s"} — clear all`,
187
189
  description: status.note ?? "endpoint URLs",
188
190
  },
189
191
  ];
@@ -215,7 +217,7 @@ async function openUnsetPicker(services) {
215
217
  else if (kind === "endpoint") {
216
218
  const count = getProviderEndpoints(id).urls.length;
217
219
  setProviderEndpoints(id, []);
218
- notice(services, "info", `unset ${count} endpoint URL(s) for ${id}`);
220
+ notice(services, "info", `unset ${count} endpoint URL(s) for ${getProvider(id).displayName}`);
219
221
  }
220
222
  else
221
223
  await unsetLlmKey(services, id);
@@ -247,7 +249,7 @@ async function appendLlmKey(services, id, keyVal) {
247
249
  const endpoint = normalizeEndpointUrl(keyVal.trim());
248
250
  try {
249
251
  const { endpoints, added } = appendProviderEndpoint(id, endpoint);
250
- notice(services, "info", `${added ? "saved" : "activated"} ${id} endpoint #${endpoints.activeIndex + 1}/${endpoints.urls.length} → ${endpoint}`);
252
+ notice(services, "info", `${added ? "saved" : "activated"} ${getProvider(id).displayName} endpoint #${endpoints.activeIndex + 1}/${endpoints.urls.length} → ${endpoint}`);
251
253
  }
252
254
  catch (error) {
253
255
  notice(services, "warn", error instanceof Error ? error.message : String(error));
@@ -255,10 +257,11 @@ async function appendLlmKey(services, id, keyVal) {
255
257
  return;
256
258
  }
257
259
  const key = keyVal.trim();
260
+ const label = getProvider(id).displayName;
258
261
  if (!getProvider(id).validateKey(key)) {
259
262
  notice(services, "warn", providerUsesEndpoints(id)
260
- ? `invalid value for ${id} · expected a key/token, or an https:// URL to add an endpoint`
261
- : `invalid API key format for ${id}`);
263
+ ? `invalid value for ${label} · expected a key/token, or an https:// URL to add an endpoint`
264
+ : `invalid API key format for ${label}`);
262
265
  return;
263
266
  }
264
267
  const { appendProviderKey } = await import("../../store/keys.js");
@@ -266,8 +269,8 @@ async function appendLlmKey(services, id, keyVal) {
266
269
  const multi = await getProviderKeys(id);
267
270
  const count = multi.source === "env" ? 1 : multi.keys.length;
268
271
  notice(services, "info", count > 1
269
- ? `added ${id} ${maskSecret(key)} · ${count} keys total`
270
- : `saved ${id} ${maskSecret(key)}`);
272
+ ? `added ${label} ${maskSecret(key)} · ${count} keys total`
273
+ : `saved ${label} ${maskSecret(key)}`);
271
274
  }
272
275
  async function setSearchKey(services, id, keyVal) {
273
276
  if (id === "duckduckgo") {
@@ -309,14 +312,488 @@ async function unsetLlmKey(services, id) {
309
312
  notice(services, "info", "ollama does not store an API key");
310
313
  return;
311
314
  }
315
+ const label = getProvider(id).displayName;
312
316
  const multi = await getProviderKeys(id);
313
317
  const storedCount = multi.source === "env" ? 0 : multi.keys.length;
314
318
  if (storedCount === 0) {
315
- notice(services, "warn", `${id} has no key to unset`);
319
+ notice(services, "warn", `${label} has no key to unset`);
316
320
  return;
317
321
  }
318
322
  await unsetProviderSecret(id);
319
- notice(services, "info", storedCount > 1 ? `unset all ${storedCount} keys for ${id}` : `unset ${id}`);
323
+ notice(services, "info", storedCount > 1 ? `unset all ${storedCount} keys for ${label}` : `unset ${label}`);
324
+ }
325
+ export async function runClineAuthForUI(services) {
326
+ let start;
327
+ try {
328
+ start = await startClineDeviceAuth();
329
+ }
330
+ catch (error) {
331
+ notice(services, "warn", `could not start Cline sign-in: ${error instanceof Error ? error.message : String(error)}`);
332
+ return undefined;
333
+ }
334
+ services.overlay.openPager("Cline sign-in", [
335
+ "Authenticate Cline on any device:",
336
+ "",
337
+ ` ${start.verificationUrl}`,
338
+ "",
339
+ ` Code: ${start.userCode}`,
340
+ "",
341
+ "Approve in your browser — clai will continue automatically.",
342
+ "(close this and press Ctrl-C to cancel)",
343
+ ].join("\n"), undefined, undefined, "plain");
344
+ const waiting = services.toast.info("waiting for Cline approval…", {
345
+ sticky: true,
346
+ });
347
+ try {
348
+ const tokens = await pollClineDeviceAuth(start);
349
+ services.toast.dismiss(waiting);
350
+ notice(services, "info", "Cline authenticated");
351
+ return tokens;
352
+ }
353
+ catch (error) {
354
+ services.toast.dismiss(waiting);
355
+ notice(services, "warn", `Cline sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
356
+ return undefined;
357
+ }
358
+ }
359
+ async function clineAddAccount(services) {
360
+ const tokens = await runClineAuthForUI(services);
361
+ if (!tokens) {
362
+ notice(services, "info", "cancelled");
363
+ return undefined;
364
+ }
365
+ return tokens;
366
+ }
367
+ async function promptCodexApiKey(services) {
368
+ const answer = await services.overlay.openSecret({
369
+ title: "ChatGPT Subscription access token",
370
+ prompt: "Paste a ChatGPT access token (JWT from auth.openai.com) or an existing `codex:` key:",
371
+ });
372
+ const value = answer?.trim();
373
+ if (!value)
374
+ return undefined;
375
+ const key = codexKeyFromAccessToken(value) ?? (value.startsWith("codex:") ? value : undefined);
376
+ if (!key) {
377
+ notice(services, "warn", "invalid ChatGPT token — expected a JWT containing the chatgpt account id");
378
+ return undefined;
379
+ }
380
+ return key;
381
+ }
382
+ function pickCodexAuthMethod(services) {
383
+ return new Promise((resolve) => {
384
+ const opened = services.overlay.openPicker({
385
+ title: "ChatGPT Subscription sign-in method",
386
+ options: [
387
+ {
388
+ value: "browser",
389
+ label: "Sign in with ChatGPT (browser)",
390
+ description: "opens auth.openai.com in your browser",
391
+ },
392
+ {
393
+ value: "headless",
394
+ label: "Sign in with ChatGPT (headless)",
395
+ description: "device code — works on remote/SSH machines",
396
+ },
397
+ {
398
+ value: "apikey",
399
+ label: "Manually enter access token / API key",
400
+ description: "paste an existing Chatgpt Subscription token",
401
+ },
402
+ ],
403
+ }, (value) => {
404
+ services.overlay.close();
405
+ resolve(value);
406
+ });
407
+ if (!opened)
408
+ resolve(undefined);
409
+ });
410
+ }
411
+ export async function runCodexAuthForUI(services) {
412
+ const method = await pickCodexAuthMethod(services);
413
+ if (method === "headless")
414
+ return runCodexDeviceAuthForUI(services);
415
+ if (method === "apikey") {
416
+ const key = await promptCodexApiKey(services);
417
+ return key
418
+ ? { manualKey: key, accessToken: "", accountId: "" }
419
+ : undefined;
420
+ }
421
+ if (method !== "browser")
422
+ return undefined;
423
+ let handle;
424
+ try {
425
+ handle = await startCodexBrowserAuth();
426
+ }
427
+ catch {
428
+ return runCodexDeviceAuthForUI(services);
429
+ }
430
+ services.overlay.openPager("ChatGPT Subscription sign-in", [
431
+ "Opening ChatGPT sign-in in your browser…",
432
+ "",
433
+ ` ${handle.url}`,
434
+ "",
435
+ "Log in with your ChatGPT account in the browser tab that opens.",
436
+ "clai will continue automatically once authorization completes.",
437
+ "(close this and press Ctrl-C to cancel)",
438
+ ].join("\n"), undefined, undefined, "plain");
439
+ const waiting = services.toast.info("waiting for ChatGPT approval…", {
440
+ sticky: true,
441
+ });
442
+ try {
443
+ await openSystemBrowser(handle.url).catch(() => { });
444
+ const credential = await handle.waitForCredential();
445
+ services.toast.dismiss(waiting);
446
+ notice(services, "info", "ChatGPT Subscription authenticated");
447
+ return credential;
448
+ }
449
+ catch (error) {
450
+ services.toast.dismiss(waiting);
451
+ notice(services, "warn", `ChatGPT Subscription sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
452
+ return undefined;
453
+ }
454
+ }
455
+ export async function runCodexDeviceAuthForUI(services) {
456
+ let start;
457
+ try {
458
+ start = await startCodexDeviceAuth();
459
+ }
460
+ catch (error) {
461
+ notice(services, "warn", `could not start ChatGPT Subscription sign-in: ${error instanceof Error ? error.message : String(error)}`);
462
+ return undefined;
463
+ }
464
+ services.overlay.openPager("ChatGPT Subscription sign-in", [
465
+ "Authenticate ChatGPT Subscription on any device:",
466
+ "",
467
+ ` ${start.verificationUrl}`,
468
+ "",
469
+ ` Code: ${start.userCode}`,
470
+ "",
471
+ "Approve in your browser — clai will continue automatically.",
472
+ "(close this and press Ctrl-C to cancel)",
473
+ ].join("\n"), undefined, undefined, "plain");
474
+ const waiting = services.toast.info("waiting for ChatGPT approval…", {
475
+ sticky: true,
476
+ });
477
+ try {
478
+ const credential = await pollCodexDeviceAuth(start);
479
+ services.toast.dismiss(waiting);
480
+ notice(services, "info", "ChatGPT Subscription authenticated");
481
+ return credential;
482
+ }
483
+ catch (error) {
484
+ services.toast.dismiss(waiting);
485
+ notice(services, "warn", `ChatGPT Subscription sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
486
+ return undefined;
487
+ }
488
+ }
489
+ export async function runCopilotAuthForUI(services) {
490
+ let start;
491
+ try {
492
+ start = await startCopilotDeviceAuth();
493
+ }
494
+ catch (error) {
495
+ notice(services, "warn", `could not start Github Copilot sign-in: ${error instanceof Error ? error.message : String(error)}`);
496
+ return undefined;
497
+ }
498
+ services.overlay.openPager("Github Copilot sign-in", [
499
+ "Authenticate Github Copilot on any device:",
500
+ "",
501
+ ` ${start.verificationUrl}`,
502
+ "",
503
+ ` Code: ${start.userCode}`,
504
+ "",
505
+ "Approve in your browser — clai will continue automatically.",
506
+ "(close this and press Ctrl-C to cancel)",
507
+ ].join("\n"), undefined, undefined, "plain");
508
+ const waiting = services.toast.info("waiting for Github Copilot approval…", {
509
+ sticky: true,
510
+ });
511
+ try {
512
+ const token = await pollCopilotDeviceAuth(start);
513
+ services.toast.dismiss(waiting);
514
+ notice(services, "info", "Github Copilot authenticated");
515
+ return token;
516
+ }
517
+ catch (error) {
518
+ services.toast.dismiss(waiting);
519
+ notice(services, "warn", `Github Copilot sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
520
+ return undefined;
521
+ }
522
+ }
523
+ async function loadClineKeys() {
524
+ const multi = await getProviderKeys("cline");
525
+ return {
526
+ keys: multi.source === "env" ? [] : multi.keys,
527
+ activeIndex: multi.source === "env" ? 0 : multi.activeIndex,
528
+ };
529
+ }
530
+ async function storeClineAccount(services, tokens) {
531
+ if (tokens.refreshToken || tokens.expiresAt !== undefined) {
532
+ await appendProviderKey("cline", tokens.accessToken, {
533
+ ...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
534
+ ...(tokens.expiresAt !== undefined ? { expiresAt: tokens.expiresAt } : {}),
535
+ });
536
+ }
537
+ else {
538
+ await appendProviderKey("cline", tokens.accessToken);
539
+ }
540
+ notice(services, "info", `added Cline account ${maskSecret(tokens.accessToken)}`);
541
+ }
542
+ async function refreshClineAccount(services, oldValue) {
543
+ const tokens = await runClineAuthForUI(services);
544
+ if (!tokens) {
545
+ notice(services, "info", "cancelled");
546
+ return;
547
+ }
548
+ const replaced = await replaceProviderKey("cline", oldValue, tokens.accessToken, {
549
+ ...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
550
+ ...(tokens.expiresAt !== undefined ? { expiresAt: tokens.expiresAt } : {}),
551
+ });
552
+ if (!replaced) {
553
+ notice(services, "warn", "Cline account was not found");
554
+ return;
555
+ }
556
+ notice(services, "info", `refreshed Cline account ${maskSecret(tokens.accessToken)}`);
557
+ }
558
+ async function saveClineKeys(services, answer, keys, activeIndex) {
559
+ const byId = new Map(keys.map((key) => [key.id, key.value]));
560
+ const detailed = resolveEditorRowsDetailed(answer.rows, byId);
561
+ const resolved = detailed.map((row) => row.value);
562
+ if (resolved.length === 0) {
563
+ await unsetProviderSecret("cline");
564
+ notice(services, "info", "unset all keys for cline");
565
+ return;
566
+ }
567
+ for (const key of resolved) {
568
+ if (!getProvider("cline").validateKey(key)) {
569
+ notice(services, "warn", "invalid Cline token");
570
+ return;
571
+ }
572
+ }
573
+ if (resolved.length > MAX_PROVIDER_KEYS) {
574
+ notice(services, "warn", `at most ${MAX_PROVIDER_KEYS} Cline accounts`);
575
+ return;
576
+ }
577
+ await setProviderKeys("cline", resolved, answer.activeIndex ?? activeIndex, detailed.filter((row) => row.disabled).map((row) => row.value));
578
+ const index = answer.activeIndex ?? activeIndex;
579
+ notice(services, "info", resolved.length === 1
580
+ ? `saved cline · ${maskSecret(resolved[0])}`
581
+ : `saved cline · ${resolved.length} accounts · active: #${index + 1}`);
582
+ }
583
+ async function openClineKeysFlow(services) {
584
+ let { keys, activeIndex } = await loadClineKeys();
585
+ for (;;) {
586
+ services.overlay.close();
587
+ const answer = await services.overlay.openKeysEditor({
588
+ provider: "cline",
589
+ heading: "CLINE ACCOUNTS",
590
+ itemLabel: "account",
591
+ addViaPicker: true,
592
+ refreshable: true,
593
+ initialKeys: keys.map((key) => ({
594
+ id: key.id,
595
+ masked: maskSecret(key.value),
596
+ disabled: key.disabled === true,
597
+ })),
598
+ activeIndex,
599
+ });
600
+ if (!answer) {
601
+ notice(services, "info", "cancelled");
602
+ return;
603
+ }
604
+ if (answer.action === "reset") {
605
+ await unsetProviderSecret("cline");
606
+ notice(services, "info", "unset all keys for cline");
607
+ return;
608
+ }
609
+ if (answer.action === "pick") {
610
+ const tokens = await clineAddAccount(services);
611
+ if (tokens)
612
+ await storeClineAccount(services, tokens);
613
+ ({ keys, activeIndex } = await loadClineKeys());
614
+ continue;
615
+ }
616
+ if (answer.action === "refresh") {
617
+ const selected = keys.find((key) => key.id === answer.slotId);
618
+ if (selected) {
619
+ await refreshClineAccount(services, selected.value);
620
+ }
621
+ else {
622
+ notice(services, "warn", "Cline account was not found");
623
+ }
624
+ ({ keys, activeIndex } = await loadClineKeys());
625
+ continue;
626
+ }
627
+ await saveClineKeys(services, answer, keys, activeIndex);
628
+ return;
629
+ }
630
+ }
631
+ async function loadOAuthKeys(provider) {
632
+ const multi = await getProviderKeys(provider);
633
+ return {
634
+ keys: multi.source === "env" ? [] : multi.keys,
635
+ activeIndex: multi.source === "env" ? 0 : multi.activeIndex,
636
+ };
637
+ }
638
+ async function saveOAuthKeys(services, provider, answer, keys, activeIndex) {
639
+ const label = getProvider(provider).displayName;
640
+ const byId = new Map(keys.map((key) => [key.id, key.value]));
641
+ const detailed = resolveEditorRowsDetailed(answer.rows, byId);
642
+ const resolved = detailed.map((row) => row.value);
643
+ if (resolved.length === 0) {
644
+ await unsetProviderSecret(provider);
645
+ notice(services, "info", `unset all keys for ${label}`);
646
+ return;
647
+ }
648
+ for (const key of resolved) {
649
+ if (!getProvider(provider).validateKey(key)) {
650
+ notice(services, "warn", `invalid ${label} token`);
651
+ return;
652
+ }
653
+ }
654
+ if (resolved.length > MAX_PROVIDER_KEYS) {
655
+ notice(services, "warn", `at most ${MAX_PROVIDER_KEYS} ${label} accounts`);
656
+ return;
657
+ }
658
+ await setProviderKeys(provider, resolved, answer.activeIndex ?? activeIndex, detailed.filter((row) => row.disabled).map((row) => row.value));
659
+ const index = answer.activeIndex ?? activeIndex;
660
+ notice(services, "info", resolved.length === 1
661
+ ? `saved ${label} · ${maskSecret(resolved[0])}`
662
+ : `saved ${label} · ${resolved.length} accounts · active: #${index + 1}`);
663
+ }
664
+ async function openCodexKeysFlow(services) {
665
+ let { keys, activeIndex } = await loadOAuthKeys("codex");
666
+ for (;;) {
667
+ services.overlay.close();
668
+ const answer = await services.overlay.openKeysEditor({
669
+ provider: "codex",
670
+ heading: "CHATGPT SUBSCRIPTION ACCOUNTS",
671
+ itemLabel: "account",
672
+ addViaPicker: true,
673
+ refreshable: true,
674
+ initialKeys: keys.map((key) => ({
675
+ id: key.id,
676
+ masked: maskSecret(key.value),
677
+ disabled: key.disabled === true,
678
+ })),
679
+ activeIndex,
680
+ });
681
+ if (!answer) {
682
+ notice(services, "info", "cancelled");
683
+ return;
684
+ }
685
+ if (answer.action === "reset") {
686
+ await unsetProviderSecret("codex");
687
+ notice(services, "info", "unset all keys for Chatgpt Subscription");
688
+ return;
689
+ }
690
+ if (answer.action === "pick") {
691
+ const credential = await runCodexAuthForUI(services);
692
+ const manualKey = credential?.manualKey;
693
+ if (credential) {
694
+ const key = manualKey ?? encodeCodexKey(credential);
695
+ await appendProviderKey("codex", key);
696
+ notice(services, "info", `added ChatGPT Subscription account ${maskSecret(key)}`);
697
+ }
698
+ else {
699
+ notice(services, "info", "cancelled");
700
+ }
701
+ ({ keys, activeIndex } = await loadOAuthKeys("codex"));
702
+ continue;
703
+ }
704
+ if (answer.action === "refresh") {
705
+ const selected = keys.find((key) => key.id === answer.slotId);
706
+ if (selected) {
707
+ const credential = await runCodexAuthForUI(services);
708
+ const manualKey = credential?.manualKey;
709
+ if (credential) {
710
+ const key = manualKey ?? encodeCodexKey(credential);
711
+ const replaced = await replaceProviderKey("codex", selected.value, key);
712
+ if (!replaced) {
713
+ notice(services, "warn", "ChatGPT Subscription account was not found");
714
+ }
715
+ else {
716
+ notice(services, "info", `refreshed ChatGPT Subscription account ${maskSecret(key)}`);
717
+ }
718
+ }
719
+ else {
720
+ notice(services, "info", "cancelled");
721
+ }
722
+ }
723
+ else {
724
+ notice(services, "warn", "ChatGPT Subscription account was not found");
725
+ }
726
+ ({ keys, activeIndex } = await loadOAuthKeys("codex"));
727
+ continue;
728
+ }
729
+ await saveOAuthKeys(services, "codex", answer, keys, activeIndex);
730
+ return;
731
+ }
732
+ }
733
+ async function openCopilotKeysFlow(services) {
734
+ let { keys, activeIndex } = await loadOAuthKeys("copilot");
735
+ for (;;) {
736
+ services.overlay.close();
737
+ const answer = await services.overlay.openKeysEditor({
738
+ provider: "copilot",
739
+ heading: "GITHUB COPILOT ACCOUNTS",
740
+ itemLabel: "account",
741
+ addViaPicker: true,
742
+ refreshable: true,
743
+ initialKeys: keys.map((key) => ({
744
+ id: key.id,
745
+ masked: maskSecret(key.value),
746
+ disabled: key.disabled === true,
747
+ })),
748
+ activeIndex,
749
+ });
750
+ if (!answer) {
751
+ notice(services, "info", "cancelled");
752
+ return;
753
+ }
754
+ if (answer.action === "reset") {
755
+ await unsetProviderSecret("copilot");
756
+ notice(services, "info", "unset all keys for Github Copilot");
757
+ return;
758
+ }
759
+ if (answer.action === "pick") {
760
+ const token = await runCopilotAuthForUI(services);
761
+ if (token) {
762
+ await appendProviderKey("copilot", token);
763
+ notice(services, "info", `added Github Copilot account ${maskSecret(token)}`);
764
+ }
765
+ else {
766
+ notice(services, "info", "cancelled");
767
+ }
768
+ ({ keys, activeIndex } = await loadOAuthKeys("copilot"));
769
+ continue;
770
+ }
771
+ if (answer.action === "refresh") {
772
+ const selected = keys.find((key) => key.id === answer.slotId);
773
+ if (selected) {
774
+ const token = await runCopilotAuthForUI(services);
775
+ if (token) {
776
+ const replaced = await replaceProviderKey("copilot", selected.value, token);
777
+ if (!replaced) {
778
+ notice(services, "warn", "Github Copilot account was not found");
779
+ }
780
+ else {
781
+ notice(services, "info", `refreshed Github Copilot account ${maskSecret(token)}`);
782
+ }
783
+ }
784
+ else {
785
+ notice(services, "info", "cancelled");
786
+ }
787
+ }
788
+ else {
789
+ notice(services, "warn", "Github Copilot account was not found");
790
+ }
791
+ ({ keys, activeIndex } = await loadOAuthKeys("copilot"));
792
+ continue;
793
+ }
794
+ await saveOAuthKeys(services, "copilot", answer, keys, activeIndex);
795
+ return;
796
+ }
320
797
  }
321
798
  export async function openLlmKeysEditor(services, id) {
322
799
  if (id === "ollama") {
@@ -333,6 +810,18 @@ export async function openLlmKeysEditor(services, id) {
333
810
  notice(services, "info", `saved ollama host → ${host.trim()}`);
334
811
  return;
335
812
  }
813
+ if (id === "cline") {
814
+ await openClineKeysFlow(services);
815
+ return;
816
+ }
817
+ if (id === "codex") {
818
+ await openCodexKeysFlow(services);
819
+ return;
820
+ }
821
+ if (id === "copilot") {
822
+ await openCopilotKeysFlow(services);
823
+ return;
824
+ }
336
825
  if (providerUsesEndpoints(id)) {
337
826
  await openEndpointsEditor(services, id);
338
827
  }
@@ -359,6 +848,8 @@ export async function openLlmKeysEditor(services, id) {
359
848
  notice(services, "info", `unset all keys for ${id}`);
360
849
  return;
361
850
  }
851
+ if (answer.action === "refresh")
852
+ return;
362
853
  const byId = new Map(stored.map((key) => [key.id, key.value]));
363
854
  const detailed = resolveEditorRowsDetailed(answer.rows, byId);
364
855
  const resolved = detailed.map((row) => row.value);