synabun 2.0.5 → 2.0.7

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 (233) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -1
  3. package/THIRD-PARTY-LICENSES.md +13 -0
  4. package/docs/whatsapp.md +151 -16
  5. package/hooks/claude-code/shared.mjs +6 -3
  6. package/lib/child-path.js +303 -0
  7. package/lib/claude-hooks.js +2 -1
  8. package/lib/data-home-migration.js +2 -0
  9. package/lib/desktop-app.js +265 -0
  10. package/lib/external-tools.js +7 -3
  11. package/lib/log-redact.js +102 -0
  12. package/lib/packaged-runtime.js +138 -11
  13. package/mcp-server/dist/http.d.ts +3 -1
  14. package/mcp-server/dist/http.d.ts.map +1 -1
  15. package/mcp-server/dist/http.js +15 -4
  16. package/mcp-server/dist/http.js.map +1 -1
  17. package/mcp-server/dist/index.d.ts.map +1 -1
  18. package/mcp-server/dist/index.js +9 -6
  19. package/mcp-server/dist/index.js.map +1 -1
  20. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  21. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  22. package/mcp-server/dist/services/budget-judgments.js +153 -0
  23. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  24. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  25. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  26. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  27. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  28. package/mcp-server/dist/services/identity.d.ts +11 -0
  29. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  30. package/mcp-server/dist/services/identity.js +24 -0
  31. package/mcp-server/dist/services/identity.js.map +1 -1
  32. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  33. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  34. package/mcp-server/dist/services/neural-interface.js +34 -2
  35. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  36. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  37. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  38. package/mcp-server/dist/services/typesafe-config.js +4 -1
  39. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  40. package/mcp-server/dist/tools/agents.d.ts +10 -7
  41. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  42. package/mcp-server/dist/tools/agents.js +34 -13
  43. package/mcp-server/dist/tools/agents.js.map +1 -1
  44. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  45. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  46. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  47. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  48. package/mcp-server/dist/tui.js +2 -1
  49. package/mcp-server/dist/tui.js.map +1 -1
  50. package/mcp-server/dist/version.d.ts +5 -0
  51. package/mcp-server/dist/version.d.ts.map +1 -0
  52. package/mcp-server/dist/version.js +21 -0
  53. package/mcp-server/dist/version.js.map +1 -0
  54. package/mcp-server/package-lock.json +2 -2
  55. package/mcp-server/package.json +1 -1
  56. package/mcp-server/run.mjs +15 -9
  57. package/mcp-server/src/http.ts +18 -3
  58. package/mcp-server/src/index.ts +9 -6
  59. package/mcp-server/src/services/budget-judgments.ts +183 -0
  60. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  61. package/mcp-server/src/services/identity.ts +27 -0
  62. package/mcp-server/src/services/neural-interface.ts +34 -2
  63. package/mcp-server/src/services/typesafe-config.ts +4 -1
  64. package/mcp-server/src/tools/agents.ts +33 -14
  65. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  66. package/mcp-server/src/tui.ts +2 -1
  67. package/mcp-server/src/version.ts +22 -0
  68. package/neural-interface/i18n/en.json +627 -57
  69. package/neural-interface/i18n/pt-BR.json +626 -56
  70. package/neural-interface/lib/account-usage.js +163 -0
  71. package/neural-interface/lib/assistant-api.js +255 -52
  72. package/neural-interface/lib/assistant-brains/claude.js +15 -2
  73. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  74. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  75. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  76. package/neural-interface/lib/assistant-budget-split.js +408 -0
  77. package/neural-interface/lib/assistant-budget.js +94 -95
  78. package/neural-interface/lib/assistant-config.js +62 -14
  79. package/neural-interface/lib/assistant-dispatch.js +432 -125
  80. package/neural-interface/lib/assistant-jev.js +26 -3
  81. package/neural-interface/lib/assistant-persona.js +68 -11
  82. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  83. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  84. package/neural-interface/lib/assistant-route-gate.js +19 -3
  85. package/neural-interface/lib/assistant-router.js +373 -42
  86. package/neural-interface/lib/assistant-runtime.js +307 -47
  87. package/neural-interface/lib/assistant-user-writer.js +4 -2
  88. package/neural-interface/lib/augmented-path.js +317 -53
  89. package/neural-interface/lib/claude-accounts.js +342 -31
  90. package/neural-interface/lib/claude-agent-bridge.js +90 -3
  91. package/neural-interface/lib/claude-executable.js +6 -1
  92. package/neural-interface/lib/claude-model-catalog.js +3 -3
  93. package/neural-interface/lib/claude-panel-auth.js +221 -0
  94. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  95. package/neural-interface/lib/cli-install-api.js +137 -0
  96. package/neural-interface/lib/cli-install.js +797 -0
  97. package/neural-interface/lib/cli-path.js +130 -0
  98. package/neural-interface/lib/cli-resolver.js +744 -0
  99. package/neural-interface/lib/cli-update-planner.js +143 -7
  100. package/neural-interface/lib/codex-account-auth.js +178 -0
  101. package/neural-interface/lib/codex-accounts.js +489 -0
  102. package/neural-interface/lib/codex-capabilities.js +2 -1
  103. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  104. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  105. package/neural-interface/lib/codex-panel-auth.js +62 -0
  106. package/neural-interface/lib/codex-session-settings.js +1 -1
  107. package/neural-interface/lib/codex-settings-schema.js +120 -0
  108. package/neural-interface/lib/desktop/config.js +3 -0
  109. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  110. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  111. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  112. package/neural-interface/lib/git-bash/install.js +529 -0
  113. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  114. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  115. package/neural-interface/lib/git-bash/manager.js +268 -0
  116. package/neural-interface/lib/git-bash/pins.js +206 -0
  117. package/neural-interface/lib/git-bash/pins.json +34 -0
  118. package/neural-interface/lib/git-bash/resolver.js +194 -0
  119. package/neural-interface/lib/git-read.js +34 -2
  120. package/neural-interface/lib/install-details.js +145 -0
  121. package/neural-interface/lib/mcp-installer.js +14 -2
  122. package/neural-interface/lib/native-loop-providers.js +16 -3
  123. package/neural-interface/lib/native-loop-runtime.js +9 -4
  124. package/neural-interface/lib/os-path.js +319 -0
  125. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  126. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  127. package/neural-interface/lib/pty-host/core.js +2 -1
  128. package/neural-interface/lib/pty-host/manager.js +27 -2
  129. package/neural-interface/lib/remote-policy.js +20 -11
  130. package/neural-interface/lib/session-title-generator.js +4 -3
  131. package/neural-interface/lib/system-about.js +340 -0
  132. package/neural-interface/lib/system-activity.js +66 -0
  133. package/neural-interface/lib/terminal-env.js +11 -3
  134. package/neural-interface/lib/terminal-path.js +55 -0
  135. package/neural-interface/lib/virtual-socket.js +12 -1
  136. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  137. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  138. package/neural-interface/lib/whatsapp/commands.js +13 -14
  139. package/neural-interface/lib/whatsapp/config.js +134 -20
  140. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  141. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  142. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  143. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  144. package/neural-interface/lib/whatsapp/manager.js +92 -32
  145. package/neural-interface/lib/whatsapp/paths.js +11 -1
  146. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  147. package/neural-interface/lib/whatsapp/redact.js +63 -0
  148. package/neural-interface/lib/whatsapp/service.js +343 -91
  149. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  150. package/neural-interface/package-lock.json +2 -2
  151. package/neural-interface/package.json +1 -1
  152. package/neural-interface/public/index.html +2 -0
  153. package/neural-interface/public/index2d.html +2 -0
  154. package/neural-interface/public/offline.html +16 -1
  155. package/neural-interface/public/onboarding.html +96 -2
  156. package/neural-interface/public/shared/account-i18n.js +7 -0
  157. package/neural-interface/public/shared/account-menu.js +539 -0
  158. package/neural-interface/public/shared/account-usage.js +19 -0
  159. package/neural-interface/public/shared/account-view.js +335 -0
  160. package/neural-interface/public/shared/agent-runtime-options.js +28 -8
  161. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  162. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  163. package/neural-interface/public/shared/assistant/asst-brain-picker.js +124 -25
  164. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  165. package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
  166. package/neural-interface/public/shared/assistant/asst-menu.js +15 -3
  167. package/neural-interface/public/shared/assistant/asst-panel.js +48 -14
  168. package/neural-interface/public/shared/assistant/asst-render.js +38 -7
  169. package/neural-interface/public/shared/assistant/asst-route.js +72 -14
  170. package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
  171. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  172. package/neural-interface/public/shared/assistant/asst-state.js +1 -1
  173. package/neural-interface/public/shared/assistant/asst-styles.js +99 -17
  174. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  175. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  176. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  177. package/neural-interface/public/shared/cdx/cdx-panel.js +48 -107
  178. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  179. package/neural-interface/public/shared/cdx/cdx-styles.js +128 -107
  180. package/neural-interface/public/shared/cdx/cdx-tabs.js +261 -664
  181. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  182. package/neural-interface/public/shared/claude-account-view.js +3 -0
  183. package/neural-interface/public/shared/cli-install-view.js +392 -0
  184. package/neural-interface/public/shared/cli-status.js +282 -22
  185. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  186. package/neural-interface/public/shared/cp/cp-styles.js +46 -0
  187. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  188. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  189. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  190. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  191. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  192. package/neural-interface/public/shared/editor/editor-panel.js +1 -1
  193. package/neural-interface/public/shared/explorer/fx-main.js +1 -1
  194. package/neural-interface/public/shared/git-bash-view.js +145 -0
  195. package/neural-interface/public/shared/html-shell.js +1 -0
  196. package/neural-interface/public/shared/i18n.js +19 -1
  197. package/neural-interface/public/shared/install-details-view.js +86 -0
  198. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +32 -0
  199. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
  200. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +118 -0
  201. package/neural-interface/public/shared/pwa-install-page.js +3 -1
  202. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  203. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  204. package/neural-interface/public/shared/settings/settings-ia.js +15 -0
  205. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  206. package/neural-interface/public/shared/storage.js +30 -3
  207. package/neural-interface/public/shared/styles.css +278 -31
  208. package/neural-interface/public/shared/synabun-mascot.js +48 -2
  209. package/neural-interface/public/shared/ui-claude-panel.js +212 -213
  210. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  211. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  212. package/neural-interface/public/shared/ui-judgments.js +10 -1
  213. package/neural-interface/public/shared/ui-loading.js +30 -6
  214. package/neural-interface/public/shared/ui-menubar.js +13 -3
  215. package/neural-interface/public/shared/ui-navbar.js +3 -1
  216. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  217. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  218. package/neural-interface/public/shared/ui-sessions.js +9 -3
  219. package/neural-interface/public/shared/ui-settings.js +55 -3
  220. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  221. package/neural-interface/public/shared/ui-sync.js +6 -0
  222. package/neural-interface/public/shared/ui-terminal.js +17 -0
  223. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  224. package/neural-interface/public/shared/ui-update.js +594 -5
  225. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  226. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  227. package/neural-interface/public/shared/worker-end.js +2 -0
  228. package/neural-interface/public/sw.js +1 -1
  229. package/neural-interface/public/variant/2d/main.js +10 -1
  230. package/neural-interface/public/variant/3d/main.js +10 -1
  231. package/neural-interface/server.js +1219 -608
  232. package/package.json +4 -1
  233. package/setup.js +9 -8
