@pentoshi/clai 4.11.24 → 4.11.26

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 (143) hide show
  1. package/README.md +91 -34
  2. package/dist/agent/stream-recovery.d.ts +1 -0
  3. package/dist/agent/stream-recovery.js +9 -0
  4. package/dist/agent/stream-recovery.js.map +1 -1
  5. package/dist/agent/turn/compaction-coordinator.js +2 -2
  6. package/dist/agent/turn/compaction-coordinator.js.map +1 -1
  7. package/dist/agent/turn/loop/stream-failure.js +4 -1
  8. package/dist/agent/turn/loop/stream-failure.js.map +1 -1
  9. package/dist/app/adapters/agent-event-adapter.js +1 -0
  10. package/dist/app/adapters/agent-event-adapter.js.map +1 -1
  11. package/dist/app/commands/catalog.js +32 -1
  12. package/dist/app/commands/catalog.js.map +1 -1
  13. package/dist/app/controllers/session-compact-helper.js +6 -8
  14. package/dist/app/controllers/session-compact-helper.js.map +1 -1
  15. package/dist/app/controllers/session-controller.d.ts +1 -0
  16. package/dist/app/controllers/session-controller.js +3 -9
  17. package/dist/app/controllers/session-controller.js.map +1 -1
  18. package/dist/app/controllers/session-usage-ledger.d.ts +7 -1
  19. package/dist/app/controllers/session-usage-ledger.js +76 -2
  20. package/dist/app/controllers/session-usage-ledger.js.map +1 -1
  21. package/dist/app/events/app-event.d.ts +2 -1
  22. package/dist/app/events/app-event.js.map +1 -1
  23. package/dist/commands/providers.d.ts +14 -0
  24. package/dist/commands/providers.js +223 -0
  25. package/dist/commands/providers.js.map +1 -1
  26. package/dist/index.js +13 -3
  27. package/dist/index.js.map +1 -1
  28. package/dist/llm/capabilities.js +10 -3
  29. package/dist/llm/capabilities.js.map +1 -1
  30. package/dist/llm/capability/state.d.ts +1 -1
  31. package/dist/llm/capability/tool-dialect.js +2 -0
  32. package/dist/llm/capability/tool-dialect.js.map +1 -1
  33. package/dist/llm/capability/vision-patterns.js +5 -0
  34. package/dist/llm/capability/vision-patterns.js.map +1 -1
  35. package/dist/llm/cline-auth.d.ts +12 -1
  36. package/dist/llm/cline-auth.js +229 -88
  37. package/dist/llm/cline-auth.js.map +1 -1
  38. package/dist/llm/cline.js +146 -48
  39. package/dist/llm/cline.js.map +1 -1
  40. package/dist/llm/codex-auth.d.ts +3 -1
  41. package/dist/llm/codex-auth.js +42 -8
  42. package/dist/llm/codex-auth.js.map +1 -1
  43. package/dist/llm/codex.js +17 -14
  44. package/dist/llm/codex.js.map +1 -1
  45. package/dist/llm/context-windows.js +6 -0
  46. package/dist/llm/context-windows.js.map +1 -1
  47. package/dist/llm/free.js +30 -9
  48. package/dist/llm/free.js.map +1 -1
  49. package/dist/llm/kiro-auth.d.ts +145 -0
  50. package/dist/llm/kiro-auth.js +1179 -0
  51. package/dist/llm/kiro-auth.js.map +1 -0
  52. package/dist/llm/kiro.d.ts +14 -0
  53. package/dist/llm/kiro.js +909 -0
  54. package/dist/llm/kiro.js.map +1 -0
  55. package/dist/llm/mimo.d.ts +2 -0
  56. package/dist/llm/mimo.js +89 -0
  57. package/dist/llm/mimo.js.map +1 -0
  58. package/dist/llm/model-families.js +9 -0
  59. package/dist/llm/model-families.js.map +1 -1
  60. package/dist/llm/model-layers/model-rules.js +54 -0
  61. package/dist/llm/model-layers/model-rules.js.map +1 -1
  62. package/dist/llm/provider-balance.d.ts +31 -0
  63. package/dist/llm/provider-balance.js +118 -0
  64. package/dist/llm/provider-balance.js.map +1 -0
  65. package/dist/llm/provider-identity.d.ts +1 -0
  66. package/dist/llm/provider-identity.js +15 -0
  67. package/dist/llm/provider-identity.js.map +1 -1
  68. package/dist/llm/provider-info-text.js +99 -1
  69. package/dist/llm/provider-info-text.js.map +1 -1
  70. package/dist/llm/provider-profile-layers.d.ts +1 -0
  71. package/dist/llm/provider-profile-layers.js +67 -1
  72. package/dist/llm/provider-profile-layers.js.map +1 -1
  73. package/dist/llm/provider-profiles.js +5 -1
  74. package/dist/llm/provider-profiles.js.map +1 -1
  75. package/dist/llm/provider.d.ts +1 -0
  76. package/dist/llm/provider.js.map +1 -1
  77. package/dist/llm/reasoning-capability.js +2 -0
  78. package/dist/llm/reasoning-capability.js.map +1 -1
  79. package/dist/llm/reasoning-errors.d.ts +1 -0
  80. package/dist/llm/reasoning-errors.js +10 -1
  81. package/dist/llm/reasoning-errors.js.map +1 -1
  82. package/dist/llm/responses-complete.js +7 -1
  83. package/dist/llm/responses-complete.js.map +1 -1
  84. package/dist/llm/responses-config.d.ts +1 -1
  85. package/dist/llm/responses-config.js.map +1 -1
  86. package/dist/llm/responses-http.d.ts +2 -2
  87. package/dist/llm/responses-http.js +2 -2
  88. package/dist/llm/responses-http.js.map +1 -1
  89. package/dist/llm/responses-stream.js +9 -3
  90. package/dist/llm/responses-stream.js.map +1 -1
  91. package/dist/llm/router.js +14 -12
  92. package/dist/llm/router.js.map +1 -1
  93. package/dist/llm/routing/provider-selection.js +6 -0
  94. package/dist/llm/routing/provider-selection.js.map +1 -1
  95. package/dist/llm/sampling.js +6 -0
  96. package/dist/llm/sampling.js.map +1 -1
  97. package/dist/llm/wire/chat-body.js +47 -12
  98. package/dist/llm/wire/chat-body.js.map +1 -1
  99. package/dist/noninteractive/stream-blocks.js +3 -0
  100. package/dist/noninteractive/stream-blocks.js.map +1 -1
  101. package/dist/store/config.js +2 -0
  102. package/dist/store/config.js.map +1 -1
  103. package/dist/store/history/jsonl-backend.js +2 -1
  104. package/dist/store/history/jsonl-backend.js.map +1 -1
  105. package/dist/store/history/jsonl-lock.js +23 -3
  106. package/dist/store/history/jsonl-lock.js.map +1 -1
  107. package/dist/store/history/lifecycle.js +3 -2
  108. package/dist/store/history/lifecycle.js.map +1 -1
  109. package/dist/types.d.ts +8 -1
  110. package/dist/types.js +2 -0
  111. package/dist/types.js.map +1 -1
  112. package/dist/ui-core/bootstrap/composition-root.js +1 -0
  113. package/dist/ui-core/bootstrap/composition-root.js.map +1 -1
  114. package/dist/ui-core/bootstrap/session-resume.d.ts +3 -0
  115. package/dist/ui-core/bootstrap/session-resume.js +52 -0
  116. package/dist/ui-core/bootstrap/session-resume.js.map +1 -1
  117. package/dist/ui-core/commands/key-commands.d.ts +9 -0
  118. package/dist/ui-core/commands/key-commands.js +438 -16
  119. package/dist/ui-core/commands/key-commands.js.map +1 -1
  120. package/dist/ui-core/commands/picker-commands.js +21 -0
  121. package/dist/ui-core/commands/picker-commands.js.map +1 -1
  122. package/dist/ui-core/commands/pickers/search-reasoning.d.ts +2 -0
  123. package/dist/ui-core/commands/pickers/search-reasoning.js +1 -1
  124. package/dist/ui-core/commands/pickers/search-reasoning.js.map +1 -1
  125. package/dist/ui-core/commands/session-commands.d.ts +4 -0
  126. package/dist/ui-core/commands/session-commands.js +94 -2
  127. package/dist/ui-core/commands/session-commands.js.map +1 -1
  128. package/dist/ui-core/rendering/format-usage.js +29 -6
  129. package/dist/ui-core/rendering/format-usage.js.map +1 -1
  130. package/dist/ui-core/state/hydrate/classic-transcript.js +4 -0
  131. package/dist/ui-core/state/hydrate/classic-transcript.js.map +1 -1
  132. package/dist/ui-core/state/transcript/apply-app-event.js +17 -0
  133. package/dist/ui-core/state/transcript/apply-app-event.js.map +1 -1
  134. package/dist/ui-core/state/transcript-hydrate.js +165 -80
  135. package/dist/ui-core/state/transcript-hydrate.js.map +1 -1
  136. package/dist/ui-core/state/transcript-store.js +7 -1
  137. package/dist/ui-core/state/transcript-store.js.map +1 -1
  138. package/dist/ui-core/state/transcript-types.d.ts +2 -0
  139. package/dist/ui-core/state/transcript-types.js +5 -2
  140. package/dist/ui-core/state/transcript-types.js.map +1 -1
  141. package/dist/version.generated.d.ts +2 -2
  142. package/dist/version.generated.js +2 -2
  143. package/package.json +1 -1
