synabun 2.0.0 → 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 (507) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/NOTICE +5 -0
  3. package/README.md +2 -1
  4. package/THIRD-PARTY-LICENSES.md +59 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +153 -18
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +26 -3
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/child-path.js +303 -0
  14. package/lib/claude-hooks.js +2 -1
  15. package/lib/data-home-migration.js +2 -0
  16. package/lib/desktop-app.js +265 -0
  17. package/lib/external-tools.js +7 -3
  18. package/lib/log-redact.js +102 -0
  19. package/lib/packaged-runtime.js +138 -11
  20. package/lib/start-launcher.js +88 -1
  21. package/mcp-server/dist/http.d.ts +3 -1
  22. package/mcp-server/dist/http.d.ts.map +1 -1
  23. package/mcp-server/dist/http.js +15 -4
  24. package/mcp-server/dist/http.js.map +1 -1
  25. package/mcp-server/dist/index.d.ts.map +1 -1
  26. package/mcp-server/dist/index.js +9 -6
  27. package/mcp-server/dist/index.js.map +1 -1
  28. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  29. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  30. package/mcp-server/dist/services/budget-judgments.js +153 -0
  31. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  32. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  33. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  34. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  35. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  36. package/mcp-server/dist/services/identity.d.ts +11 -0
  37. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  38. package/mcp-server/dist/services/identity.js +24 -0
  39. package/mcp-server/dist/services/identity.js.map +1 -1
  40. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  41. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  42. package/mcp-server/dist/services/neural-interface.js +34 -2
  43. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  44. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  45. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  46. package/mcp-server/dist/services/typesafe-config.js +4 -1
  47. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  48. package/mcp-server/dist/tools/agents.d.ts +10 -7
  49. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  50. package/mcp-server/dist/tools/agents.js +34 -13
  51. package/mcp-server/dist/tools/agents.js.map +1 -1
  52. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  53. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  54. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  55. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  56. package/mcp-server/dist/tui.js +2 -1
  57. package/mcp-server/dist/tui.js.map +1 -1
  58. package/mcp-server/dist/version.d.ts +5 -0
  59. package/mcp-server/dist/version.d.ts.map +1 -0
  60. package/mcp-server/dist/version.js +21 -0
  61. package/mcp-server/dist/version.js.map +1 -0
  62. package/mcp-server/package-lock.json +2 -2
  63. package/mcp-server/package.json +1 -3
  64. package/mcp-server/run.mjs +15 -9
  65. package/mcp-server/src/http.ts +18 -3
  66. package/mcp-server/src/index.ts +9 -6
  67. package/mcp-server/src/services/budget-judgments.ts +183 -0
  68. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  69. package/mcp-server/src/services/identity.ts +27 -0
  70. package/mcp-server/src/services/neural-interface.ts +34 -2
  71. package/mcp-server/src/services/typesafe-config.ts +4 -1
  72. package/mcp-server/src/tools/agents.ts +33 -14
  73. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  74. package/mcp-server/src/tui.ts +2 -1
  75. package/mcp-server/src/version.ts +22 -0
  76. package/neural-interface/i18n/en.json +2126 -209
  77. package/neural-interface/i18n/pt-BR.json +2123 -207
  78. package/neural-interface/lib/account-usage.js +163 -0
  79. package/neural-interface/lib/assistant-api.js +316 -57
  80. package/neural-interface/lib/assistant-brains/claude.js +19 -2
  81. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  82. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  83. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  84. package/neural-interface/lib/assistant-budget-split.js +408 -0
  85. package/neural-interface/lib/assistant-budget.js +94 -95
  86. package/neural-interface/lib/assistant-catalog.js +179 -27
  87. package/neural-interface/lib/assistant-config.js +91 -14
  88. package/neural-interface/lib/assistant-dispatch.js +494 -138
  89. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  90. package/neural-interface/lib/assistant-jev.js +26 -3
  91. package/neural-interface/lib/assistant-memory.js +24 -10
  92. package/neural-interface/lib/assistant-persona.js +73 -12
  93. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  94. package/neural-interface/lib/assistant-pricing.js +21 -6
  95. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  96. package/neural-interface/lib/assistant-route-gate.js +19 -3
  97. package/neural-interface/lib/assistant-router.js +373 -42
  98. package/neural-interface/lib/assistant-runtime.js +488 -59
  99. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  100. package/neural-interface/lib/assistant-test-density.js +142 -0
  101. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  102. package/neural-interface/lib/assistant-user-context.js +908 -0
  103. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  104. package/neural-interface/lib/assistant-user-learn.js +404 -0
  105. package/neural-interface/lib/assistant-user-threads.js +226 -0
  106. package/neural-interface/lib/assistant-user-writer.js +183 -0
  107. package/neural-interface/lib/augmented-path.js +317 -53
  108. package/neural-interface/lib/browser-background-tab.js +134 -0
  109. package/neural-interface/lib/browser-screenshot.js +678 -61
  110. package/neural-interface/lib/claude-accounts.js +342 -31
  111. package/neural-interface/lib/claude-agent-bridge.js +123 -9
  112. package/neural-interface/lib/claude-executable.js +6 -1
  113. package/neural-interface/lib/claude-model-catalog.js +33 -5
  114. package/neural-interface/lib/claude-panel-auth.js +221 -0
  115. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  116. package/neural-interface/lib/claude-temporary.js +46 -7
  117. package/neural-interface/lib/cli-install-api.js +137 -0
  118. package/neural-interface/lib/cli-install.js +797 -0
  119. package/neural-interface/lib/cli-path.js +130 -0
  120. package/neural-interface/lib/cli-resolver.js +744 -0
  121. package/neural-interface/lib/cli-update-planner.js +143 -7
  122. package/neural-interface/lib/cloudflared/install.js +533 -0
  123. package/neural-interface/lib/cloudflared/manager.js +545 -0
  124. package/neural-interface/lib/cloudflared/pins.js +115 -0
  125. package/neural-interface/lib/cloudflared/pins.json +63 -0
  126. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  127. package/neural-interface/lib/codex-account-auth.js +178 -0
  128. package/neural-interface/lib/codex-accounts.js +489 -0
  129. package/neural-interface/lib/codex-capabilities.js +2 -1
  130. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  131. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  132. package/neural-interface/lib/codex-panel-auth.js +62 -0
  133. package/neural-interface/lib/codex-session-settings.js +1 -1
  134. package/neural-interface/lib/codex-settings-schema.js +120 -0
  135. package/neural-interface/lib/desktop/config.js +3 -0
  136. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  137. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  138. package/neural-interface/lib/editor-completion.js +714 -0
  139. package/neural-interface/lib/editor-config.js +149 -0
  140. package/neural-interface/lib/editor-files-api.js +268 -0
  141. package/neural-interface/lib/editor-git-api.js +134 -0
  142. package/neural-interface/lib/file-explorer-api.js +1039 -0
  143. package/neural-interface/lib/file-explorer-system.js +355 -0
  144. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  145. package/neural-interface/lib/file-explorer-watch.js +239 -0
  146. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  147. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  148. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  149. package/neural-interface/lib/git-bash/install.js +529 -0
  150. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  151. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  152. package/neural-interface/lib/git-bash/manager.js +268 -0
  153. package/neural-interface/lib/git-bash/pins.js +206 -0
  154. package/neural-interface/lib/git-bash/pins.json +34 -0
  155. package/neural-interface/lib/git-bash/resolver.js +194 -0
  156. package/neural-interface/lib/git-diff-api.js +103 -0
  157. package/neural-interface/lib/git-read.js +234 -0
  158. package/neural-interface/lib/greeting/api.js +89 -0
  159. package/neural-interface/lib/greeting/budgets.js +90 -0
  160. package/neural-interface/lib/greeting/engine.js +461 -0
  161. package/neural-interface/lib/greeting/facts.js +320 -0
  162. package/neural-interface/lib/greeting/sources.js +252 -0
  163. package/neural-interface/lib/greeting/writer.js +139 -0
  164. package/neural-interface/lib/http-guards.js +189 -13
  165. package/neural-interface/lib/install-details.js +145 -0
  166. package/neural-interface/lib/machine-names.js +88 -0
  167. package/neural-interface/lib/mcp-installer.js +14 -2
  168. package/neural-interface/lib/memory-api.js +31 -7
  169. package/neural-interface/lib/native-loop-providers.js +26 -8
  170. package/neural-interface/lib/native-loop-runtime.js +9 -4
  171. package/neural-interface/lib/os-path.js +319 -0
  172. package/neural-interface/lib/plugin-installer.js +4 -2
  173. package/neural-interface/lib/project-storage.js +5 -7
  174. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  175. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  176. package/neural-interface/lib/pty-host/core.js +2 -1
  177. package/neural-interface/lib/pty-host/manager.js +27 -2
  178. package/neural-interface/lib/remote-policy.js +20 -11
  179. package/neural-interface/lib/request-gate.js +589 -0
  180. package/neural-interface/lib/session-title-generator.js +4 -3
  181. package/neural-interface/lib/system-about.js +340 -0
  182. package/neural-interface/lib/system-activity.js +66 -0
  183. package/neural-interface/lib/terminal-env.js +11 -3
  184. package/neural-interface/lib/terminal-path.js +55 -0
  185. package/neural-interface/lib/ui-blob-api.js +108 -0
  186. package/neural-interface/lib/ui-blob-store.js +68 -0
  187. package/neural-interface/lib/virtual-socket.js +12 -1
  188. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  189. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  190. package/neural-interface/lib/whatsapp/commands.js +13 -14
  191. package/neural-interface/lib/whatsapp/config.js +134 -20
  192. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  193. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  194. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  195. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  196. package/neural-interface/lib/whatsapp/manager.js +92 -32
  197. package/neural-interface/lib/whatsapp/paths.js +11 -1
  198. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  199. package/neural-interface/lib/whatsapp/redact.js +63 -0
  200. package/neural-interface/lib/whatsapp/service.js +343 -91
  201. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  202. package/neural-interface/package-lock.json +701 -2
  203. package/neural-interface/package.json +13 -2
  204. package/neural-interface/public/index.html +2 -0
  205. package/neural-interface/public/index2d.html +2 -0
  206. package/neural-interface/public/install-app.html +9 -6
  207. package/neural-interface/public/offline.html +19 -3
  208. package/neural-interface/public/onboarding.html +107 -2
  209. package/neural-interface/public/shared/account-i18n.js +7 -0
  210. package/neural-interface/public/shared/account-menu.js +539 -0
  211. package/neural-interface/public/shared/account-usage.js +19 -0
  212. package/neural-interface/public/shared/account-view.js +335 -0
  213. package/neural-interface/public/shared/agent-runtime-options.js +50 -14
  214. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  215. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  216. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  217. package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
  218. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  219. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  220. package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
  221. package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
  222. package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
  223. package/neural-interface/public/shared/assistant/asst-render.js +197 -23
  224. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  225. package/neural-interface/public/shared/assistant/asst-route.js +95 -343
  226. package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
  227. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  228. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  229. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  230. package/neural-interface/public/shared/assistant/asst-state.js +186 -2
  231. package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
  232. package/neural-interface/public/shared/blob-store.js +111 -22
  233. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  234. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  235. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  236. package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
  237. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  238. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  239. package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
  240. package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
  241. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  242. package/neural-interface/public/shared/claude-account-view.js +3 -0
  243. package/neural-interface/public/shared/cli-install-view.js +392 -0
  244. package/neural-interface/public/shared/cli-status.js +282 -22
  245. package/neural-interface/public/shared/constants.js +6 -1
  246. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  247. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  248. package/neural-interface/public/shared/cp/cp-styles.js +61 -0
  249. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  250. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  251. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  252. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  253. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  254. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  255. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  256. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  257. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  258. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  259. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  260. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  261. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  262. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  263. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  264. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  265. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  266. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  267. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  268. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  269. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  270. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  271. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  272. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  273. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  274. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  275. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  276. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  277. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  278. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  279. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  280. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  281. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  282. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  283. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  284. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  285. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  286. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  287. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  288. package/neural-interface/public/shared/file-links.js +339 -0
  289. package/neural-interface/public/shared/git-bash-view.js +145 -0
  290. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  291. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  292. package/neural-interface/public/shared/html-shell.js +161 -165
  293. package/neural-interface/public/shared/i18n.js +19 -1
  294. package/neural-interface/public/shared/install-details-view.js +86 -0
  295. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  296. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  297. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
  298. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  299. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
  300. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
  301. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  302. package/neural-interface/public/shared/pwa-install-page.js +32 -2
  303. package/neural-interface/public/shared/pwa-install.css +3 -3
  304. package/neural-interface/public/shared/pwa-install.js +100 -1
  305. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  306. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  307. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  308. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  309. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  310. package/neural-interface/public/shared/settings/settings-ia.js +33 -4
  311. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  312. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  313. package/neural-interface/public/shared/start-bridge.js +238 -19
  314. package/neural-interface/public/shared/storage.js +30 -3
  315. package/neural-interface/public/shared/styles.css +1710 -770
  316. package/neural-interface/public/shared/synabun-mascot.js +68 -58
  317. package/neural-interface/public/shared/tunnel-view.js +167 -0
  318. package/neural-interface/public/shared/ui-claude-panel.js +369 -229
  319. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  320. package/neural-interface/public/shared/ui-detail.js +15 -0
  321. package/neural-interface/public/shared/ui-explorer.js +22 -0
  322. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  323. package/neural-interface/public/shared/ui-file-links.js +103 -0
  324. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  325. package/neural-interface/public/shared/ui-help.js +26 -0
  326. package/neural-interface/public/shared/ui-invite.js +126 -116
  327. package/neural-interface/public/shared/ui-judgments.js +10 -1
  328. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  329. package/neural-interface/public/shared/ui-loading.js +187 -19
  330. package/neural-interface/public/shared/ui-menubar.js +25 -33
  331. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  332. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  333. package/neural-interface/public/shared/ui-navbar.js +48 -22
  334. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  335. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  336. package/neural-interface/public/shared/ui-sessions.js +9 -3
  337. package/neural-interface/public/shared/ui-settings.js +509 -490
  338. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  339. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  340. package/neural-interface/public/shared/ui-sync.js +22 -0
  341. package/neural-interface/public/shared/ui-terminal.js +40 -5
  342. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  343. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  344. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  345. package/neural-interface/public/shared/ui-update.js +594 -5
  346. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  347. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  348. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  349. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  350. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  351. package/neural-interface/public/shared/worker-end.js +253 -0
  352. package/neural-interface/public/sw.js +2 -2
  353. package/neural-interface/public/variant/2d/main.js +10 -1
  354. package/neural-interface/public/variant/3d/main.js +10 -1
  355. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  356. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  357. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  358. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  359. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  360. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  361. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  362. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  363. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  364. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  365. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  366. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  367. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  368. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  369. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  370. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  371. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  372. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  373. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  374. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  375. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  376. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  377. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  378. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  379. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  380. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  381. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  382. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  383. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  384. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  385. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  386. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  387. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  388. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  389. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  390. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  391. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  392. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  393. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  394. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  395. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  396. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  397. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  398. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  399. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  400. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  401. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  402. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  403. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  404. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  405. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  406. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  407. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  408. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  409. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  410. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  411. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  412. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  413. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  414. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  415. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  416. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  417. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  418. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  419. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  420. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  421. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  422. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  423. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  424. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  425. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  426. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  427. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  428. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  429. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  430. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  431. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  432. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  433. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  434. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  435. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  436. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  437. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  438. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  439. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  440. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  441. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  442. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  443. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  444. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  445. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  446. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  447. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  448. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  449. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  450. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  451. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  452. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  453. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  454. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  455. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  456. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  457. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  458. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  459. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  460. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  461. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  462. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  463. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  464. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  465. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  466. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  467. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  468. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  469. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  470. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  471. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  472. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  473. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  474. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  475. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  476. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  477. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  478. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  479. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  480. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  481. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  482. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  483. package/neural-interface/scripts/build-vendor.mjs +78 -2
  484. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  485. package/neural-interface/server.js +2085 -1629
  486. package/neural-interface/templates/rulesets/README.md +1 -1
  487. package/package.json +5 -8
  488. package/setup.js +53 -15
  489. package/benchmarks/browser-live-results.json +0 -79
  490. package/benchmarks/browser-v2-results.json +0 -258
  491. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  492. package/neural-interface/public/category-logos/claude.svg +0 -1
  493. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  494. package/neural-interface/public/category-logos/openai.svg +0 -1
  495. package/neural-interface/public/category-logos/synabun.png +0 -0
  496. package/neural-interface/public/claude.svg +0 -1
  497. package/neural-interface/public/gallery.svg +0 -6
  498. package/neural-interface/public/leonardoai.svg +0 -1
  499. package/neural-interface/public/mcp.svg +0 -4
  500. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  501. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  502. package/neural-interface/public/sessionicon.svg +0 -8
  503. package/neural-interface/public/toolbar-album.svg +0 -8
  504. package/neural-interface/public/toolbar-question.svg +0 -6
  505. package/neural-interface/public/toolbar-sessions.svg +0 -6
  506. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  507. package/skills/synabun/synabunicon.png +0 -0