@@ -1,3 +1,8 @@
1
+ import { providerAuthSnapshot } from './ui-provider-auth.js';
2
+ import { mountProviderTerminalAuth, providerApiKeyRequest, startProviderTerminalLogin } from './ui-provider-terminal-auth.js';
3
+ import { accountName, accountSwitchPlan, accountText, tabAccountId, serverAccountId } from './account-view.js';
4
+ import { mountAccountMenu } from './account-menu.js';
5
+ import './account-i18n.js';
1
6
  import { hostedSidepanelId, sidepanelHost } from './ui-sidepanel-runtime.js';
2
7
  // ═══════════════════════════════════════════
3
8
  // SynaBun — Claude Code Panel (Main Area)
@@ -12,7 +17,7 @@ import { fetchClaudeSessions, fetchBrowserSessions, searchSessions } from './api
12
17
  import { createFrameRenderer } from './utils.js';
13
18
  import { notify, NOTIF_TYPE } from './ui-notifications.js';
14
19
  import { registerSidepanel, setSidepanelVisible, syncSidepanelLayout, focusSidepanel, openAnotherSidepanelSession, isHostedSessionFocused } from './ui-sidepanel-windows.js';
15
- import { subscribeCliStatus, recheckCliStatus, getCliDocUrl, getCliInstallCommand, getCliLabel } from './cli-status.js';
20
+ import { mountCliBanner } from './ui-cli-banner.js';
16
21
  import { nativeLoopWindowId } from './ui-native-window-id.js';
