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.
- package/CHANGELOG.md +97 -0
- package/NOTICE +5 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +59 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +153 -18
- package/hooks/claude-code/pre-compact.mjs +11 -0
- package/hooks/claude-code/prompt-submit.mjs +60 -184
- package/hooks/claude-code/session-start.mjs +73 -16
- package/hooks/claude-code/shared.mjs +26 -3
- package/hooks/claude-code/stop.mjs +5 -5
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/start-launcher.js +88 -1
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +10 -7
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +34 -13
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tui.js +2 -1
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +33 -14
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tui.ts +2 -1
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +2126 -209
- package/neural-interface/i18n/pt-BR.json +2123 -207
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/assistant-api.js +316 -57
- package/neural-interface/lib/assistant-brains/claude.js +19 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +91 -14
- package/neural-interface/lib/assistant-dispatch.js +494 -138
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +73 -12
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +488 -59
- package/neural-interface/lib/assistant-task-prompt.js +13 -3
- package/neural-interface/lib/assistant-test-density.js +142 -0
- package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
- package/neural-interface/lib/assistant-user-context.js +908 -0
- package/neural-interface/lib/assistant-user-knowledge.js +328 -0
- package/neural-interface/lib/assistant-user-learn.js +404 -0
- package/neural-interface/lib/assistant-user-threads.js +226 -0
- package/neural-interface/lib/assistant-user-writer.js +183 -0
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +123 -9
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +33 -5
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/claude-temporary.js +46 -7
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/cloudflared/install.js +533 -0
- package/neural-interface/lib/cloudflared/manager.js +545 -0
- package/neural-interface/lib/cloudflared/pins.js +115 -0
- package/neural-interface/lib/cloudflared/pins.json +63 -0
- package/neural-interface/lib/cloudflared/tunnel.js +265 -0
- package/neural-interface/lib/codex-account-auth.js +178 -0
- package/neural-interface/lib/codex-accounts.js +489 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +120 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/editor-completion-endpoint.js +264 -0
- package/neural-interface/lib/editor-completion-opencode.js +436 -0
- package/neural-interface/lib/editor-completion.js +714 -0
- package/neural-interface/lib/editor-config.js +149 -0
- package/neural-interface/lib/editor-files-api.js +268 -0
- package/neural-interface/lib/editor-git-api.js +134 -0
- package/neural-interface/lib/file-explorer-api.js +1039 -0
- package/neural-interface/lib/file-explorer-system.js +355 -0
- package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
- package/neural-interface/lib/file-explorer-watch.js +239 -0
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +234 -0
- package/neural-interface/lib/greeting/api.js +89 -0
- package/neural-interface/lib/greeting/budgets.js +90 -0
- package/neural-interface/lib/greeting/engine.js +461 -0
- package/neural-interface/lib/greeting/facts.js +320 -0
- package/neural-interface/lib/greeting/sources.js +252 -0
- package/neural-interface/lib/greeting/writer.js +139 -0
- package/neural-interface/lib/http-guards.js +189 -13
- package/neural-interface/lib/install-details.js +145 -0
- package/neural-interface/lib/machine-names.js +88 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-loop-providers.js +26 -8
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +4 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +701 -2
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/install-app.html +9 -6
- package/neural-interface/public/offline.html +19 -3
- package/neural-interface/public/onboarding.html +107 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +539 -0
- package/neural-interface/public/shared/account-usage.js +19 -0
- package/neural-interface/public/shared/account-view.js +335 -0
- package/neural-interface/public/shared/agent-runtime-options.js +50 -14
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +32 -2
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +430 -81
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +140 -61
- package/neural-interface/public/shared/assistant/asst-menu.js +27 -11
- package/neural-interface/public/shared/assistant/asst-panel.js +198 -42
- package/neural-interface/public/shared/assistant/asst-render.js +197 -23
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +95 -343
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +534 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +186 -2
- package/neural-interface/public/shared/assistant/asst-styles.js +247 -11
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +80 -118
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +164 -107
- package/neural-interface/public/shared/cdx/cdx-tabs.js +348 -668
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +61 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
- package/neural-interface/public/shared/editor/editor-ai.js +619 -0
- package/neural-interface/public/shared/editor/editor-commands.js +212 -0
- package/neural-interface/public/shared/editor/editor-compare.js +103 -0
- package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
- package/neural-interface/public/shared/editor/editor-doc.js +257 -0
- package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
- package/neural-interface/public/shared/editor/editor-git.js +95 -0
- package/neural-interface/public/shared/editor/editor-languages.js +156 -0
- package/neural-interface/public/shared/editor/editor-lint.js +229 -0
- package/neural-interface/public/shared/editor/editor-loader.js +51 -0
- package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
- package/neural-interface/public/shared/editor/editor-menu.js +233 -0
- package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
- package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
- package/neural-interface/public/shared/editor/editor-palette.js +125 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
- package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
- package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
- package/neural-interface/public/shared/editor/editor-safety.js +500 -0
- package/neural-interface/public/shared/editor/editor-search.js +352 -0
- package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
- package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
- package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
- package/neural-interface/public/shared/editor/editor-theme.js +244 -0
- package/neural-interface/public/shared/editor/editor-view.js +332 -0
- package/neural-interface/public/shared/explorer/fx-api.js +216 -0
- package/neural-interface/public/shared/explorer/fx-links.js +204 -0
- package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
- package/neural-interface/public/shared/explorer/fx-main.js +865 -0
- package/neural-interface/public/shared/explorer/fx-names.js +87 -0
- package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
- package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
- package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
- package/neural-interface/public/shared/file-links.js +339 -0
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
- package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
- package/neural-interface/public/shared/html-shell.js +161 -165
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +131 -17
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +22 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +158 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +32 -2
- package/neural-interface/public/shared/pwa-install.css +3 -3
- package/neural-interface/public/shared/pwa-install.js +100 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
- package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
- package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
- package/neural-interface/public/shared/settings/settings-ia.js +33 -4
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/showcase-privacy.js +489 -0
- package/neural-interface/public/shared/start-bridge.js +238 -19
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +1710 -770
- package/neural-interface/public/shared/synabun-mascot.js +68 -58
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +369 -229
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-detail.js +15 -0
- package/neural-interface/public/shared/ui-explorer.js +22 -0
- package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
- package/neural-interface/public/shared/ui-file-links.js +103 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +187 -19
- package/neural-interface/public/shared/ui-menubar.js +25 -33
- package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
- package/neural-interface/public/shared/ui-navbar.js +48 -22
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +509 -490
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +22 -0
- package/neural-interface/public/shared/ui-terminal.js +40 -5
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-tunnel.js +130 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
- package/neural-interface/public/shared/ui-update.js +594 -5
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/ui-whiteboard.js +12 -0
- package/neural-interface/public/shared/ui-worker-end.js +245 -0
- package/neural-interface/public/shared/ui-workspaces.js +41 -27
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +253 -0
- package/neural-interface/public/sw.js +2 -2
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
- package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
- package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
- package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
- package/neural-interface/scripts/build-vendor.mjs +78 -2
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +2085 -1629
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +5 -8
- package/setup.js +53 -15
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/neural-interface/lib/codex-capabilities.d.ts +0 -41
- package/neural-interface/public/category-logos/claude.svg +0 -1
- package/neural-interface/public/category-logos/gemini-color.svg +0 -1
- package/neural-interface/public/category-logos/openai.svg +0 -1
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +0 -1
- package/neural-interface/public/gallery.svg +0 -6
- package/neural-interface/public/leonardoai.svg +0 -1
- package/neural-interface/public/mcp.svg +0 -4
- package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
- package/neural-interface/public/pin-svgrepo-com.svg +0 -25
- package/neural-interface/public/sessionicon.svg +0 -8
- package/neural-interface/public/toolbar-album.svg +0 -8
- package/neural-interface/public/toolbar-question.svg +0 -6
- package/neural-interface/public/toolbar-sessions.svg +0 -6
- package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
- 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
|
-
|
|
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
|
-
|
|
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)
|
|
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,
|
|
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
|
|
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:
|
|
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
|
+
}
|