@@ -1,3 +1,5 @@
1
+ import { redactProviderSecrets } from './provider-panel-api-key-auth.js';
2
+ import { applyClaudeLaunchEnv, CLAUDE_CREDENTIAL_ENV } from './claude-accounts.js';
1
3
  import { claudeStallSeconds } from './claude-stall-policy.js';
2
4
  import { detectProject as memoryProject } from '../../mcp-server/dist/config.js';
3
5
  // ── Claude Agent SDK bridge for the Claude sidepanel (claude-skin) ──
@@ -30,7 +32,7 @@ import { CLAUDE_NOT_INSTALLED, resolveClaudeSdkExecutable } from './claude-execu
30
32
  import { diagnoseLaunchFailure, isNativeBinaryLaunchFailure } from './native-binary-runtime.js';
31
33
  import { normalizePanelSession, startSignature, applyPanelSessionOptions, liveSettingsPatch, normalizeMcpServers, mergeTabMcpServers } from './claude-panel-session.js';
32
34
  import { SESSION_REQUESTS, runSessionRequest, slimRewind, slimMcpStatus } from './claude-panel-requests.js';
33
- import { applyTemporaryOptions, plansDirsOf, planFileOfToolCall, removeTemporaryPlanFiles } from './claude-temporary.js';
35
+ import { applyTemporaryOptions, plansDirsOf, snapshotPlanFiles, planFileOfToolCall, removeTemporaryPlanFiles } from './claude-temporary.js';
34
36
  import { rootBypassBlock } from './claude-bypass-policy.js';