17
22
  import { createStickyScrollController } from './ui-scroll-follow.js';
18
23
  import { requestGeneratedSessionTitle } from './session-title.js';
@@ -166,9 +171,8 @@ function activeTab() { return _tabs[_activeTabIdx] || null; }
166
171
  let _browserEmbed = null; // { sessionId, ws, canvas, ctx, urlBar, container }
167
172
  let _browserEmbedVisible = false;
168
173
 
169
- // ── CLI-installed status (claude binary detection) ──
170
- let _cliInstalled = null; // null = unknown / missing, version string = installed
171
- let _cliUnsub = null;
174
+ // ── CLI status (claude binary detection): the banner controller, ui-cli-banner.js ──
175
+ let _cliBanner = null;
172
176
 
173
177
  // Marked.js (lazy loaded)
174
178
  let _marked = null;
@@ -308,7 +312,7 @@ const SLASH_COMMANDS = [
308
312
  { name: 'plugin', desc: 'List active plugins and skills', kind: 'info' },
309
313
  { name: 'status', desc: 'Show session status summary', kind: 'info' },
310
314
  { name: 'cost', desc: 'Toggle the cost widget', kind: 'action' },
311
- { name: 'login', desc: 'Open Claude account login', kind: 'action' },
315
+ { name: 'login', desc: 'Sign this tab\'s Claude account in (opens the account menu)', kind: 'action' },
312
316
  { name: 'logout', desc: 'Open Claude account sign-out', kind: 'action' },
313
317
  { name: 'plan', desc: 'Toggle plan mode', kind: 'action' },
314
318
  { name: 'btw', desc: 'Add context while Claude is processing (Shift+Enter)', kind: 'info' },
@@ -322,7 +326,7 @@ const SLASH_COMMANDS = [
322
326
  { name: 'reload-plugins', desc: 'Reload plugins without restarting the session', kind: 'action', needs: 'reload_plugins' },
323
327
  { name: 'tasks', desc: 'Background work of this tab: running tasks (with stop) and scheduled wakeups', kind: 'info' },
324
328
  { name: 'usage', desc: 'This session\'s cost and the plan\'s usage limits', kind: 'info', needs: 'session_requests' },
325
- { name: 'account', desc: 'Choose the Claude account this tab runs under', kind: 'action', needs: 'accounts' },
329
+ { name: 'account', desc: 'Choose and manage the Claude account of this tab (the header account menu)', kind: 'action', needs: 'accounts' },
326
330
  { name: 'effective-settings', desc: 'The Claude Code settings in effect here, and the file each comes from', kind: 'info', needs: 'settings_view' },
327
331
  ];
328
332
 
@@ -544,72 +548,7 @@ function injectStyles() {
544
548
  .cp-messages:has(.msg) .cp-empty,
545
549
  .cp-messages:has(.msg-status) .cp-empty { display: none; }
546
550
 
547
- /* ── CLI-not-installed banner (sticky top of messages container) ── */
548
- .cp-cli-banner {
549
- flex-shrink: 0;
550
- display: grid;
551
- grid-template-columns: auto 1fr auto;
552
- gap: 10px;
553
- align-items: center;
554
- margin: 6px 8px 0;
555
- padding: 10px 12px;
556
- border: 1px solid rgba(255,180,80,0.45);
557
- border-radius: 8px;
558
- background: rgba(255,180,80,0.08);
559
- color: rgba(255,235,200,0.92);
560
- font-size: 11px;
561
- line-height: 1.4;
562
- pointer-events: auto;
563
- position: relative;
564
- z-index: 5;
565
- }
566
- .cp-cli-banner-icon {
567
- width: 22px; height: 22px;
568
- display: flex; align-items: center; justify-content: center;
569
- border-radius: 50%;
570
- background: rgba(255,180,80,0.25);
571
- color: rgba(255,210,140,1);
572
- font-weight: 700;
573
- }
574
- .cp-cli-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
575
- .cp-cli-banner-title {
576
- font-size: 12px; font-weight: 600;
577
- color: rgba(255,210,140,0.95);
578
- }
579
- .cp-cli-banner-body {
580
- font-size: 11px;
581
- color: rgba(255,255,255,0.7);
582
- word-break: break-word;
583
- }
584
- .cp-cli-banner-body code {
585
- padding: 1px 5px;
586
- background: rgba(0,0,0,0.35);
587
- border-radius: 3px;
588
- font-size: 10.5px;
589
- color: rgba(255,255,255,0.85);
590
- }
591
- .cp-cli-banner-actions { display: flex; gap: 6px; }
592
- .cp-cli-banner-link, .cp-cli-banner-recheck {
593
- display: inline-flex; align-items: center; gap: 4px;
594
- padding: 4px 9px;
595
- font-size: 11px;
596
- font-weight: 500;
597
- border-radius: 4px;
598
- border: 1px solid rgba(255,255,255,0.2);
599
- background: rgba(255,255,255,0.05);
600
- color: rgba(255,255,255,0.9);
601
- text-decoration: none;
602
- cursor: pointer;
603
- user-select: none;
604
- font-family: inherit;
605
- }
606
- .cp-cli-banner-link:hover, .cp-cli-banner-recheck:hover {
607
- background: rgba(255,255,255,0.12);
608
- border-color: rgba(255,255,255,0.4);
609
- }
610
- .cp-cli-banner-recheck:disabled { opacity: 0.5; cursor: default; }
611
- .claude-panel.cp-cli-blocked .cp-send { opacity: 0.4 !important; cursor: not-allowed !important; }
612
- .claude-panel.cp-cli-blocked .cp-input { opacity: 0.7; }
551
+ /* ── CLI banner (ui-cli-banner.js): its rules are in cp/cp-styles.js ── */
613
552
 
614
553
  /* ── Browser embed (live screencast inside panel) ── */
615
554
  .cp-browser-embed {
@@ -2975,6 +2914,7 @@ function connectTab(tab) {
2975
2914
  ws.addEventListener('open', () => {
2976
2915
  if (tab.ws !== ws) return;
2977
2916
  clearTimeout(tab.reconnectTimer);
2917
+ _ensureClaudeTerminalAuth(tab);
2978
2918
  connectionOpened(tab); // no statement of the tab's mode was made on this socket yet
2979
2919
  // Reclaim this tab's server session if it outlived the old socket — a turn in
2980
2920
  // flight, a permission card, background agents or a scheduled wakeup. The
@@ -3046,6 +2986,7 @@ function switchTab(idx) {
3046
2986
  tab.messagesEl._exitToken = (tab.messagesEl._exitToken || 0) + 1; // cancel pending exits
3047
2987
  _attachMessagesEl(tab);
3048
2988
  tab.messagesEl.style.display = '';
2989
+ _ensureClaudeTerminalAuth(tab);
3049
2990
  tab.scrollController?.setActive(true);
3050
2991
  tab.messagesEl.classList.remove('cp-tab-exit');
3051
2992
  tab.messagesEl.classList.add('cp-tab-enter');
@@ -3127,6 +3068,7 @@ function switchTab(idx) {
3127
3068
  _setCompactingUI(!!tab.compacting);
3128
3069
  populateViewDropdown(_panel?.querySelector('#cp-view'), tab);
3129
3070
  populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
3071
+ _renderAccountMenu();
3130
3072
  _paintTemporary(tab);
3131
3073
  saveTabs();
3132
3074
  }
@@ -3149,6 +3091,8 @@ function closeTab(idx, { detachAutomation = false } = {}) {
3149
3091
  if (tab.automationRunId) forgetWorkerEnd(tab.automationRunId);
3150
3092
  flushSessionSnapshotSave(tab);
3151
3093
  recordHookEvent(tab, 'SessionEnd', tab.sessionId || '(new)');
3094
+ tab.authView?.dispose();
3095
+ tab.terminalAuthView?.dispose();
3152
3096
  tab.closed = true;
3153
3097
  tab.titleRequestController?.abort();
3154
3098
  tab.titleRequestController = null;
@@ -4479,7 +4423,7 @@ function writeSessionSnapshot(tab) {
4479
4423
  const sid = tab?.sessionId || null;
4480
4424
  const $msgs = tab?.messagesEl || null;
4481
4425
  if (!sid || !$msgs) return;
4482
- const html = $msgs.innerHTML || '';
4426
+ const html = providerAuthSnapshot($msgs);
4483
4427
  if (!html.trim()) return;
4484
4428
  // Skip snapshot if the transcript only contains the empty-state placeholder
4485
4429
  if ($msgs.children.length === 1 && $msgs.firstElementChild?.classList?.contains('cp-empty')) return;
@@ -5134,7 +5078,7 @@ function _contextMenuData(tab, { full = false } = {}) {
5134
5078
  folder: tab.init?.cwd || tab.project || '',
5135
5079
  // The mode as the project bar words it (it knows Bypass and "from settings").
5136
5080
  mode: ($mode && !$mode.hidden && $mode.querySelector('.cp-dd-label')?.textContent) || tab.permissionMode || '',
5137
- account: [tab.accountInfo?.email, tab.accountInfo?.organization].filter(Boolean).join(' · '),
5081
+ account: [tab.accountInfo?.authMethod === 'apiKey' ? 'API key' : tab.accountInfo?.email, tab.accountInfo?.organization].filter(Boolean).join(' · '),
5138
5082
  flags: sessionFlags(tab.session, { toolPolicy: tab.toolPolicy, fastModeState: tab.fastModeState, account: tab.accountId }),
5139
5083
  limit: tab.rateLimit?.text || '',
5140
5084
  backgroundTasks: (tab.backgroundWork || []).length,
@@ -5323,6 +5267,7 @@ function _processTabMsg(tab, msg) {
5323
5267
  if (tab === activeTab()) {
5324
5268
  renderStatusline(tab);
5325
5269
  populateModeDropdown(_panel?.querySelector('#cp-mode'), tab);
5270
+ _renderAccountMenu();
5326
5271
  }
5327
5272
  break;
5328
5273
  }
@@ -5546,6 +5491,8 @@ function _processTabMsg(tab, msg) {
5546
5491
  break;
5547
5492
  }
5548
5493
  appendError(tab, msg.message);
5494
+ // The tab's account is gone, or belongs to another conversation: the accounts card is where to choose.
5495
+ if (msg.code === 'account_unavailable' || msg.code === 'account_change_refused') _showAccounts(tab);
5549
5496
  notify('panel', NOTIF_TYPE.ERROR, tab.label || 'Claude Code', { tabId: tab.id });
5550
5497
  if (typeof msg.message === 'string' && /Claude CLI not found|ENOENT/i.test(msg.message)) {
5551
5498
  flagClaudeCliInstallFailure(msg.message);
@@ -7567,6 +7514,7 @@ function _afterTemporary(tab, sid, label) {
7567
7514
  for (const waiting of tab._sessionRequests?.values() || []) { clearTimeout(waiting.timer); try { waiting.reject(new Error('The temporary chat has ended.')); } catch {} }
7568
7515
  // An open history search lists its prompts.
7569
7516
  if (tab === activeTab()) _panel?.querySelector('.cp-rsearch')?.remove();
7517
+ tab.terminalAuthView?.dispose();
7570
7518
  tabAfterTemporary(tab, newTabState(sid, label || 'New chat', tab.id));
7571
7519
  }
7572
7520
 
@@ -8617,47 +8565,149 @@ function _showUsage(tab) {
8617
8565
  }).catch(err => appendInfoCard(tab, { title: 'Usage', kind: 'info', body: err.message }));
8618
8566
  }
8619
8567
 
8620
- // /account: the Claude accounts set up in SynaBun, and which one this tab uses.
8568
+ // ── Claude accounts ──
8569
+ // Every account and its state come from the server (lib/claude-accounts.js
8570
+ // describe(): its mode, whether it is signed in, its identity: an email or a
8571
+ // masked key, never a key); account-view.js decides the words. One list for
8572
+ // every tab, and one place to choose and manage accounts: the account menu in
8573
+ // the header (account-menu.js, the same component as the Codex panel's).
8574
+ // /account, /login and the sign-in screen's "Other accounts…" open it.
8575
+ // `claude:accounts-changed` (any window, any surface) reloads the list.
8621
8576
  // A conversation belongs to the account it started under (its transcript lives
8622
- // in that account's config directory), so the account is chosen before the
8623
- // first message of a tab.
8624
- function _showAccounts(tab, wanted = '') {
8625
- if (!hasCapability(tab, 'accounts')) { appendStatus(tab, 'Choosing an account needs a newer SynaBun server: restart it to enable this.'); return; }
8626
- fetch('/api/claude/accounts').then(r => r.json()).then((d) => {
8627
- const accounts = Array.isArray(d?.accounts) ? d.accounts : [];
8628
- const pick = (account) => {
8629
- const id = account.isDefault ? '' : account.id;
8630
- if (id === (tab.accountId || '')) { appendStatus(tab, `This tab already uses ${account.label}.`); return; }
8631
- if (tab.sessionId) { appendStatus(tab, 'This conversation belongs to the account it started under. Start a new chat (or a new tab) to use another account.'); return; }
8632
- if (!account.loggedIn) { appendStatus(tab, `${account.label} is not signed in yet. Sign it in from Settings first.`); return; }
8633
- tab.accountId = id;
8634
- saveTabs();
8635
- if (tab === activeTab()) renderStatusline(tab);
8636
- appendStatus(tab, `This tab runs under ${account.label}${account.email ? ` (${account.email})` : ''}.`);
8637
- };
8638
- if (wanted) {
8577
+ // in that account's config directory): a tab with one opens another account in
8578
+ // a new chat, and the tab's own account changes only before its first message.
8579
+ let _claudeAccountRows = null;
8580
+ let _claudeAccountsLoad = null;
8581
+ let _claudeAccountsError = '';
8582
+ let _accountMenu = null;
8583
+ function _loadClaudeAccounts({ force = false, refresh = false } = {}) {
8584
+ if (!force && !refresh && _claudeAccountRows) return Promise.resolve(_claudeAccountRows);
8585
+ if (_claudeAccountsLoad && !refresh) return _claudeAccountsLoad;
8586
+ const load = fetch(`/api/claude/accounts${refresh ? '?refresh=true' : ''}`, { cache: 'no-store' })
8587
+ .then(r => r.ok ? r.json() : Promise.reject(new Error(accountText('readFailed'))))
8588
+ .then((d) => {
8589
+ _claudeAccountRows = Array.isArray(d?.accounts) ? d.accounts : [];
8590
+ _claudeAccountsError = '';
8591
+ _claudeAccountsChanged();
8592
+ return _claudeAccountRows;
8593
+ })
8594
+ .catch((error) => { _claudeAccountsError = accountText('readFailed'); _claudeAccountsChanged(); throw error; })
8595
+ .finally(() => { if (_claudeAccountsLoad === load) _claudeAccountsLoad = null; });
8596
+ _claudeAccountsLoad = load;
8597
+ return load;
8598
+ }
8599
+ function _claudeAccountRow(id) {
8600
+ return (_claudeAccountRows || []).find(row => row.id === serverAccountId(id)) || null;
8601
+ }
8602
+ function _claudeAccountsChanged() {
8603
+ _renderAccountMenu();
8604
+ }
8605
+
8606
+ // The header account menu: the active tab's account and what it runs on, every account, and their
8607
+ // management (add, sign in with Claude, an API key, the "Uses" switch, sign out, rename, remove).
8608
+ function _mountAccountMenu() {
8609
+ if (_accountMenu || !_panel) return _accountMenu;
8610
+ const actions = _panel.querySelector('.cp-header-actions');
8611
+ if (!actions) return null;
8612
+ const path = id => `/api/claude/accounts/${encodeURIComponent(id)}`;
8613
+ _accountMenu = mountAccountMenu({
8614
+ provider: 'claude', container: actions, before: actions.firstChild, layer: () => _panel,
8615
+ read: () => {
8616
+ const tab = activeTab();
8617
+ return {
8618
+ rows: _claudeAccountRows || [], currentId: tab?.accountId || '', hasConversation: !!tab?.sessionId,
8619
+ available: !!tab && hasCapability(tab, 'accounts') && !tab.automationActive,
8620
+ busy: !!(tab?.running || tab?.automationActive), loading: !_claudeAccountRows, error: _claudeAccountsError,
8621
+ };
8622
+ },
8623
+ actions: {
8624
+ load: ({ refresh }) => _loadClaudeAccounts({ force: true, refresh }),
8625
+ pick: (id) => { const tab = activeTab(); if (tab) _pickAccount(tab, id); },
8626
+ add: async () => (await _claudeAuthRequest('/api/claude/accounts', { method: 'POST', body: '{}' })).account,
8627
+ signIn: (id) => startProviderTerminalLogin('claude', id, `Claude sign-in · ${_claudeAccountRow(id)?.label || id}`),
8628
+ saveKey: (id, key) => providerApiKeyRequest('claude', id, key),
8629
+ setMode: (id, mode) => _claudeAuthRequest(path(id), { method: 'PATCH', body: JSON.stringify({ mode }) }),
8630
+ signOut: (id, credential) => _claudeAuthRequest(`${path(id)}/auth/logout`, { method: 'POST', body: JSON.stringify({ credential }) }),
8631
+ rename: (id, label) => _claudeAuthRequest(path(id), { method: 'PATCH', body: JSON.stringify({ label }) }),
8632
+ remove: (id) => _claudeAuthRequest(path(id), { method: 'DELETE' }),
8633
+ },
8634
+ });
8635
+ return _accountMenu;
8636
+ }
8637
+ function _renderAccountMenu() {
8638
+ if (!_accountMenu) return;
8639
+ const tab = activeTab();
8640
+ if (tab && hasCapability(tab, 'accounts') && !_claudeAccountRows && !_claudeAccountsLoad) _loadClaudeAccounts().catch(() => {});
8641
+ _accountMenu.render();
8642
+ }
8643
+
8644
+ // Pick an account for a tab: now when it has no conversation yet, else in a new chat (a new tab).
8645
+ function _pickAccount(tab, id) {
8646
+ const rows = _claudeAccountRows || [];
8647
+ const plan = accountSwitchPlan({ rows, currentId: tab.accountId || '', targetId: id, hasConversation: !!tab.sessionId });
8648
+ const target = _claudeAccountRow(id);
8649
+ if (plan.effect === 'missing' || plan.effect === 'same') { appendStatus(tab, plan.message); return; }
8650
+ if (plan.effect === 'next-chat') {
8651
+ if (_tabs.length >= MAX_TABS) { appendStatus(tab, `${plan.message} ${accountText('tooManyTabs')}`); return; }
8652
+ const fresh = createTab(null, 'New chat');
8653
+ fresh.accountId = tabAccountId(id);
8654
+ _ensureClaudeTerminalAuth(fresh);
8655
+ saveTabs();
8656
+ if (fresh === activeTab()) _renderAccountMenu();
8657
+ appendStatus(fresh, accountText('newChatUnder', { label: target ? accountName(target) : id }));
8658
+ return;
8659
+ }
8660
+ tab.accountId = tabAccountId(id);
8661
+ _ensureClaudeTerminalAuth(tab);
8662
+ saveTabs();
8663
+ if (tab === activeTab()) { renderStatusline(tab); _renderAccountMenu(); }
8664
+ appendStatus(tab, plan.message);
8665
+ }
8666
+
8667
+ // /account (and /login, and the sign-in screen's "Other accounts…"): the header account menu.
8668
+ // `/account <name>` picks that account at once; `manage` opens one account's management.
8669
+ function _showAccounts(tab, wanted = '', { manage = '' } = {}) {
8670
+ if (!hasCapability(tab, 'accounts')) { appendStatus(tab, accountText('needsServer')); return; }
8671
+ if (wanted) {
8672
+ _loadClaudeAccounts({ force: true }).then((accounts) => {
8639
8673
  const w = wanted.trim().toLowerCase();
8640
- const match = accounts.find(a => a.id.toLowerCase() === w || String(a.label || '').toLowerCase() === w || String(a.email || '').toLowerCase() === w);
8674
+ const match = accounts.find(a => a.id.toLowerCase() === w || String(a.label || '').toLowerCase() === w || String(a.subscription?.email || '').toLowerCase() === w);
8641
8675
  if (!match) { appendStatus(tab, `No account "${wanted}". Use /account to list them.`); return; }
8642
- pick(match);
8643
- return;
8644
- }
8645
- const current = tab.accountId || 'default';
8646
- const html = accounts.map(a => `<div class="cp-task-row" data-account="${esc(a.id)}"><div class="cp-task-head">
8647
- <span class="cp-task-title">${esc(a.label)}${a.id === current ? ' ✓' : ''}</span>
8648
- <span class="cp-chip ${a.loggedIn ? 'cp-chip-ok' : 'cp-chip-warn'}">${a.loggedIn ? 'signed in' : 'not signed in'}</span>
8649
- ${a.id !== current ? '<button type="button" class="cp-engine-retry" data-use>Use for this tab</button>' : ''}</div>
8650
- <div class="cp-task-stats">${esc([a.email, a.organization].filter(Boolean).join(' · ') || 'no identity yet')}</div></div>`).join('')
8651
- + '<div class="cp-info-row muted">Accounts are added and signed in from Settings. The default account is what Claude Code uses in a terminal.</div>';
8652
- const card = appendInfoCard(tab, { title: 'Claude accounts', kind: 'info', html });
8653
- card?.querySelectorAll('[data-use]').forEach((btn) => {
8654
- btn.addEventListener('click', () => {
8655
- const id = btn.closest('[data-account]')?.dataset.account;
8656
- const account = accounts.find(a => a.id === id);
8657
- if (account) pick(account);
8658
- });
8659
- });
8660
- }).catch(() => appendStatus(tab, 'Could not read the accounts.'));
8676
+ _pickAccount(tab, match.id);
8677
+ }).catch(() => appendStatus(tab, accountText('readFailed')));
8678
+ return;
8679
+ }
8680
+ if (tab !== activeTab()) return;
8681
+ _mountAccountMenu()?.open(manage ? { manage } : {});
8682
+ }
8683
+
8684
+ async function _claudeAuthRequest(url, options = {}) {
8685
+ const response = await fetch(url, { ...options, cache: 'no-store', headers: { 'Content-Type': 'application/json' } });
8686
+ const data = await response.json().catch(() => ({}));
8687
+ if (!response.ok || data.error) throw new Error(data.error || 'Claude authentication request failed.');
8688
+ return data;
8689
+ }
8690
+
8691
+ function _ensureClaudeTerminalAuth(tab) {
8692
+ const accountId = tab.accountId || 'default';
8693
+ if (tab.closed || !tab.messagesEl) return;
8694
+ if (tab.terminalAuthView && tab.terminalAuthAccount === accountId) { void tab.terminalAuthView.refresh(); return; }
8695
+ tab.terminalAuthView?.dispose();
8696
+ tab.terminalAuthAccount = accountId;
8697
+ const named = accountId !== 'default' ? _claudeAccountRow(accountId) : null;
8698
+ tab.terminalAuthView = mountProviderTerminalAuth({
8699
+ host: tab.messagesEl, provider: 'claude', accountId, logo: CLAUDE_ICON,
8700
+ ...(named ? { message: `Sign in to Claude to use ${named.label}.` } : {}),
8701
+ // Another account (or a new one) is one click away from the sign-in screen: the header account menu.
8702
+ moreAction: { label: accountText('otherAccounts'), run: () => _showAccounts(tab) },
8703
+ isCurrent: () => !tab.closed && (tab.accountId || 'default') === accountId,
8704
+ readAccount: async () => {
8705
+ const account = (await _claudeAuthRequest(`/api/claude/accounts/${encodeURIComponent(accountId)}/auth?refresh=true`)).account;
8706
+ if (!tab.closed && (tab.accountId || 'default') === accountId) tab.accountInfo = account;
8707
+ return account;
8708
+ },
8709
+ onSignedIn: () => { _loadClaudeAccounts({ force: true }).catch(() => {}); },
8710
+ });
8661
8711
  }
8662
8712
 
8663
8713
  // /mcp: every server of the session with its state, and what can be done about it.
@@ -9176,8 +9226,15 @@ function runSlashCommand(tab, raw) {
9176
9226
  return true;
9177
9227
  }
9178
9228
  case 'cost': { clearInput(); emit('cost:toggle'); return true; }
9179
- case 'login': { clearInput(); window.open('https://claude.ai/login', '_blank'); return true; }
9180
- case 'logout': { clearInput(); window.open('https://claude.ai/logout', '_blank'); return true; }
9229
+ case 'login': { clearInput(); _showAccounts(tab, '', { manage: serverAccountId(tab.accountId) }); return true; }
9230
+ case 'logout': {
9231
+ clearInput();
9232
+ if (tab.running || tab.automationActive) { appendStatus(tab, 'Finish or stop the current turn before signing out.'); return true; }
9233
+ void _claudeAuthRequest(`/api/claude/accounts/${encodeURIComponent(tab.accountId || 'default')}/auth/logout`, { method: 'POST', body: '{}' })
9234
+ .then(() => { tab.accountInfo = null; tab.terminalAuthView?.update({ loggedIn: false }); })
9235
+ .catch(() => appendStatus(tab, 'Could not sign out. Try again.'));
9236
+ return true;
9237
+ }
9181
9238
  case 'plan': {
9182
9239
  clearInput();
9183
9240
  // The one path for plan mode: on is the user's pick of Plan, off goes back
@@ -9199,8 +9256,9 @@ function runSlashCommand(tab, raw) {
9199
9256
  function send({ shift = false } = {}) {
9200
9257
  const tab = activeTab();
9201
9258
  if (!tab) return;
9202
- if ((_cliInstalled === null || _cliInstallFailureForced) && !tab.automationActive) {
9203
- notify({ type: NOTIF_TYPE.WARN, title: 'Claude CLI not installed', body: 'Install the Claude Code CLI before sending a message.' });
9259
+ if (_cliBlocked() && !tab.automationActive) {
9260
+ // The banner above the transcript says why, and offers the way out.
9261
+ _cliBanner?.nudge();
9204
9262
  return;
9205
9263
  }
9206
9264
  const $input = _panel?.querySelector('#cp-input');
@@ -10094,116 +10152,50 @@ function syncReservedWidth(knownWidth) {
10094
10152
  }
10095
10153
 
10096
10154
  // ── CLI installation status (banner + send-block) ──
10097
- let _cliInstallFailureForced = false;
10098
- let _cliFailureReason = null;
10099
-
10100
- function _ensureCliBannerEl() {
10101
- if (!_panel) return null;
10102
- const container = _panel.querySelector('#cp-messages-container');
10103
- if (!container || !container.parentNode) return null;
10104
- let banner = container.parentNode.querySelector(':scope > .cp-cli-banner');
10105
- if (banner) { _renderCliBannerContent(banner); return banner; }
10106
- const url = getCliDocUrl('claude-code');
10107
- banner = document.createElement('div');
10108
- banner.className = 'cp-cli-banner';
10109
- banner.innerHTML = `
10110
- <div class="cp-cli-banner-icon">!</div>
10111
- <div class="cp-cli-banner-text">
10112
- <div class="cp-cli-banner-title"></div>
10113
- <div class="cp-cli-banner-body"></div>
10114
- </div>
10115
- <div class="cp-cli-banner-actions">
10116
- <a class="cp-cli-banner-link" href="${esc(url || '#')}" target="_blank" rel="noopener noreferrer">Install guide</a>
10117
- <button class="cp-cli-banner-recheck" type="button">Re-check</button>
10118
- </div>
10119
- `;
10120
- container.parentNode.insertBefore(banner, container);
10121
- const btn = banner.querySelector('.cp-cli-banner-recheck');
10122
- btn?.addEventListener('click', async () => {
10123
- btn.disabled = true;
10124
- btn.textContent = 'Checking…';
10125
- try { await recheckCliStatus('claude-code'); }
10126
- finally {
10127
- if (btn.isConnected) {
10128
- btn.disabled = false;
10129
- btn.textContent = 'Re-check';
10130
- }
10131
- }
10132
- });
10133
- _renderCliBannerContent(banner);
10134
- return banner;
10135
- }
10136
-
10137
- // Distinguish "never installed" from "installed but would not launch" —
10138
- // telling the user to install something already present sends them chasing a
10139
- // reinstall they do not need.
10140
- function _renderCliBannerContent(banner) {
10141
- const titleEl = banner.querySelector('.cp-cli-banner-title');
10142
- const bodyEl = banner.querySelector('.cp-cli-banner-body');
10143
- if (!titleEl || !bodyEl) return;
10144
- const label = getCliLabel('claude-code');
10145
- bodyEl.replaceChildren();
10146
-
10147
- if (_cliInstalled === null) {
10148
- titleEl.textContent = `${label} CLI not installed`;
10149
- bodyEl.append('Run ');
10150
- const code = document.createElement('code');
10151
- code.textContent = getCliInstallCommand('claude-code');
10152
- bodyEl.append(code, ' or follow the install guide.');
10153
- return;
10154
- }
10155
-
10156
- titleEl.textContent = `${label} CLI failed to start`;
10157
- bodyEl.append(`${label} v${_cliInstalled} is installed, but it could not be launched. `);
10158
- if (_cliFailureReason) {
10159
- const code = document.createElement('code');
10160
- code.textContent = _cliFailureReason;
10161
- bodyEl.append(code);
10162
- } else {
10163
- bodyEl.append('Check the CLI path in Settings, then re-check.');
10155
+ // One controller (ui-cli-banner.js) draws the banner above the transcript while
10156
+ // Claude Code is not usable: not installed (with Install, which runs Anthropic's
10157
+ // own installer on the server), being installed, failed to install, or found but
10158
+ // failed to start (with the error it gave). It also holds the class that keeps
10159
+ // sending off. What it says is cli-install-view.js (`cliInstall.*` strings).
10160
+ function _cliBannerController() {
10161
+ if (!_cliBanner) {
10162
+ _cliBanner = mountCliBanner({
10163
+ toolKey: 'claude-code',
10164
+ prefix: 'cp',
10165
+ panel: () => _panel,
10166
+ anchor: () => _panel?.querySelector('#cp-messages-container'),
10167
+ onBlockedChange: (blocked) => {
10168
+ const $send = _panel?.querySelector('#cp-send');
10169
+ if (!$send) return;
10170
+ if (blocked) { $send.disabled = true; return; }
10171
+ const tab = activeTab();
10172
+ const $input = _panel?.querySelector('#cp-input');
10173
+ if (tab?.running) $send.disabled = false;
10174
+ else $send.disabled = !$input?.value.trim() && !tab?.attachedImages?.length && !tab?.attachedFiles?.length;
10175
+ },
10176
+ });
10164
10177
  }
10178
+ return _cliBanner;
10165
10179
  }
10166
10180
 
10167
- function _removeCliBannerEl() {
10168
- if (!_panel) return;
10169
- const banner = _panel.querySelector(':scope > .cp-cli-banner');
10170
- banner?.remove();
10181
+ /** Sending is held back: Claude Code is missing, broken, failed to install, or being installed. */
10182
+ function _cliBlocked() {
10183
+ return !!_cliBanner?.blocked();
10171
10184
  }
10172
10185
 
10173
10186
  function _refreshCliBanners() {
10174
10187
  if (!_panel) return;
10175
- const missing = _cliInstalled === null || _cliInstallFailureForced;
10176
- _panel.classList.toggle('cp-cli-blocked', missing);
10177
- if (missing) _ensureCliBannerEl();
10178
- else _removeCliBannerEl();
10179
- const $send = _panel.querySelector('#cp-send');
10180
- if ($send && missing) $send.disabled = true;
10188
+ _cliBannerController().refresh();
10181
10189
  }
10182
10190
 
10191
+ // A session could not start Claude Code: "failed to start" with its words at
10192
+ // once, then the server is asked again and its answer decides what stays.
10183
10193
  export function flagClaudeCliInstallFailure(reason = null) {
10184
- _cliInstallFailureForced = true;
10185
- _cliFailureReason = reason ? String(reason).slice(0, 300) : null;
10186
- _refreshCliBanners();
10187
- // Clear from the RETURNED value, not the subscriber callback: the callback
10188
- // only fires when the version string changes, and a launch failure does not
10189
- // change it, so the banner would otherwise latch permanently.
10190
- recheckCliStatus('claude-code')
10191
- .then((info) => {
10192
- if (!info?.installed) return;
10193
- _cliInstallFailureForced = false;
10194
- _cliFailureReason = null;
10195
- _refreshCliBanners();
10196
- })
10197
- .catch(() => {});
10194
+ _cliBannerController().flagFailure(reason);
10198
10195
  }
10199
10196
 
10200
10197
  function _ensureCliSubscription() {
10201
- if (_cliUnsub) return;
10202
- _cliUnsub = subscribeCliStatus('claude-code', (info) => {
10203
- _cliInstalled = info?.installed || null;
10204
- if (_cliInstalled) { _cliInstallFailureForced = false; _cliFailureReason = null; }
10205
- _refreshCliBanners();
10206
- });
10198
+ _cliBannerController();
10207
10199
  }
10208
10200
 
10209
10201
  // ── Delegated transcript actions ──
@@ -11192,6 +11184,13 @@ function wireEvents() {
11192
11184
  const $close = _panel.querySelector('.cp-close-btn');
11193
11185
  const $minimize = _panel.querySelector('.cp-minimize-btn');
11194
11186
  const $slide = _panel.querySelector('.cp-slide-btn');
11187
+ _mountAccountMenu();
11188
+ // Any window or surface changed an account (signed in, a key saved, its mode, renamed, added,
11189
+ // removed): the account menu re-reads the list, and the tabs on it re-check sign-in.
11190
+ on('sync:claude:accounts-changed', (msg) => {
11191
+ _loadClaudeAccounts({ force: true }).catch(() => {});
11192
+ for (const t of _tabs) if (!msg?.accountId || serverAccountId(t.accountId) === msg.accountId) void t.terminalAuthView?.refresh();
11193
+ });
11195
11194
  const $new = _panel.querySelector('.cp-new-btn');
11196
11195
 
11197
11196
  const $brand = _panel.querySelector('.cp-brand');