@@ -5,10 +5,11 @@ import { appendSearchProviderKey, getProviderKeys, getSearchProviderKeys, listPr
5
5
  import { searchProviderIds } from "../../tools/web/types.js";
6
6
  import { formatKeyStatus } from "../rendering/format-keys.js";
7
7
  import { notice, openEndpointsEditor, openSearchKeysEditor, resolveEditorRowsDetailed } from "./keys/editors.js";
8
- import { pollClineDeviceAuth, startClineDeviceAuth, } from "../../llm/cline-auth.js";
8
+ import { maybeRefreshClineToken, pollClineDeviceAuth, startClineDeviceAuth, } from "../../llm/cline-auth.js";
9
9
  import { codexKeyFromAccessToken, encodeCodexKey, pollCodexDeviceAuth, startCodexBrowserAuth, startCodexDeviceAuth, } from "../../llm/codex-auth.js";
10
10
  import { openSystemBrowser } from "../../mcp/auth/loopback.js";
11
11
  import { pollCopilotDeviceAuth, startCopilotDeviceAuth, } from "../../llm/copilot-auth.js";
12
+ import { encodeKiroKey, decodeKiroKey, isKiroOAuthToken, maybeRefreshKiroCredential, pollKiroDeviceAuth, startKiroDeviceAuth, startKiroSocialAuth, exchangeKiroSocialCode, importExistingKiroAuth, refreshKiroToken, listenForKiroSocialCallback, createKiroCliAuthorizationFlow, exchangeKiroPortalCode, listenForKiroCliCallback, parseKiroCliCallback, isHeadlessEnvironment, KIRO_KEY_PREFIX, } from "../../llm/kiro-auth.js";
12
13
  import { appendProviderKey, replaceProviderKey } from "../../store/keys.js";
13
14
  import { MAX_PROVIDER_KEYS } from "../../llm/key-rotation.js";
14
15
  const SEARCH_IDS = new Set(["brave", "tavily", "duckduckgo", "exa"]);
@@ -331,6 +332,7 @@ export async function runClineAuthForUI(services) {
331
332
  notice(services, "warn", `could not start Cline sign-in: ${error instanceof Error ? error.message : String(error)}`);
332
333
  return undefined;
333
334
  }
335
+ void openSystemBrowser(start.verificationUrl).catch(() => { });
334
336
  services.overlay.openPager("Cline sign-in", [
335
337
  "Authenticate Cline on any device:",
336
338
  "",
@@ -341,20 +343,33 @@ export async function runClineAuthForUI(services) {
341
343
  "Approve in your browser — clai will continue automatically.",
342
344
  "(close this and press Ctrl-C to cancel)",
343
345
  ].join("\n"), undefined, undefined, "plain");
346
+ const abortController = new AbortController();
347
+ const unsubscribe = services.overlay.subscribe(() => {
348
+ if (!services.overlay.isOpen())
349
+ abortController.abort();
350
+ });
344
351
  const waiting = services.toast.info("waiting for Cline approval…", {
345
352
  sticky: true,
346
353
  });
347
354
  try {
348
- const tokens = await pollClineDeviceAuth(start);
349
- services.toast.dismiss(waiting);
355
+ const tokens = await pollClineDeviceAuth(start, {
356
+ signal: abortController.signal,
357
+ });
350
358
  notice(services, "info", "Cline authenticated");
351
359
  return tokens;
352
360
  }
353
361
  catch (error) {
354
- services.toast.dismiss(waiting);
362
+ if (abortController.signal.aborted) {
363
+ notice(services, "info", "cancelled");
364
+ return undefined;
365
+ }
355
366
  notice(services, "warn", `Cline sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
356
367
  return undefined;
357
368
  }
369
+ finally {
370
+ unsubscribe();
371
+ services.toast.dismiss(waiting);
372
+ }
358
373
  }
359
374
  async function clineAddAccount(services) {
360
375
  const tokens = await runClineAuthForUI(services);
@@ -436,21 +451,40 @@ export async function runCodexAuthForUI(services) {
436
451
  "clai will continue automatically once authorization completes.",
437
452
  "(close this and press Ctrl-C to cancel)",
438
453
  ].join("\n"), undefined, undefined, "plain");
454
+ const abortController = new AbortController();
455
+ const unsubscribe = services.overlay.subscribe(() => {
456
+ if (!services.overlay.isOpen())
457
+ abortController.abort();
458
+ });
439
459
  const waiting = services.toast.info("waiting for ChatGPT approval…", {
440
460
  sticky: true,
441
461
  });
442
462
  try {
443
463
  await openSystemBrowser(handle.url).catch(() => { });
444
- const credential = await handle.waitForCredential();
445
- services.toast.dismiss(waiting);
464
+ const credential = await Promise.race([
465
+ handle.waitForCredential(),
466
+ new Promise((_, reject) => {
467
+ if (abortController.signal.aborted)
468
+ reject(new Error("ChatGPT authentication cancelled"));
469
+ abortController.signal.addEventListener("abort", () => reject(new Error("ChatGPT authentication cancelled")));
470
+ }),
471
+ ]);
446
472
  notice(services, "info", "ChatGPT Subscription authenticated");
447
473
  return credential;
448
474
  }
449
475
  catch (error) {
450
- services.toast.dismiss(waiting);
476
+ if (abortController.signal.aborted) {
477
+ notice(services, "info", "cancelled");
478
+ return undefined;
479
+ }
451
480
  notice(services, "warn", `ChatGPT Subscription sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
452
481
  return undefined;
453
482
  }
483
+ finally {
484
+ unsubscribe();
485
+ services.toast.dismiss(waiting);
486
+ handle.close();
487
+ }
454
488
  }
455
489
  export async function runCodexDeviceAuthForUI(services) {
456
490
  let start;
@@ -461,6 +495,7 @@ export async function runCodexDeviceAuthForUI(services) {
461
495
  notice(services, "warn", `could not start ChatGPT Subscription sign-in: ${error instanceof Error ? error.message : String(error)}`);
462
496
  return undefined;
463
497
  }
498
+ void openSystemBrowser(start.verificationUrl).catch(() => { });
464
499
  services.overlay.openPager("ChatGPT Subscription sign-in", [
465
500
  "Authenticate ChatGPT Subscription on any device:",
466
501
  "",
@@ -471,20 +506,33 @@ export async function runCodexDeviceAuthForUI(services) {
471
506
  "Approve in your browser — clai will continue automatically.",
472
507
  "(close this and press Ctrl-C to cancel)",
473
508
  ].join("\n"), undefined, undefined, "plain");
509
+ const abortController = new AbortController();
510
+ const unsubscribe = services.overlay.subscribe(() => {
511
+ if (!services.overlay.isOpen())
512
+ abortController.abort();
513
+ });
474
514
  const waiting = services.toast.info("waiting for ChatGPT approval…", {
475
515
  sticky: true,
476
516
  });
477
517
  try {
478
- const credential = await pollCodexDeviceAuth(start);
479
- services.toast.dismiss(waiting);
518
+ const credential = await pollCodexDeviceAuth(start, {
519
+ signal: abortController.signal,
520
+ });
480
521
  notice(services, "info", "ChatGPT Subscription authenticated");
481
522
  return credential;
482
523
  }
483
524
  catch (error) {
484
- services.toast.dismiss(waiting);
525
+ if (abortController.signal.aborted) {
526
+ notice(services, "info", "cancelled");
527
+ return undefined;
528
+ }
485
529
  notice(services, "warn", `ChatGPT Subscription sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
486
530
  return undefined;
487
531
  }
532
+ finally {
533
+ unsubscribe();
534
+ services.toast.dismiss(waiting);
535
+ }
488
536
  }
489
537
  export async function runCopilotAuthForUI(services) {
490
538
  let start;
@@ -505,20 +553,33 @@ export async function runCopilotAuthForUI(services) {
505
553
  "Approve in your browser — clai will continue automatically.",
506
554
  "(close this and press Ctrl-C to cancel)",
507
555
  ].join("\n"), undefined, undefined, "plain");
556
+ const abortController = new AbortController();
557
+ const unsubscribe = services.overlay.subscribe(() => {
558
+ if (!services.overlay.isOpen())
559
+ abortController.abort();
560
+ });
508
561
  const waiting = services.toast.info("waiting for Github Copilot approval…", {
509
562
  sticky: true,
510
563
  });
511
564
  try {
512
- const token = await pollCopilotDeviceAuth(start);
513
- services.toast.dismiss(waiting);
565
+ const token = await pollCopilotDeviceAuth(start, {
566
+ signal: abortController.signal,
567
+ });
514
568
  notice(services, "info", "Github Copilot authenticated");
515
569
  return token;
516
570
  }
517
571
  catch (error) {
518
- services.toast.dismiss(waiting);
572
+ if (abortController.signal.aborted) {
573
+ notice(services, "info", "cancelled");
574
+ return undefined;
575
+ }
519
576
  notice(services, "warn", `Github Copilot sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
520
577
  return undefined;
521
578
  }
579
+ finally {
580
+ unsubscribe();
581
+ services.toast.dismiss(waiting);
582
+ }
522
583
  }
523
584
  async function loadClineKeys() {
524
585
  const multi = await getProviderKeys("cline");
@@ -539,13 +600,27 @@ async function storeClineAccount(services, tokens) {
539
600
  }
540
601
  notice(services, "info", `added Cline account ${maskSecret(tokens.accessToken)}`);
541
602
  }
542
- async function refreshClineAccount(services, oldValue) {
603
+ async function refreshClineAccount(services, slot) {
604
+ if (slot.refreshToken) {
605
+ notice(services, "info", "refreshing Cline account…");
606
+ const fresh = await maybeRefreshClineToken(slot.value, slot.refreshToken);
607
+ if (fresh?.accessToken) {
608
+ const replaced = await replaceProviderKey("cline", slot.value, fresh.accessToken, {
609
+ ...(fresh.refreshToken ? { refreshToken: fresh.refreshToken } : {}),
610
+ ...(fresh.expiresAt !== undefined ? { expiresAt: fresh.expiresAt } : {}),
611
+ });
612
+ if (replaced) {
613
+ notice(services, "info", `refreshed Cline account ${maskSecret(fresh.accessToken)}`);
614
+ return;
615
+ }
616
+ }
617
+ }
543
618
  const tokens = await runClineAuthForUI(services);
544
619
  if (!tokens) {
545
620
  notice(services, "info", "cancelled");
546
621
  return;
547
622
  }
548
- const replaced = await replaceProviderKey("cline", oldValue, tokens.accessToken, {
623
+ const replaced = await replaceProviderKey("cline", slot.value, tokens.accessToken, {
549
624
  ...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}),
550
625
  ...(tokens.expiresAt !== undefined ? { expiresAt: tokens.expiresAt } : {}),
551
626
  });
@@ -607,6 +682,7 @@ async function openClineKeysFlow(services) {
607
682
  return;
608
683
  }
609
684
  if (answer.action === "pick") {
685
+ await savePickerDraft("cline", answer, keys, activeIndex);
610
686
  const tokens = await clineAddAccount(services);
611
687
  if (tokens)
612
688
  await storeClineAccount(services, tokens);
@@ -616,7 +692,7 @@ async function openClineKeysFlow(services) {
616
692
  if (answer.action === "refresh") {
617
693
  const selected = keys.find((key) => key.id === answer.slotId);
618
694
  if (selected) {
619
- await refreshClineAccount(services, selected.value);
695
+ await refreshClineAccount(services, selected);
620
696
  }
621
697
  else {
622
698
  notice(services, "warn", "Cline account was not found");
@@ -635,6 +711,18 @@ async function loadOAuthKeys(provider) {
635
711
  activeIndex: multi.source === "env" ? 0 : multi.activeIndex,
636
712
  };
637
713
  }
714
+ async function savePickerDraft(provider, answer, keys, activeIndex) {
715
+ const byId = new Map(keys.map((key) => [key.id, key.value]));
716
+ const detailed = answer.rows.flatMap((row) => {
717
+ if (row.slotId) {
718
+ const value = byId.get(row.slotId);
719
+ return value ? [{ value, disabled: row.disabled === true }] : [];
720
+ }
721
+ const value = row.value.trim();
722
+ return value ? [{ value, disabled: row.disabled === true }] : [];
723
+ });
724
+ await setProviderKeys(provider, detailed.map((row) => row.value), answer.activeIndex ?? activeIndex, detailed.filter((row) => row.disabled).map((row) => row.value));
725
+ }
638
726
  async function saveOAuthKeys(services, provider, answer, keys, activeIndex) {
639
727
  const label = getProvider(provider).displayName;
640
728
  const byId = new Map(keys.map((key) => [key.id, key.value]));
@@ -688,6 +776,7 @@ async function openCodexKeysFlow(services) {
688
776
  return;
689
777
  }
690
778
  if (answer.action === "pick") {
779
+ await savePickerDraft("codex", answer, keys, activeIndex);
691
780
  const credential = await runCodexAuthForUI(services);
692
781
  const manualKey = credential?.manualKey;
693
782
  if (credential) {
@@ -757,6 +846,7 @@ async function openCopilotKeysFlow(services) {
757
846
  return;
758
847
  }
759
848
  if (answer.action === "pick") {
849
+ await savePickerDraft("copilot", answer, keys, activeIndex);
760
850
  const token = await runCopilotAuthForUI(services);
761
851
  if (token) {
762
852
  await appendProviderKey("copilot", token);
@@ -795,6 +885,334 @@ async function openCopilotKeysFlow(services) {
795
885
  return;
796
886
  }
797
887
  }
888
+ function pickKiroAuthMethod(services) {
889
+ return new Promise((resolve) => {
890
+ const opened = services.overlay.openPicker({
891
+ title: "Kiro AI sign-in method",
892
+ options: [
893
+ {
894
+ value: "kiro-cli",
895
+ label: "Sign in with Kiro (recommended)",
896
+ description: "Google, GitHub, or Builder ID — browser sign-in",
897
+ },
898
+ {
899
+ value: "builder-id",
900
+ label: "Sign in with AWS Builder ID",
901
+ description: "device code — works on any device or terminal",
902
+ },
903
+ {
904
+ value: "idc",
905
+ label: "Sign in with AWS IAM Identity Center (IDC)",
906
+ description: "enterprise SSO start URL",
907
+ },
908
+ {
909
+ value: "import",
910
+ label: "Import existing Kiro CLI / Desktop sign-in",
911
+ description: "reads local Kiro or AWS SSO credentials",
912
+ },
913
+ {
914
+ value: "apikey",
915
+ label: "Manually enter API key / token",
916
+ description: "paste an API key or token",
917
+ },
918
+ ],
919
+ }, (value) => {
920
+ services.overlay.close();
921
+ resolve(value);
922
+ });
923
+ if (!opened)
924
+ resolve(undefined);
925
+ });
926
+ }
927
+ export async function runKiroDeviceAuthForUI(services, options = {}) {
928
+ let start;
929
+ try {
930
+ start = await startKiroDeviceAuth(options);
931
+ }
932
+ catch (error) {
933
+ notice(services, "warn", `could not start Kiro sign-in: ${error instanceof Error ? error.message : String(error)}`);
934
+ return undefined;
935
+ }
936
+ const completeUrl = start.verificationUriComplete || start.verificationUri;
937
+ void openSystemBrowser(completeUrl).catch(() => { });
938
+ services.overlay.openPager("Kiro AI sign-in", [
939
+ "Authenticate Kiro AI on any device:",
940
+ "",
941
+ ` ${completeUrl}`,
942
+ "",
943
+ ` Code: ${start.userCode}`,
944
+ "",
945
+ "Approve in your browser — clai will continue automatically.",
946
+ "(close this and press Ctrl-C to cancel)",
947
+ ].join("\n"), undefined, undefined, "plain");
948
+ const abortController = new AbortController();
949
+ const unsubscribe = services.overlay.subscribe(() => {
950
+ if (!services.overlay.isOpen())
951
+ abortController.abort();
952
+ });
953
+ const waiting = services.toast.info("waiting for Kiro approval…", {
954
+ sticky: true,
955
+ });
956
+ try {
957
+ const credential = await pollKiroDeviceAuth(start, {
958
+ signal: abortController.signal,
959
+ });
960
+ notice(services, "info", "Kiro AI authenticated");
961
+ return credential;
962
+ }
963
+ catch (error) {
964
+ if (abortController.signal.aborted) {
965
+ notice(services, "info", "cancelled");
966
+ return undefined;
967
+ }
968
+ notice(services, "warn", `Kiro AI sign-in failed: ${error instanceof Error ? error.message : String(error)}`);
969
+ return undefined;
970
+ }
971
+ finally {
972
+ unsubscribe();
973
+ services.toast.dismiss(waiting);
974
+ }
975
+ }
976
+ export async function runKiroPortalAuthForUI(services) {
977
+ if (isHeadlessEnvironment()) {
978
+ return runKiroDeviceAuthForUI(services, { authMethod: "builder-id" });
979
+ }
980
+ const { authorizeUrl, codeVerifier, state } = createKiroCliAuthorizationFlow();
981
+ let server;
982
+ try {
983
+ server = listenForKiroCliCallback({ expectedState: state });
984
+ }
985
+ catch {
986
+ server = undefined;
987
+ }
988
+ if (server) {
989
+ server.promise
990
+ .then((cb) => services.overlay.answerSecret(`${cb.path}?code=${cb.code}&login_option=${cb.loginOption}${cb.state ? `&state=${cb.state}` : ""}`))
991
+ .catch(() => { });
992
+ }
993
+ await openSystemBrowser(authorizeUrl).catch(() => { });
994
+ const waiting = services.toast.info("waiting for Kiro sign-in…", {
995
+ sticky: true,
996
+ });
997
+ const prompt = "Sign in with Google, GitHub, or Builder ID in your browser.\nclai will continue automatically — or paste the redirect URL / code below:";
998
+ try {
999
+ const pasted = await services.overlay.openSecret({
1000
+ title: "Kiro AI sign-in",
1001
+ prompt,
1002
+ reveal: true,
1003
+ });
1004
+ if (!pasted?.trim())
1005
+ return undefined;
1006
+ const cb = parseKiroCliCallback(pasted.trim());
1007
+ const cred = await exchangeKiroPortalCode(cb, codeVerifier);
1008
+ notice(services, "info", "Kiro AI authenticated");
1009
+ return cred;
1010
+ }
1011
+ catch (err) {
1012
+ notice(services, "warn", `Kiro sign-in failed: ${err instanceof Error ? err.message : String(err)}`);
1013
+ return undefined;
1014
+ }
1015
+ finally {
1016
+ services.toast.dismiss(waiting);
1017
+ server?.close();
1018
+ }
1019
+ }
1020
+ export async function runKiroSocialAuthForUI(services, provider) {
1021
+ const { url, codeVerifier } = await startKiroSocialAuth(provider);
1022
+ const label = provider === "google" ? "Google" : "GitHub";
1023
+ let callbackHandle;
1024
+ try {
1025
+ callbackHandle = await listenForKiroSocialCallback({
1026
+ onCode: (urlOrCode) => {
1027
+ services.overlay.answerSecret(urlOrCode);
1028
+ },
1029
+ });
1030
+ }
1031
+ catch { }
1032
+ await openSystemBrowser(url).catch(() => { });
1033
+ const waiting = services.toast.info(`waiting for Kiro (${label}) sign-in…`, {
1034
+ sticky: true,
1035
+ });
1036
+ try {
1037
+ const pasted = await services.overlay.openSecret({
1038
+ title: `Kiro AI (${label}) sign-in`,
1039
+ prompt: `Sign in with ${label} in your browser.\nWhen prompted "Open Kiro", click Open (or paste the redirect URL / code below):`,
1040
+ reveal: true,
1041
+ });
1042
+ if (!pasted?.trim())
1043
+ return undefined;
1044
+ const cred = await exchangeKiroSocialCode(pasted.trim(), codeVerifier, provider);
1045
+ notice(services, "info", `Kiro AI (${label}) authenticated`);
1046
+ return cred;
1047
+ }
1048
+ catch (err) {
1049
+ notice(services, "warn", `Kiro sign-in failed: ${err instanceof Error ? err.message : String(err)}`);
1050
+ return undefined;
1051
+ }
1052
+ finally {
1053
+ services.toast.dismiss(waiting);
1054
+ callbackHandle?.close();
1055
+ }
1056
+ }
1057
+ export async function runKiroAuthForUI(services) {
1058
+ const method = await pickKiroAuthMethod(services);
1059
+ if (!method)
1060
+ return undefined;
1061
+ if (method === "kiro-cli") {
1062
+ return runKiroPortalAuthForUI(services);
1063
+ }
1064
+ if (method === "builder-id") {
1065
+ return runKiroDeviceAuthForUI(services, { authMethod: "builder-id" });
1066
+ }
1067
+ if (method === "idc") {
1068
+ const startUrl = await services.overlay.openSecret({
1069
+ title: "AWS IAM Identity Center Start URL",
1070
+ prompt: "Enter AWS IDC Start URL (e.g. https://my-org.awsapps.com/start):",
1071
+ reveal: true,
1072
+ });
1073
+ if (!startUrl?.trim())
1074
+ return undefined;
1075
+ const region = await services.overlay.openSecret({
1076
+ title: "AWS Region",
1077
+ prompt: "Enter AWS Region (default: us-east-1):",
1078
+ reveal: true,
1079
+ });
1080
+ return runKiroDeviceAuthForUI(services, {
1081
+ authMethod: "idc",
1082
+ startUrl: startUrl.trim(),
1083
+ region: region?.trim() || "us-east-1",
1084
+ });
1085
+ }
1086
+ if (method === "import") {
1087
+ const imported = await importExistingKiroAuth();
1088
+ if (!imported) {
1089
+ notice(services, "warn", "no existing Kiro credential found to import");
1090
+ return undefined;
1091
+ }
1092
+ notice(services, "info", "imported existing Kiro credentials");
1093
+ return imported;
1094
+ }
1095
+ if (method === "apikey") {
1096
+ const answer = await services.overlay.openSecret({
1097
+ title: "Kiro API Key or Token",
1098
+ prompt: "Paste Kiro API key, access token, or refresh token:",
1099
+ });
1100
+ const raw = answer?.trim();
1101
+ if (!raw)
1102
+ return undefined;
1103
+ if (raw.startsWith(KIRO_KEY_PREFIX)) {
1104
+ const decoded = decodeKiroKey(raw);
1105
+ if (decoded)
1106
+ return decoded;
1107
+ }
1108
+ if (raw.startsWith("aorAAAAAG")) {
1109
+ try {
1110
+ return await refreshKiroToken(raw);
1111
+ }
1112
+ catch (err) {
1113
+ notice(services, "warn", `failed to validate refresh token: ${err instanceof Error ? err.message : String(err)}`);
1114
+ return undefined;
1115
+ }
1116
+ }
1117
+ return {
1118
+ accessToken: raw,
1119
+ apiKey: raw,
1120
+ authMethod: "api_key",
1121
+ region: "us-east-1",
1122
+ };
1123
+ }
1124
+ return undefined;
1125
+ }
1126
+ async function openKiroKeysFlow(services) {
1127
+ let { keys, activeIndex } = await loadOAuthKeys("kiro");
1128
+ for (;;) {
1129
+ services.overlay.close();
1130
+ const answer = await services.overlay.openKeysEditor({
1131
+ provider: "kiro",
1132
+ heading: "KIRO AI ACCOUNTS",
1133
+ itemLabel: "account",
1134
+ addViaPicker: true,
1135
+ refreshable: true,
1136
+ initialKeys: keys.map((key) => ({
1137
+ id: key.id,
1138
+ masked: maskSecret(key.value),
1139
+ disabled: key.disabled === true,
1140
+ })),
1141
+ activeIndex,
1142
+ });
1143
+ if (!answer) {
1144
+ notice(services, "info", "cancelled");
1145
+ return;
1146
+ }
1147
+ if (answer.action === "reset") {
1148
+ await unsetProviderSecret("kiro");
1149
+ notice(services, "info", "unset all keys for Kiro AI");
1150
+ return;
1151
+ }
1152
+ if (answer.action === "pick") {
1153
+ await savePickerDraft("kiro", answer, keys, activeIndex);
1154
+ const credential = await runKiroAuthForUI(services);
1155
+ if (credential) {
1156
+ const key = encodeKiroKey(credential);
1157
+ await appendProviderKey("kiro", key, {
1158
+ ...(credential.refreshToken ? { refreshToken: credential.refreshToken } : {}),
1159
+ ...(credential.expiresAt !== undefined ? { expiresAt: credential.expiresAt } : {}),
1160
+ });
1161
+ notice(services, "info", `added Kiro account ${maskSecret(key)}`);
1162
+ }
1163
+ else {
1164
+ notice(services, "info", "cancelled");
1165
+ }
1166
+ ({ keys, activeIndex } = await loadOAuthKeys("kiro"));
1167
+ continue;
1168
+ }
1169
+ if (answer.action === "refresh") {
1170
+ const selected = keys.find((key) => key.id === answer.slotId);
1171
+ if (selected) {
1172
+ if (selected.refreshToken || isKiroOAuthToken(selected.value)) {
1173
+ notice(services, "info", "refreshing Kiro account…");
1174
+ const refreshedKey = await maybeRefreshKiroCredential(selected.value, selected.refreshToken);
1175
+ if (refreshedKey) {
1176
+ const decoded = decodeKiroKey(refreshedKey);
1177
+ const replaced = await replaceProviderKey("kiro", selected.value, refreshedKey, {
1178
+ ...(decoded?.refreshToken ? { refreshToken: decoded.refreshToken } : {}),
1179
+ ...(decoded?.expiresAt !== undefined ? { expiresAt: decoded.expiresAt } : {}),
1180
+ });
1181
+ if (replaced) {
1182
+ notice(services, "info", `refreshed Kiro account ${maskSecret(refreshedKey)}`);
1183
+ ({ keys, activeIndex } = await loadOAuthKeys("kiro"));
1184
+ continue;
1185
+ }
1186
+ }
1187
+ }
1188
+ const credential = await runKiroAuthForUI(services);
1189
+ if (credential) {
1190
+ const key = encodeKiroKey(credential);
1191
+ const replaced = await replaceProviderKey("kiro", selected.value, key, {
1192
+ ...(credential.refreshToken ? { refreshToken: credential.refreshToken } : {}),
1193
+ ...(credential.expiresAt !== undefined ? { expiresAt: credential.expiresAt } : {}),
1194
+ });
1195
+ if (!replaced) {
1196
+ notice(services, "warn", "Kiro account was not found");
1197
+ }
1198
+ else {
1199
+ notice(services, "info", `refreshed Kiro account ${maskSecret(key)}`);
1200
+ }
1201
+ }
1202
+ else {
1203
+ notice(services, "info", "cancelled");
1204
+ }
1205
+ }
1206
+ else {
1207
+ notice(services, "warn", "Kiro account was not found");
1208
+ }
1209
+ ({ keys, activeIndex } = await loadOAuthKeys("kiro"));
1210
+ continue;
1211
+ }
1212
+ await saveOAuthKeys(services, "kiro", answer, keys, activeIndex);
1213
+ return;
1214
+ }
1215
+ }
798
1216
  export async function openLlmKeysEditor(services, id) {
799
1217
  if (id === "ollama") {
800
1218
  const host = await services.overlay.openSecret({
@@ -822,6 +1240,10 @@ export async function openLlmKeysEditor(services, id) {
822
1240
  await openCopilotKeysFlow(services);
823
1241
  return;
824
1242
  }
1243
+ if (id === "kiro") {
1244
+ await openKiroKeysFlow(services);
1245
+ return;
1246
+ }
825
1247
  if (providerUsesEndpoints(id)) {
826
1248
  await openEndpointsEditor(services, id);
827
1249
  }