35
37
  import { randomUUID } from 'node:crypto';
36
38
  import { dirname, join } from 'node:path';
@@ -107,6 +109,7 @@ export const PANEL_CAPABILITIES = Object.freeze([
107
109
  'message_flags', // session.hookEvents / promptSuggestions / subagentText / agentSummaries → the matching SDK options
108
110
  'task_control', // inbound `stop_task` / `background_tasks` → `task_control_result`; system/session_crons events
109
111
  'origin_human', // a query marked `typed: true` is sent with origin {kind:'human'}
112
+ 'greeting', // query `greetingId`: the model is told once what SynaBun greeted the user with (lib/greeting/)
110
113
  'session_requests', // inbound `session_request {id, what, args}` → `session_response`: context_usage, usage, mcp_status, mcp_reconnect, mcp_toggle, rewind_preview
111
114
  'rewind_stats', // `rewind_result` carries filesChanged / insertions / deletions
112
115
  'session_title', // query `title` names a new session where the CLI reads it
@@ -222,11 +225,11 @@ const _allSessions = new Set();
222
225
 
223
226
  function _orphanKey(wid, sid) { return sid ? `${wid}:${sid}` : wid; }
224
227
 
225
- function log(...args) { console.log('[claude-sdk]', ...args); }
228
+ function log(...args) { console.log('[claude-sdk]', ...args.map(redactProviderSecrets)); }
226
229
 
227
230
  // Panel sessions, a temporary chat (lib/claude-temporary.js): the plan files
228
231
  // Claude Code wrote for one that still have to be removed. path → { plansDirs,
229
- // since, ended, last }. One is removed once the pump of the process that wrote
232
+ // since, before, ended, last }. One is removed once the pump of the process that wrote
230
233
  // it has settled (`ended`), and stays owed after that removal, whether it
231
234
  // removed the file or found none: the pump settles when the SDK's own cleanup
232
235
  // stops waiting for the process, which can be before the process is gone, and
@@ -414,6 +417,7 @@ export class ClaudeSession {
414
417
  // ── wire helpers ──────────────────────────────────────────────────────────
415
418
 
416
419
  send(data) {
420
+ data = redactProviderSecrets(data);
417
421
  if (this.destroyed) return;
418
422
  if (this.orphanBuffer) {
419
423
  // High-volume deltas are not buffered while detached — full assistant
@@ -450,6 +454,7 @@ export class ClaudeSession {
450
454
  // without a prompt: the first real ensureQuery() adopts it if the session's
451
455
  // configuration is still the one it was started with.
452
456
  ensureQuery({ warm = false } = {}) {
457
+ if (this._authChanging) { this._startRefused = { code: 'auth_changing', message: 'Sign-in is changing. Try again.' }; return null; }
453
458
  if (this.q) return this.q;
454
459
  // Panel sessions under a named Claude account: every process start checks
455
460
  // the account again (a message, a recreation after a stall, a lost-session
@@ -479,6 +484,16 @@ export class ClaudeSession {
479
484
  return null;
480
485
  }
481
486
  let accountEnv = null;
487
+ if (!this.panel && typeof this.opts.claudeAccountId === 'string' && this.opts.claudeAccountId && this.opts.claudeAccountId !== 'default'
488
+ && typeof deps.claudeAccountLaunch === 'function') {
489
+ let launch = null;
490
+ try { launch = deps.claudeAccountLaunch(this.opts.claudeAccountId); } catch { launch = null; }
491
+ if (!launch?.ok) {
492
+ this._startRefused = { code: 'account_unavailable', message: ACCOUNT_UNAVAILABLE };
493
+ log(`session create refused: the account "${this.opts.claudeAccountId}" is no longer set up`);
494
+ return null;
495
+ }
496
+ }
482
497
  if (this.panel && this.accountId) {
483
498
  accountEnv = this._accountEnvOf(this.accountId);
484
499
  if (!accountEnv) {
@@ -602,14 +617,30 @@ export class ClaudeSession {
602
617
  // `typesafe:'off'` on a query (the live skin test) keeps this CLI's hooks
603
618
  // from paying for judgments: they read the kill switch from their env.
604
619
  // (Panel sessions: the CLI reports running / idle / requires_action only when
605
- // asked to; `idle` is its own word that a turn is over. See _noteSessionState.)
606
- env: { ...cleanEnv(), ...(this.typesafeOff ? { SYNABUN_TYPESAFE: 'off' } : {}), ...(sessionOpts.env || {}), ...(accountEnv || {}), ...(this.panel ? { CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: '1' } : {}) },
620
+ // asked to; `idle` is its own word that a turn is over. See _noteSessionState.
621
+ // SYNABUN_PANEL_SESSION tells the user-level SynaBun hooks that the panel greets here, not they.)
622
+ env: { ...cleanEnv(), ...(this.typesafeOff ? { SYNABUN_TYPESAFE: 'off' } : {}), ...(sessionOpts.env || {}), ...(accountEnv || {}), ...(this.panel ? { CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: '1', SYNABUN_PANEL_SESSION: '1' } : {}) },
607
623
  abortController: this.abortController,
608
624
  stderr: (text) => {
609
625
  const t = String(text || '').trim();
610
- if (t) this.send({ type: 'stderr', text: t });
626
+ // Raw diagnostic chunks can split a credential. API-key sessions use
627
+ // the SDK's structured errors instead of forwarding raw stderr.
628
+ if (t && !this._usesProfileApiKey) this.send({ type: 'stderr', text: t });
611
629
  },
612
630
  };
631
+ // The account decides the credential (lib/claude-accounts.js launchFor()):
632
+ // every inherited credential variable is removed, then the account's own
633
+ // config directory and, in API-key mode, its key are added. Subscription
634
+ // mode adds no key, so Claude Code uses the login in that directory. No
635
+ // other account's key, and nothing SynaBun was started with, reaches it.
636
+ const launch = this._launchFor(accountEnv);
637
+ this._usesProfileApiKey = !!launch?.env?.ANTHROPIC_API_KEY;
638
+ this._credentialMode = launch?.mode || null; // what the process was given: 'subscription' | 'apiKey'
639
+ if (launch) options.env = applyClaudeLaunchEnv(options.env, launch);
640
+ // On Windows, the Git Bash the host names for this process (lib/git-bash/: SynaBun's own copy on a PC without
641
+ // one); without it Claude Code has no Bash tool and runs the hooks in PowerShell. Panel tabs and the brain.
642
+ if (typeof deps?.childEnv === 'function') options.env = deps.childEnv(options.env);
643
+
613
644
  if (Array.isArray(sessionOpts.disallowedTools) && sessionOpts.disallowedTools.length) options.disallowedTools = [...sessionOpts.disallowedTools];
614
645
  if (this.panel) {
615
646
  // The process is launched able to take Bypass, so the tab's mode control
@@ -643,6 +674,16 @@ export class ClaudeSession {
643
674
  // Resuming a session whose prompt cache has expired pays to write it
644
675
  // again: the CLI says how much at SessionStart.
645
676
  options.hooks = mergeHookMatchers(options.hooks, {
677
+ // The greeting SynaBun showed when this conversation opened (lib/greeting/): said to the
678
+ // model once, with the first prompt, as background. The model is never asked to greet.
679
+ UserPromptSubmit: [{
680
+ hooks: [async () => {
681
+ const note = gen === this._queryGen ? this._greetingNote : '';
682
+ if (!note) return {};
683
+ this._greetingNote = '';
684
+ return { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: note } };
685
+ }],
686
+ }],
646
687
  SessionStart: [{
647
688
  hooks: [async (input) => {
648
689
  if (gen === this._queryGen && input?.prompt_cache_likely_expired === true && Number(input?.estimated_cache_write_usd) > 0) {
@@ -744,7 +785,10 @@ export class ClaudeSession {
744
785
  // (Which of the session's MCP connections is SynaBun's is asked of the live process when a tool of one is called.)
745
786
  applyTemporaryOptions(options, { mcpUrl: deps.mcpUrl || '', mcpStatus: () => this.q?.mcpServerStatus?.() });
746
787
  this._tempPlansDirs = plansDirsOf(options.env, { cwd: workDir, settings: options.settings });
747
- if (!this._tempSince) this._tempSince = Date.now();
788
+ if (!this._tempSince) {
789
+ this._tempPlansBefore = snapshotPlanFiles(this._tempPlansDirs);
790
+ this._tempSince = Date.now();
791
+ }
748
792
  }
749
793
  // The executable resolved at the top of this call. The SDK uses
750
794
  // pathToClaudeCodeExecutable verbatim with zero validation, so it was vetted
@@ -905,6 +949,29 @@ export class ClaudeSession {
905
949
  return env;
906
950
  }
907
951
 
952
+ // The launch of the account this session runs under (the host's
953
+ // claudeAccountLaunch(), lib/claude-accounts.js launchFor()), or null when
954
+ // the session names none: panel sessions always do ('' = the default
955
+ // account); any other session only when it was given `claudeAccountId` (the
956
+ // Assistant's brain). A host without claudeAccountLaunch gets what it had:
957
+ // the named account's directory and the profile key, nothing stripped for
958
+ // a default account without a key.
959
+ _launchFor(accountEnv) {
960
+ const named = this.panel ? (this.accountId || 'default') : (typeof this.opts.claudeAccountId === 'string' && this.opts.claudeAccountId ? this.opts.claudeAccountId : null);
961
+ if (!named) return null;
962
+ if (typeof deps.claudeAccountLaunch === 'function') {
963
+ let launch = null;
964
+ try { launch = deps.claudeAccountLaunch(named); } catch { launch = null; }
965
+ if (launch?.ok) return launch;
966
+ return null;
967
+ }
968
+ if (!this.panel) return null;
969
+ let key = null;
970
+ try { key = deps.claudeAccountApiKey?.(named) || null; } catch { key = null; }
971
+ if (!accountEnv && !key) return null;
972
+ return { ok: true, accountId: named, mode: key ? 'apiKey' : 'subscription', env: { ...(accountEnv || {}), ...(key ? { ANTHROPIC_API_KEY: key } : {}) }, unset: [...CLAUDE_CREDENTIAL_ENV] };
973
+ }
974
+
908
975
  // A start ensureQuery() refused (see there): tell the tab why and close the
909
976
  // turn it is waiting on. False when the last start was not refused.
910
977
  _announceRefusedStart() {
@@ -1079,7 +1146,7 @@ export class ClaudeSession {
1079
1146
  // stay owed and are swept once more when it can no longer be alive.
1080
1147
  _removeTemporaryPlans() {
1081
1148
  if (!this._tempPlans?.size) return;
1082
- const owed = { plansDirs: this._tempPlansDirs, since: this._tempSince, ended: false, last: false };
1149
+ const owed = { plansDirs: this._tempPlansDirs, since: this._tempSince, before: this._tempPlansBefore, ended: false, last: false };
1083
1150
  for (const path of this._tempPlans) _temporaryPlansOwed.set(path, owed);
1084
1151
  this._tempPlans.clear();
1085
1152
  Promise.resolve(this.pumpPromise).catch(() => {}).then(() => {
@@ -1098,6 +1165,7 @@ export class ClaudeSession {
1098
1165
  this._tempLive = false;
1099
1166
  this._tempOver = false;
1100
1167
  this._tempSince = 0;
1168
+ this._tempPlansBefore = null;
1101
1169
  }
1102
1170
 
1103
1171
  // Whether this message makes, keeps or may not make the conversation a
@@ -1367,6 +1435,11 @@ export class ClaudeSession {
1367
1435
  }
1368
1436
  return;
1369
1437
  }
1438
+ case 'rate_limit_event': {
1439
+ try { deps?.accountRateLimit?.(this.panel ? this.accountId || 'default' : this.opts.claudeAccountId || 'default', m.rate_limit_info); } catch {}
1440
+ this.sendEvent(m);
1441
+ return;
1442
+ }
1370
1443
  case 'result': {
1371
1444
  this._flushDeltas();
1372
1445
  this._handleResult(m);
@@ -1629,7 +1702,7 @@ export class ClaudeSession {
1629
1702
  this.sendEvent({
1630
1703
  type: 'system', subtype: 'session_info',
1631
1704
  info: {
1632
- account: r.account || null,
1705
+ account: this._usesProfileApiKey ? { authMethod: 'apiKey' } : r.account || null,
1633
1706
  outputStyle: r.output_style || '',
1634
1707
  availableOutputStyles: Array.isArray(r.available_output_styles) ? r.available_output_styles : [],
1635
1708
  models: Array.isArray(r.models) ? r.models : [],
@@ -2811,6 +2884,17 @@ export class ClaudeSession {
2811
2884
  } catch {}
2812
2885
  }
2813
2886
 
2887
+ // The panel echoes the id of the greeting it showed when this conversation opened: the model is
2888
+ // told once, with this prompt (the UserPromptSubmit hook in _startQuery). A slash command takes none.
2889
+ // (A prompt the person typed in the panel is also them being here, whether or not the user-level hooks are installed.)
2890
+ if (this.panel && !warmOnly && msg.typed === true && typeof prompt === 'string' && prompt.trim() && typeof deps.greetingActivity === 'function') {
2891
+ try { deps.greetingActivity({ cwd: cwd || this.cwd || null, words: prompt, temporary: this.temporary === true }); } catch { /* the prompt goes on */ }
2892
+ }
2893
+ if (this.panel && !warmOnly && typeof msg.greetingId === 'string' && typeof deps.greetingNote === 'function') {
2894
+ try { this._greetingNote = String(deps.greetingNote(msg.greetingId, { skip: typeof prompt === 'string' && prompt.trimStart().startsWith('/') }) || ''); }
2895
+ catch { this._greetingNote = ''; }
2896
+ }
2897
+
2814
2898
  // SynaBun skill injection runs FIRST; unrecognized /commands pass through to the CLI
2815
2899
  if (prompt && prompt.startsWith('/')) {
2816
2900
  try {
@@ -3101,6 +3185,28 @@ export class ClaudeSession {
3101
3185
  }
3102
3186
  }
3103
3187
 
3188
+ // Credentials are fixed in the CLI environment. Retire only this account's
3189
+ // idle panel processes before changing its key; hold starts until storage/logout
3190
+ // finishes. Other accounts and non-panel SDK callers are unaffected.
3191
+ // (The Assistant's brain names its account with `claudeAccountId`: retired the same way.)
3192
+ const sessionAccountOf = (s) => (s.panel ? (s.accountId || 'default') : (typeof s.opts?.claudeAccountId === 'string' && s.opts.claudeAccountId ? s.opts.claudeAccountId : null));
3193
+ export async function withPanelAccountAuthChange(accountId, change) {
3194
+ const sessions = [..._allSessions].filter(s => !s.destroyed && sessionAccountOf(s) === accountId);
3195
+ if (sessions.some(s => s.inTurn || s.pendingTurns || s.pendingPerms.size || s._hasPendingWork() || s._authChanging)) {
3196
+ throw Object.assign(new Error('Finish or stop this account’s work before changing sign-in.'), { status: 409 });
3197
+ }
3198
+ for (const session of sessions) session._authChanging = true;
3199
+ try {
3200
+ await Promise.all(sessions.map(session => session._endQuery({ graceful: false })));
3201
+ return await change();
3202
+ } finally { for (const session of sessions) session._authChanging = false; }
3203
+ }
3204
+
3205
+ /** Whether a live session (a panel tab's, the Assistant brain's) runs under this account. */
3206
+ export function accountInUse(accountId) {
3207
+ return [..._allSessions].some(s => !s.destroyed && sessionAccountOf(s) === accountId);
3208
+ }
3209
+
3104
3210
  // ── connection layer ─────────────────────────────────────────────────────────
3105
3211
 
3106
3212
  export function createClaudeBridge(ws) {
@@ -3189,6 +3295,14 @@ export function isSessionLive(sessionId) {
3189
3295
  return [..._allSessions].some(s => !s.destroyed && s.sessionId === sessionId && !!s.q);
3190
3296
  }
3191
3297
 
3298
+ // How many side-panel sessions are working (a turn, a question, background tasks
3299
+ // or crons, a sign-in change): the desktop app waits for them before it restarts
3300
+ // into an update (GET /api/system/activity). The Assistant's brain counts there itself.
3301
+ export function panelSessionsWorking() {
3302
+ return [..._allSessions].filter(s => s.panel && !s.destroyed
3303
+ && (s.inTurn || s.pendingTurns || s.pendingPerms.size || s._hasPendingWork() || s._authChanging)).length;
3304
+ }
3305
+
3192
3306
  export function shutdownAllBridges() {
3193
3307
  for (const session of [..._allSessions]) {
3194
3308
  try { session.destroy(); } catch {}
@@ -10,7 +10,12 @@
10
10
  //
11
11
  // resolveClaudeLauncher what a terminal or a shell would start: the
12
12
  // user's configured command, else the first
13
- // `claude` on PATH that is not inside SynaBun
13
+ // `claude` on PATH that is not inside SynaBun.
14
+ // server.js asks lib/cli-resolver.js instead
15
+ // (Settings, PATH and the OS PATH, the known
16
+ // install folders, a --version that answers),
17
+ // which follows a Windows `claude.cmd` with
18
+ // windowsClaudeTargets below
14
19
  // resolveClaudeSdkExecutable what the SDK can start. It spawns with
15
20
  // shell:false, so an npm command file on Windows
16
21
  // is followed to the executable behind it
@@ -14,7 +14,7 @@ import { spawn } from 'node:child_process';
14
14
  import { dirname, join, sep } from 'node:path';
15
15
  import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
16
16
  import { createRequire } from 'node:module';
17
- import { getAugmentedPath } from './augmented-path.js';
17
+ import { withPathEnv, getAugmentedPath } from './augmented-path.js';
18
18
 
19
19
  // Effort ladder the Claude Code CLI accepts for --effort. Models advertise their
20
20
  // own subset via supportedEffortLevels; this is the superset used as a fallback
@@ -45,6 +45,32 @@ const CACHE_TTL_MS = 10 * 60 * 1000;
45
45
  let _cache = { at: 0, key: '', models: [] };
46
46
  let _inflight = null;
47
47
 
48
+ // Who else keeps a copy of this list (the Assistant's model catalog). Told when a
49
+ // discovery returns a list that differs from the one before it, so a copy is
50
+ // dropped the moment the picker's own source changes: after a CLI update, or
51
+ // when the account's models change. The first discovery after a start counts.
52
+ const _changeListeners = new Set();
53
+
54
+ export function onClaudeModelsChanged(listener) {
55
+ if (typeof listener !== 'function') return () => {};
56
+ _changeListeners.add(listener);
57
+ return () => { _changeListeners.delete(listener); };
58
+ }
59
+
60
+ // What makes two lists the same list for a picker: ids in order, the model each
61
+ // runs as, the label, the context window and the effort levels.
62
+ export function claudeModelsSignature(models) {
63
+ return JSON.stringify((Array.isArray(models) ? models : []).map(m => [
64
+ m?.id || '', m?.resolvedModel || '', m?.label || '', m?.contextWindow || 0, (m?.effortLevels || []).join(','),
65
+ ]));
66
+ }
67
+
68
+ function notifyModelsChanged(models) {
69
+ for (const listener of [..._changeListeners]) {
70
+ try { listener(models); } catch {}
71
+ }
72
+ }
73
+
48
74
  // What a cache keyed on "the CLI" has to key on. An update never changes the
49
75
  // configured path: the native installer and Homebrew re-point a symlink at a new
50
76
  // version directory, and npm rewrites the entrypoint in place. The resolved target
@@ -214,7 +240,7 @@ export function queryClaudeCliModels(claudeBin, { cwd, timeoutMs = DISCOVERY_TIM
214
240
  try {
215
241
  child = spawnImpl(shellCommand(bin, useShell), args, {
216
242
  cwd: cwd || process.cwd(),
217
- env: { ...process.env, PATH: getAugmentedPath(), CLAUDE_CODE_ENTRYPOINT: 'sdk-cli' },
243
+ env: { ...withPathEnv(process.env, getAugmentedPath()), CLAUDE_CODE_ENTRYPOINT: 'sdk-cli' },
218
244
  stdio: ['pipe', 'pipe', 'ignore'],
219
245
  shell: useShell,
220
246
  });
@@ -262,7 +288,7 @@ export function queryClaudeCliModels(claudeBin, { cwd, timeoutMs = DISCOVERY_TIM
262
288
  // Cached discovery. `source` lets the UI distinguish a verified list from the
263
289
  // baked-in fallback. Cache is keyed on the binary's identity, so switching the
264
290
  // configured CLI (Settings > Terminal) or updating it re-discovers.
265
- export async function discoverClaudeModels(claudeBin, { force = false, cwd } = {}) {
291
+ export async function discoverClaudeModels(claudeBin, { force = false, cwd, query = queryClaudeCliModels } = {}) {
266
292
  const bin = String(claudeBin || '');
267
293
  const key = binaryIdentity(bin);
268
294
  const fresh = _cache.models.length
@@ -273,10 +299,12 @@ export async function discoverClaudeModels(claudeBin, { force = false, cwd } = {
273
299
 
274
300
  _inflight = (async () => {
275
301
  try {
276
- const raw = await queryClaudeCliModels(bin, { cwd });
302
+ const raw = await query(bin, { cwd });
277
303
  const models = normalizeClaudeModels(raw);
278
304
  if (models.length) {
305
+ const changed = claudeModelsSignature(models) !== claudeModelsSignature(_cache.models);
279
306
  _cache = { at: Date.now(), key, models };
307
+ if (changed) notifyModelsChanged(models);
280
308
  return { source: 'cli', models };
281
309
  }
282
310
  return { source: 'fallback', models: [...CLAUDE_FALLBACK_MODELS] };
@@ -372,7 +400,7 @@ export function queryClaudeCliVersion(claudeBin, { timeoutMs = 10000 } = {}) {
372
400
 
373
401
  try {
374
402
  child = spawn(shellCommand(bin, useShell), args, {
375
- env: { ...process.env, PATH: getAugmentedPath() },
403
+ env: withPathEnv(process.env, getAugmentedPath()),
376
404
  stdio: ['ignore', 'pipe', 'ignore'],
377
405
  shell: useShell,
378
406
  });
@@ -0,0 +1,221 @@
1
+ // Claude owns OAuth credentials and refresh. SynaBun owns only pasted keys
2
+ // in a separate private profile file; this adapter exposes safe auth status.
3
+ import { spawn } from 'node:child_process';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { verifyClaudeApiKey } from './claude-profile-api-key.js';
9
+ import { validateProviderApiKey, redactProviderSecrets } from './provider-panel-api-key-auth.js';
10
+
11
+ export function claudeAuthEnv(env, accountEnv) {
12
+ const clean = { ...env };
13
+ for (const key of Object.keys(clean)) {
14
+ if (/^(CLAUDECODE|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_API_KEY|ANTHROPIC_API_KEY_FILE_DESCRIPTOR|CLAUDE_CODE_USE_BEDROCK|CLAUDE_CODE_USE_VERTEX|CLAUDE_CODE_USE_FOUNDRY)$/.test(key)) delete clean[key];
15
+ }
16
+ return { ...clean, ...accountEnv, NO_COLOR: '1', FORCE_COLOR: '0' };
17
+ }
18
+
19
+ export function claudeAuthorizationUrl(text) {
20
+ for (const match of text.matchAll(/https:\/\/[^\s<>"\x1b]+/g)) {
21
+ try {
22
+ const url = new URL(match[0]);
23
+ if (['claude.ai', 'console.anthropic.com', 'platform.claude.com'].includes(url.hostname)
24
+ && !url.username && !url.password && url.searchParams.has('client_id') && url.searchParams.has('code_challenge')) return url.href;
25
+ } catch {}
26
+ }
27
+ return '';
28
+ }
29
+
30
+ export function createClaudePanelAuth({ accounts, executable, env = () => process.env, spawnProcess = spawn, timeoutMs = 10 * 60_000, statusTimeoutMs = 15_000, verifyKey = verifyClaudeApiKey, changeCredentials = async (_id, change) => change() } = {}) {
31
+ const operations = new Map();
32
+ function account(id = 'default') {
33
+ if (typeof id !== 'string' || !/^[a-z0-9-]{1,64}$/.test(id)) throw Object.assign(new Error('Unknown Claude account'), { status: 404 });
34
+ const row = accounts.profileFor ? accounts.profileFor(id) : accounts.get(id);
35
+ if (!row || row.id !== id) throw Object.assign(new Error('Unknown Claude account'), { status: 404 });
36
+ return row;
37
+ }
38
+ function launch(id, args, extraEnv = {}) {
39
+ account(id);
40
+ const found = executable();
41
+ if (!found?.path) throw new Error(found?.reason || 'Claude CLI not found. Install Claude Code to sign in.');
42
+ const script = /\.[cm]?js$/i.test(found.path);
43
+ const proc = spawnProcess(script ? process.execPath : found.path, script ? [found.path, ...args] : args, {
44
+ env: { ...claudeAuthEnv(env(), accounts.envFor(id)), ...extraEnv }, stdio: ['pipe', 'pipe', 'pipe'], shell: false, windowsHide: true,
45
+ });
46
+ proc.stdin?.on('error', () => {}); // A native exit can race a manual-code write.
47
+ return proc;
48
+ }
49
+ // Claude Code's own answer about the account's login (never a credential).
50
+ function nativeStatus(id = 'default') {
51
+ account(id);
52
+ return new Promise((resolve, reject) => {
53
+ let proc;
54
+ try { proc = launch(id, ['auth', 'status', '--json']); } catch (error) { reject(error); return; }
55
+ let output = '', settled = false;
56
+ const finish = (error, result) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolve(result); };
57
+ const timer = setTimeout(() => { proc.kill(); finish(new Error('Claude authentication status timed out. Try Refresh.')); }, statusTimeoutMs);
58
+ proc.stdout.on('data', chunk => { output = (output + chunk).slice(-65536); });
59
+ proc.stderr.on('data', () => {}); // Never forward CLI output or credential-bearing errors.
60
+ proc.on('error', () => finish(new Error('Could not start Claude Code. Check its installation.')));
61
+ proc.on('close', () => {
62
+ try {
63
+ const data = JSON.parse(output);
64
+ if (typeof data.loggedIn !== 'boolean') throw new Error();
65
+ finish(null, {
66
+ loggedIn: data.loggedIn, email: redactProviderSecrets(data.email || null), orgName: redactProviderSecrets(data.orgName || null),
67
+ authMethod: redactProviderSecrets(data.authMethod || null), subscriptionType: redactProviderSecrets(data.subscriptionType || null),
68
+ });
69
+ } catch { finish(new Error('Could not read Claude authentication. Update Claude Code to a version supporting auth status --json.')); }
70
+ });
71
+ });
72
+ }
73
+ // The account as every surface shows it (lib/claude-accounts.js describe()),
74
+ // after asking Claude Code about its subscription login. A failed check of an
75
+ // account that still has a usable API key is not an error: the key is used.
76
+ async function read(id = 'default') {
77
+ account(id);
78
+ // An account running on its API key is signed in whatever its subscription
79
+ // says: no process is started for the answer. The subscription half is
80
+ // asked in the background, so the account list can show it later.
81
+ const current = accounts.describeForClient?.(id);
82
+ if (current?.mode === 'apiKey' && current.signedIn === true) {
83
+ Promise.resolve(accounts.refreshStatus?.([id])).catch(() => {});
84
+ return { accountId: id, account: current, state: snapshot(id) };
85
+ }
86
+ let status = null, failure = null;
87
+ try { status = await nativeStatus(id); } catch (error) { failure = error; }
88
+ account(id); // A profile removed meanwhile is gone, whatever the CLI said.
89
+ if (status) accounts.noteNativeStatus?.(id, status);
90
+ const row = accounts.describeForClient ? accounts.describeForClient(id) : null;
91
+ if (failure && !(row?.mode === 'apiKey' && row.signedIn)) throw failure;
92
+ return { accountId: id, account: row || legacyAccount(id, status), state: snapshot(id) };
93
+ }
94
+ // A host without the account model (an older caller, a test double).
95
+ function legacyAccount(id, status) {
96
+ if (accounts.readApiKey?.(id)) return { loggedIn: true, email: null, authMethod: 'apiKey', subscriptionType: null };
97
+ return { loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod, subscriptionType: status.subscriptionType };
98
+ }
99
+ const keyPending = new Set();
100
+ async function signInApiKey(id, key) {
101
+ account(id); key = validateProviderApiKey('claude', key);
102
+ if (keyPending.has(id) || ['starting', 'pending'].includes(snapshot(id).status)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
103
+ keyPending.add(id);
104
+ try {
105
+ await verifyKey(key);
106
+ account(id); // A removed profile must never be recreated by a late reply.
107
+ await changeCredentials(id, () => { account(id); accounts.saveApiKey(id, key); });
108
+ operations.set(id, { status: 'success', authUrl: '' });
109
+ } finally { keyPending.delete(id); key = null; }
110
+ }
111
+ // `credential`: 'apiKey' removes the key SynaBun keeps, 'subscription' runs
112
+ // Claude Code's own logout in the account's config directory, 'all' both.
113
+ async function signOut(id, credential = 'all') {
114
+ account(id);
115
+ if (!['all', 'apiKey', 'subscription'].includes(credential)) throw Object.assign(new Error('Choose which sign-in to remove.'), { status: 400 });
116
+ if (keyPending.has(id)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
117
+ const op = operations.get(id);
118
+ if (op && ['starting', 'pending'].includes(op.status) && credential !== 'apiKey') cancel(id, op.operationId);
119
+ keyPending.add(id);
120
+ try {
121
+ await changeCredentials(id, async () => {
122
+ if (credential !== 'subscription') accounts.clearApiKey(id);
123
+ if (credential === 'apiKey') return;
124
+ operations.delete(id);
125
+ await new Promise((resolve, reject) => {
126
+ let proc, timer;
127
+ try { proc = launch(id, ['auth', 'logout']); } catch { reject(new Error('Could not sign out of Claude. Try again.')); return; }
128
+ let settled = false;
129
+ const finish = ok => { if (settled) return; settled = true; clearTimeout(timer); ok ? resolve() : reject(new Error('Could not sign out of Claude. Try again.')); };
130
+ proc.stdout.on('data', () => {}); proc.stderr.on('data', () => {});
131
+ proc.on('error', () => finish(false)); proc.on('close', code => finish(code === 0));
132
+ timer = setTimeout(() => { proc.kill(); finish(false); }, statusTimeoutMs);
133
+ });
134
+ accounts.noteNativeStatus?.(id, { loggedIn: false });
135
+ });
136
+ } finally { keyPending.delete(id); }
137
+ }
138
+ // What the account's sessions use from now on; its idle panel processes end
139
+ // so the next message starts under the new credential.
140
+ async function setMode(id, mode) {
141
+ account(id);
142
+ if (keyPending.has(id) || ['starting', 'pending'].includes(snapshot(id).status)) throw Object.assign(new Error('Finish sign-in first.'), { status: 409 });
143
+ return changeCredentials(id, () => accounts.setMode(id, mode));
144
+ }
145
+ function snapshot(id) {
146
+ const op = operations.get(id);
147
+ return op ? { accountId: id, operationId: op.operationId, status: op.status, authUrl: op.authUrl, error: op.error || '' } : { accountId: id, status: 'idle' };
148
+ }
149
+ function start(id = 'default') {
150
+ account(id);
151
+ if (keyPending.has(id)) throw Object.assign(new Error('Sign-in already pending'), { status: 409 });
152
+ const existing = operations.get(id);
153
+ if (existing && ['starting', 'pending'].includes(existing.status)) return snapshot(id);
154
+ // BROWSER is a supported Claude Code handoff. Suppress its OS browser launch:
155
+ // the panel opens the URL printed by the native command instead.
156
+ const dir = mkdtempSync(join(tmpdir(), 'synabun-claude-oauth-'));
157
+ const browser = join(dir, process.platform === 'win32' ? 'browser.cmd' : 'browser');
158
+ writeFileSync(browser, process.platform === 'win32' ? '@exit /b 0\r\n' : '#!/bin/sh\nexit 0\n', { mode: 0o700 });
159
+ const op = { operationId: randomUUID(), status: 'starting', authUrl: '', process: null };
160
+ operations.set(id, op);
161
+ let timer, forceTimer, output = '';
162
+ const cleanup = () => { clearTimeout(timer); clearTimeout(forceTimer); rmSync(dir, { recursive: true, force: true }); };
163
+ const finish = (status, error = '') => {
164
+ if (!['starting', 'pending'].includes(op.status)) return;
165
+ op.status = status; op.error = error; op.authUrl = '';
166
+ clearTimeout(timer);
167
+ };
168
+ op.stop = (status, error) => {
169
+ finish(status, error);
170
+ op.process?.kill();
171
+ forceTimer = setTimeout(() => op.process?.kill('SIGKILL'), 2000); forceTimer.unref?.();
172
+ };
173
+ try {
174
+ op.process = launch(id, ['auth', 'login', '--claudeai'], { BROWSER: browser });
175
+ const collect = chunk => {
176
+ output = (output + chunk).slice(-65536);
177
+ if (['starting', 'pending'].includes(op.status)) {
178
+ const url = claudeAuthorizationUrl(output.slice(0, output.lastIndexOf('\n') + 1));
179
+ if (url) { op.authUrl = url; op.status = 'pending'; }
180
+ }
181
+ };
182
+ op.process.stdout.on('data', collect); op.process.stderr.on('data', collect);
183
+ op.process.on('error', () => { finish('error', 'Could not start Claude Code. Check its installation.'); cleanup(); });
184
+ op.process.on('close', async code => {
185
+ cleanup();
186
+ if (!['starting', 'pending'].includes(op.status)) return;
187
+ if (code !== 0) { finish('error', 'Claude sign-in failed. Retry, or update Claude Code if auth login is unavailable.'); return; }
188
+ try {
189
+ const status = await nativeStatus(id);
190
+ if (status.loggedIn) {
191
+ // A subscription just signed into is what the account uses from now on.
192
+ if (accounts.noteSubscriptionSignIn) accounts.noteSubscriptionSignIn(id, status);
193
+ else accounts.recordIdentity?.(id);
194
+ finish('success');
195
+ } else finish('error', 'Claude Code finished without confirming sign-in. Try again.');
196
+ } catch (error) { finish('error', error.message); }
197
+ });
198
+ timer = setTimeout(() => op.stop('error', 'Claude sign-in timed out. Try again.'), timeoutMs); timer.unref?.();
199
+ } catch (error) { finish('error', error.message); cleanup(); }
200
+ return snapshot(id);
201
+ }
202
+ function cancel(id, operationId) {
203
+ account(id);
204
+ const op = operations.get(id);
205
+ if (!op || op.operationId !== operationId) throw Object.assign(new Error('This sign-in attempt is no longer active. Refresh its status.'), { status: 409 });
206
+ if (['starting', 'pending'].includes(op.status)) op.stop('cancelled', '');
207
+ return snapshot(id);
208
+ }
209
+ function submitCode(id, operationId, code) {
210
+ account(id);
211
+ const op = operations.get(id);
212
+ if (!op || op.operationId !== operationId || op.status !== 'pending') throw Object.assign(new Error('This sign-in attempt is no longer active. Refresh its status.'), { status: 409 });
213
+ if (typeof code !== 'string' || !code.trim() || code.length > 4096 || /[\r\n\0]/.test(code)) throw Object.assign(new Error('Paste a single sign-in code from Claude.'), { status: 400 });
214
+ if (!op.process?.stdin?.writable) throw new Error('Claude Code is no longer accepting a sign-in code. Retry sign-in.');
215
+ op.process.stdin.write(`${code.trim()}\n`, error => {
216
+ if (error && op.status === 'pending') op.stop('error', 'Claude Code stopped accepting the sign-in code. Retry sign-in.');
217
+ });
218
+ return snapshot(id);
219
+ }
220
+ return { start, read, nativeStatus, cancel, submitCode, snapshot, signInApiKey, signOut, setMode, close: () => { for (const [id, op] of operations) if (['starting', 'pending'].includes(op.status)) cancel(id, op.operationId); } };
221
+ }
@@ -0,0 +1,62 @@
1
+ // SynaBun's own file, never Claude's native credential store. Atomic 0600 writes
2
+ // work on every supported platform without a Keychain prompt or service naming.
3
+ import { constants, openSync, closeSync, fstatSync, readFileSync, writeFileSync, mkdirSync, renameSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { validateProviderApiKey, providerApiKeyError } from './provider-panel-api-key-auth.js';
7
+ export const CLAUDE_API_KEY_FILE = '.synabun-api-key';
8
+ // The same file holds an account's key for either provider (Claude: an
9
+ // Anthropic key in its CLAUDE_CONFIG_DIR; Codex: an OpenAI key in its
10
+ // CODEX_HOME, lib/codex-accounts.js), checked against that provider's format.
11
+ export function readProfileApiKey(home, provider = 'claude') {
12
+ let fd;
13
+ try {
14
+ fd = openSync(join(home, CLAUDE_API_KEY_FILE), constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
15
+ const stat = fstatSync(fd);
16
+ if (!stat.isFile() || (process.platform !== 'win32' && (stat.mode & 0o077))) throw new Error('Unsafe credential permissions');
17
+ return validateProviderApiKey(provider, readFileSync(fd, 'utf8'));
18
+ } catch (error) {
19
+ if (error.code === 'ENOENT') return null;
20
+ throw new Error('Could not read the saved API key. Sign in again.');
21
+ } finally { if (fd !== undefined) closeSync(fd); }
22
+ }
23
+ export function readClaudeProfileApiKey(home) { return readProfileApiKey(home, 'claude'); }
24
+ export function saveProfileApiKey(home, key, provider = 'claude') {
25
+ key = validateProviderApiKey(provider, key);
26
+ mkdirSync(home, { recursive: true, mode: 0o700 });
27
+ const target = join(home, CLAUDE_API_KEY_FILE), temp = `${target}.${randomUUID()}.tmp`;
28
+ try { writeFileSync(temp, key, { mode: 0o600, flag: 'wx' }); renameSync(temp, target); }
29
+ finally { rmSync(temp, { force: true }); }
30
+ }
31
+ export function saveClaudeProfileApiKey(home, key) { saveProfileApiKey(home, key, 'claude'); }
32
+ export function clearProfileApiKey(home) { rmSync(join(home, CLAUDE_API_KEY_FILE), { force: true }); }
33
+ export function clearClaudeProfileApiKey(home) { clearProfileApiKey(home); }
34
+ export async function verifyClaudeApiKey(key, fetchRequest = fetch) {
35
+ let response;
36
+ try {
37
+ response = await fetchRequest('https://api.anthropic.com/v1/models?limit=1', {
38
+ headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' }, signal: AbortSignal.timeout(15000), redirect: 'error',
39
+ });
40
+ } catch { throw providerApiKeyError('verification_unreachable'); }
41
+ finally { key = null; }
42
+ // Discard the body without letting a cleanup failure mask the HTTP result.
43
+ // Neither provider response bodies nor exception text are surfaced or logged.
44
+ try { await response.body?.cancel(); } catch {}
45
+ if ([401, 403].includes(response.status)) throw providerApiKeyError('key_rejected');
46
+ if (!response.ok) throw providerApiKeyError('verification_unavailable');
47
+ }
48
+
49
+ // OpenAI's read-only model list answers whether a key is accepted. The key goes
50
+ // in the Authorization header only; the body and any error text are dropped.
51
+ export async function verifyOpenAiApiKey(key, fetchRequest = fetch) {
52
+ let response;
53
+ try {
54
+ response = await fetchRequest('https://api.openai.com/v1/models', {
55
+ headers: { authorization: `Bearer ${key}` }, signal: AbortSignal.timeout(15000), redirect: 'error',
56
+ });
57
+ } catch { throw providerApiKeyError('verification_unreachable', 'codex'); }
58
+ finally { key = null; }
59
+ try { await response.body?.cancel(); } catch {}
60
+ if ([401, 403].includes(response.status)) throw providerApiKeyError('key_rejected', 'codex');
61
+ if (!response.ok) throw providerApiKeyError('verification_unavailable', 'codex');
62
+ }