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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// SynaBun — the facts behind Settings → About (GET /api/system/about)
|
|
2
|
+
//
|
|
3
|
+
// What the About page shows: the app (version, channel, how it was installed, the Git branch of a clone), the
|
|
4
|
+
// runtime it runs on, the links its package.json and README give, and every dependency of the packages the
|
|
5
|
+
// product is made of, each with the version actually installed and its license. Everything is read from files
|
|
6
|
+
// on disk: package.json files, node_modules/<name>/package.json, the vendored bundles' manifest.json, LICENSE,
|
|
7
|
+
// .git/HEAD. No npm, no git process, no network. The answer is built on the first get() and kept for the life of
|
|
8
|
+
// the process: the code that runs does not change until a restart either.
|
|
9
|
+
//
|
|
10
|
+
// Never in it: an environment variable, a token, a setting. A path is the data folder's and nothing else.
|
|
11
|
+
|
|
12
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
13
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { formatSynabunVersion, synabunReleaseChannel } from './synabun-version.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The packages the product is made of, relative to the package root. One that is not on disk (the desktop app's
|
|
19
|
+
* shell is in a Git clone only; inside the desktop app it is packed where the server cannot read it) is left out.
|
|
20
|
+
* `onDemand`: installed later, by SynaBun itself, into `installDirs` (the WhatsApp connector, lib/whatsapp/installer.js).
|
|
21
|
+
*/
|
|
22
|
+
export const ABOUT_PACKAGES = Object.freeze([
|
|
23
|
+
Object.freeze({ id: 'neural-interface', dir: 'neural-interface' }),
|
|
24
|
+
Object.freeze({ id: 'mcp-server', dir: 'mcp-server' }),
|
|
25
|
+
Object.freeze({ id: 'desktop', dir: 'packaging/desktop' }),
|
|
26
|
+
Object.freeze({ id: 'whatsapp-connector', dir: 'neural-interface/lib/whatsapp/connector', onDemand: true }),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** Front-end libraries bundled into public/vendor/ by scripts/build-vendor.mjs: folder → package and product name. */
|
|
30
|
+
const VENDOR_BUNDLES = Object.freeze({
|
|
31
|
+
three: { label: 'three.js', name: 'three' },
|
|
32
|
+
xterm: { label: 'xterm.js', name: '@xterm/xterm' },
|
|
33
|
+
codemirror: { label: 'CodeMirror' },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// The community links README.md gives (package.json has no field for them).
|
|
37
|
+
const COMMUNITY_LINKS = Object.freeze([
|
|
38
|
+
Object.freeze({ id: 'discord', url: 'https://discord.gg/x6yWqE9GZP' }),
|
|
39
|
+
Object.freeze({ id: 'x', url: 'https://x.com/SynabunAI' }),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const NPM_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/i;
|
|
43
|
+
const MAX_TEXT = 200;
|
|
44
|
+
|
|
45
|
+
const text = (value, max = MAX_TEXT) => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : null);
|
|
46
|
+
|
|
47
|
+
function readJson(path) {
|
|
48
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readText(path, max = 256 * 1024) {
|
|
52
|
+
try {
|
|
53
|
+
const body = readFileSync(path, 'utf8');
|
|
54
|
+
return body.length > max ? body.slice(0, max) : body;
|
|
55
|
+
} catch { return ''; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A package.json's license, as written: an SPDX expression, the old object, or the old list. */
|
|
59
|
+
export function licenseOf(pkg) {
|
|
60
|
+
if (!pkg || typeof pkg !== 'object') return null;
|
|
61
|
+
const one = (value) => (typeof value === 'string' ? text(value, 64) : value && typeof value === 'object' ? text(value.type, 64) : null);
|
|
62
|
+
if (pkg.license != null) return one(pkg.license);
|
|
63
|
+
if (Array.isArray(pkg.licenses)) {
|
|
64
|
+
const all = pkg.licenses.map(one).filter(Boolean);
|
|
65
|
+
return all.length ? all.join(' OR ').slice(0, 64) : null;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Is `child` the folder `parent` or inside it? */
|
|
71
|
+
function within(child, parent) {
|
|
72
|
+
const rel = relative(parent, child);
|
|
73
|
+
return rel === '' || (!!rel && !rel.startsWith('..') && !isAbsolute(rel));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Where Node would find `name` for a package in `dir`: node_modules of `dir` and of each folder above it, up to
|
|
78
|
+
* `root` and never past it, then the extra folders (an on-demand install). The package.json found, or null.
|
|
79
|
+
*/
|
|
80
|
+
function installedPackage(name, dir, root, extraDirs = []) {
|
|
81
|
+
if (!NPM_NAME.test(name)) return null;
|
|
82
|
+
const folders = [];
|
|
83
|
+
if (within(dir, root)) {
|
|
84
|
+
for (let at = dir; ; at = resolve(at, '..')) {
|
|
85
|
+
folders.push(at);
|
|
86
|
+
if (at === root || resolve(at, '..') === at) break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const extra of extraDirs) folders.push(extra);
|
|
90
|
+
for (const folder of folders) {
|
|
91
|
+
const pkg = readJson(join(folder, 'node_modules', ...name.split('/'), 'package.json'));
|
|
92
|
+
if (pkg && typeof pkg.version === 'string') return pkg;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** One dependency: the range package.json declares, the version installed (or null) and its license. */
|
|
98
|
+
function dependencyRow(name, declared, dir, root, extraDirs, extra = {}) {
|
|
99
|
+
const pkg = installedPackage(name, dir, root, extraDirs);
|
|
100
|
+
return {
|
|
101
|
+
name: String(name).slice(0, 214),
|
|
102
|
+
declared: text(String(declared ?? ''), 120) || '',
|
|
103
|
+
version: pkg ? text(pkg.version, 64) : null,
|
|
104
|
+
installed: !!pkg,
|
|
105
|
+
license: licenseOf(pkg),
|
|
106
|
+
...extra,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const byName = (a, b) => a.name.localeCompare(b.name, 'en');
|
|
111
|
+
|
|
112
|
+
/** The dependency groups of one package of the product, or null when its package.json is not on disk. */
|
|
113
|
+
export function readProductPackage(spec, { root, installDirs = [] } = {}) {
|
|
114
|
+
const dir = resolve(root, spec.dir);
|
|
115
|
+
const pkg = readJson(join(dir, 'package.json'));
|
|
116
|
+
if (!pkg || typeof pkg !== 'object') return null;
|
|
117
|
+
const deps = (field) => (pkg[field] && typeof pkg[field] === 'object' && !Array.isArray(pkg[field]) ? Object.entries(pkg[field]) : []);
|
|
118
|
+
const optional = new Set(deps('optionalDependencies').map(([name]) => name));
|
|
119
|
+
const runtime = [
|
|
120
|
+
...deps('dependencies').filter(([name]) => !optional.has(name)).map(([name, range]) => dependencyRow(name, range, dir, root, installDirs)),
|
|
121
|
+
...deps('optionalDependencies').map(([name, range]) => dependencyRow(name, range, dir, root, installDirs, { optional: true })),
|
|
122
|
+
].sort(byName);
|
|
123
|
+
const dev = deps('devDependencies').map(([name, range]) => dependencyRow(name, range, dir, root, installDirs)).sort(byName);
|
|
124
|
+
return {
|
|
125
|
+
id: spec.id,
|
|
126
|
+
name: text(pkg.name, 214) || spec.id,
|
|
127
|
+
version: text(pkg.version, 64),
|
|
128
|
+
onDemand: !!spec.onDemand,
|
|
129
|
+
runtime,
|
|
130
|
+
dev,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The bundles in public/vendor/: each with the version its manifest records, and the packages it holds. */
|
|
135
|
+
export function readVendored(vendorDir, { licenseFrom } = {}) {
|
|
136
|
+
let folders = [];
|
|
137
|
+
try { folders = readdirSync(vendorDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort(); } catch { return []; }
|
|
138
|
+
const out = [];
|
|
139
|
+
for (const folder of folders) {
|
|
140
|
+
const manifest = readJson(join(vendorDir, folder, 'manifest.json'));
|
|
141
|
+
const version = text(manifest?.version, 64);
|
|
142
|
+
if (!manifest || !version) continue; // no manifest, no honest version
|
|
143
|
+
const known = VENDOR_BUNDLES[folder] || {};
|
|
144
|
+
let packages;
|
|
145
|
+
if (manifest.packages && typeof manifest.packages === 'object') {
|
|
146
|
+
packages = Object.entries(manifest.packages)
|
|
147
|
+
.filter(([name, v]) => NPM_NAME.test(name) && typeof v === 'string' && v)
|
|
148
|
+
.map(([name, v]) => ({ name, version: v.slice(0, 64) }));
|
|
149
|
+
} else {
|
|
150
|
+
const name = known.name || folder;
|
|
151
|
+
if (!NPM_NAME.test(name)) continue;
|
|
152
|
+
packages = [{ name, version: version.replace(/-[0-9a-f]{8}$/, '') }];
|
|
153
|
+
}
|
|
154
|
+
for (const p of packages) p.license = licenseFrom ? licenseFrom(p.name, p.version) : null;
|
|
155
|
+
packages.sort(byName);
|
|
156
|
+
out.push({ id: folder, label: known.label || folder, version: version.replace(/-[0-9a-f]{8}$/, ''), packages });
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** `git+https://github.com/o/r.git` → `https://github.com/o/r`; null for anything but an https address. */
|
|
162
|
+
export function repositoryUrl(repository) {
|
|
163
|
+
const raw = typeof repository === 'string' ? repository : repository?.url;
|
|
164
|
+
if (typeof raw !== 'string') return null;
|
|
165
|
+
const url = raw.trim().replace(/^git\+/, '').replace(/\.git$/, '').replace(/\/+$/, '');
|
|
166
|
+
return /^https:\/\/[^\s/]+\/[^\s]+$/.test(url) ? url : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const httpsOrNull = (value) => (typeof value === 'string' && /^https:\/\/\S+$/.test(value.trim()) ? value.trim().replace(/\/+$/, '') : null);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The project's links, from the root package.json: repository, homepage, bugs, name. The website's docs and
|
|
173
|
+
* changelog pages, the npm page and the community links are the addresses README.md gives; the license is the
|
|
174
|
+
* README's ./LICENSE on GitHub's default branch. A link whose source is missing is left out.
|
|
175
|
+
*/
|
|
176
|
+
export function aboutLinks(pkg) {
|
|
177
|
+
const repository = repositoryUrl(pkg?.repository);
|
|
178
|
+
const github = repository && /^https:\/\/github\.com\//.test(repository) ? repository : null;
|
|
179
|
+
const website = httpsOrNull(pkg?.homepage);
|
|
180
|
+
const issues = httpsOrNull(typeof pkg?.bugs === 'string' ? pkg.bugs : pkg?.bugs?.url) || (github ? `${github}/issues` : null);
|
|
181
|
+
const name = typeof pkg?.name === 'string' && NPM_NAME.test(pkg.name) ? pkg.name : null;
|
|
182
|
+
const links = [
|
|
183
|
+
{ id: 'repository', url: repository },
|
|
184
|
+
{ id: 'releases', url: github ? `${github}/releases` : null },
|
|
185
|
+
{ id: 'changelog', url: website ? `${website}/changelog` : null },
|
|
186
|
+
{ id: 'issues', url: issues },
|
|
187
|
+
{ id: 'website', url: website },
|
|
188
|
+
{ id: 'docs', url: website ? `${website}/docs` : null },
|
|
189
|
+
{ id: 'npm', url: name && !pkg.private ? `https://www.npmjs.com/package/${name}` : null },
|
|
190
|
+
{ id: 'license', url: github ? `${github}/blob/main/LICENSE` : null },
|
|
191
|
+
...COMMUNITY_LINKS,
|
|
192
|
+
];
|
|
193
|
+
return links.filter((link) => link.url);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The years and holder of the copyright line, from LICENSE (or NOTICE); the holder is package.json's author when it names one. */
|
|
197
|
+
export function copyrightOf({ license = '', notice = '', author = null } = {}) {
|
|
198
|
+
const lines = `${license}\n${notice}`.split(/\r?\n/);
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
const m = /^\s*Copyright\s+(?:\(c\)\s*|©\s*)?(\d{4}(?:\s*[-–]\s*\d{4})?)\s+(.+?)\s*$/i.exec(line);
|
|
201
|
+
if (!m || /\[|\]/.test(m[2])) continue; // the Apache template's "[yyyy] [name of copyright owner]"
|
|
202
|
+
return { years: m[1].replace(/\s*[-–]\s*/, '–'), holder: author || text(m[2], 120) };
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const authorName = (author) => text(typeof author === 'string' ? author.replace(/\s*[<(].*$/, '') : author?.name, 120);
|
|
208
|
+
|
|
209
|
+
/** The branch and short commit of the checkout at `root`, read from .git (a worktree's `gitdir:` file too). */
|
|
210
|
+
export function readGitHead(root) {
|
|
211
|
+
let gitDir = join(root, '.git');
|
|
212
|
+
try {
|
|
213
|
+
if (statSync(gitDir).isFile()) {
|
|
214
|
+
const m = /^gitdir:\s*(.+)$/m.exec(readText(gitDir, 4096));
|
|
215
|
+
if (!m) return null;
|
|
216
|
+
gitDir = resolve(root, m[1].trim());
|
|
217
|
+
}
|
|
218
|
+
} catch { return null; }
|
|
219
|
+
const head = readText(join(gitDir, 'HEAD'), 4096).trim();
|
|
220
|
+
if (!head) return null;
|
|
221
|
+
const common = (() => {
|
|
222
|
+
const pointer = readText(join(gitDir, 'commondir'), 4096).trim();
|
|
223
|
+
return pointer ? resolve(gitDir, pointer) : gitDir;
|
|
224
|
+
})();
|
|
225
|
+
const ref = /^ref:\s*(refs\/heads\/([^\s]+))$/.exec(head);
|
|
226
|
+
let commit = null;
|
|
227
|
+
if (ref && !ref[1].split('/').includes('..')) {
|
|
228
|
+
for (const dir of [gitDir, common]) {
|
|
229
|
+
const loose = readText(join(dir, ...ref[1].split('/')), 4096).trim();
|
|
230
|
+
if (/^[0-9a-f]{40,64}$/.test(loose)) { commit = loose; break; }
|
|
231
|
+
}
|
|
232
|
+
if (!commit) {
|
|
233
|
+
const packed = readText(join(common, 'packed-refs'));
|
|
234
|
+
const line = packed.split(/\r?\n/).find((l) => l.endsWith(` ${ref[1]}`));
|
|
235
|
+
if (line && /^[0-9a-f]{40,64} /.test(line)) commit = line.split(' ')[0];
|
|
236
|
+
}
|
|
237
|
+
} else if (/^[0-9a-f]{40,64}$/.test(head)) {
|
|
238
|
+
commit = head;
|
|
239
|
+
}
|
|
240
|
+
const branch = ref ? text(ref[2], 120) : null;
|
|
241
|
+
if (!branch && !commit) return null;
|
|
242
|
+
return { branch, commit: commit ? commit.slice(0, 7) : null };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The operating system's own name and version: macOS from SystemVersion.plist, Linux from os-release, Windows from os.version(). */
|
|
246
|
+
export function readOsInfo({ platform = process.platform, readFile = readText, osApi = os } = {}) {
|
|
247
|
+
const kernel = `${osApi.type()} ${osApi.release()}`;
|
|
248
|
+
let name = null;
|
|
249
|
+
let version = null;
|
|
250
|
+
if (platform === 'darwin') {
|
|
251
|
+
const plist = readFile('/System/Library/CoreServices/SystemVersion.plist', 64 * 1024);
|
|
252
|
+
const key = (k) => text(new RegExp(`<key>${k}</key>\\s*<string>([^<]+)</string>`).exec(plist)?.[1], 64);
|
|
253
|
+
name = key('ProductName') || 'macOS';
|
|
254
|
+
version = key('ProductVersion');
|
|
255
|
+
} else if (platform === 'linux') {
|
|
256
|
+
const release = readFile('/etc/os-release', 64 * 1024);
|
|
257
|
+
const field = (k) => text(new RegExp(`^${k}=("?)([^"\\n]*)\\1\\s*$`, 'm').exec(release)?.[2], 120);
|
|
258
|
+
name = field('NAME') || 'Linux';
|
|
259
|
+
version = field('VERSION_ID') || field('VERSION');
|
|
260
|
+
} else if (platform === 'win32') {
|
|
261
|
+
name = text(typeof osApi.version === 'function' ? osApi.version() : '', 120) || 'Windows';
|
|
262
|
+
version = text(osApi.release(), 64);
|
|
263
|
+
} else {
|
|
264
|
+
name = text(osApi.type(), 64);
|
|
265
|
+
version = text(osApi.release(), 64);
|
|
266
|
+
}
|
|
267
|
+
return { name, version, kernel: text(kernel, 120), platform, arch: process.arch };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The About facts of this install, built on the first get() and kept.
|
|
272
|
+
*
|
|
273
|
+
* @param {Object} o
|
|
274
|
+
* @param {string} o.packageRoot the SynaBun package (PACKAGE_ROOT)
|
|
275
|
+
* @param {string} o.dataHome the data folder (shown on the page; it is the owner's, and the route is the owner's)
|
|
276
|
+
* @param {number|string} o.port the port the server listens on
|
|
277
|
+
* @param {'desktop'|'app'|'npm'|'git'} o.install how this copy was installed
|
|
278
|
+
* @param {Object<string, string[]>} [o.installDirs] more folders to resolve a package's dependencies in, by package id
|
|
279
|
+
* @param {string} [o.vendorDir] the vendored bundles (public/vendor of the Neural Interface)
|
|
280
|
+
* @param {() => Object} [o.runtime] the runtime facts (tests); default: this process
|
|
281
|
+
*/
|
|
282
|
+
export function createSystemAbout({ packageRoot, dataHome, port, install, installDirs = {}, vendorDir, runtime } = {}) {
|
|
283
|
+
const root = resolve(packageRoot);
|
|
284
|
+
let cached = null;
|
|
285
|
+
|
|
286
|
+
const build = () => {
|
|
287
|
+
const pkg = readJson(join(root, 'package.json')) || {};
|
|
288
|
+
const version = typeof pkg.version === 'string' ? (formatSynabunVersion(pkg.version) || text(pkg.version, 64)) : null;
|
|
289
|
+
const author = authorName(pkg.author);
|
|
290
|
+
const packages = ABOUT_PACKAGES.map((spec) => readProductPackage(spec, { root, installDirs: installDirs[spec.id] || [] })).filter(Boolean);
|
|
291
|
+
const ni = resolve(root, 'neural-interface');
|
|
292
|
+
const vendored = readVendored(vendorDir || join(ni, 'public', 'vendor'), {
|
|
293
|
+
// The bundle was built from the packages in the Neural Interface's node_modules: their license, when they are there.
|
|
294
|
+
licenseFrom: (name) => licenseOf(installedPackage(name, ni, root)),
|
|
295
|
+
});
|
|
296
|
+
const count = (list) => list.length;
|
|
297
|
+
const counts = {
|
|
298
|
+
runtime: packages.reduce((n, p) => n + count(p.runtime), 0),
|
|
299
|
+
dev: packages.reduce((n, p) => n + count(p.dev), 0),
|
|
300
|
+
vendored: vendored.reduce((n, b) => n + count(b.packages), 0),
|
|
301
|
+
};
|
|
302
|
+
counts.total = counts.runtime + counts.dev + counts.vendored;
|
|
303
|
+
const facts = runtime ? runtime() : {
|
|
304
|
+
node: process.versions.node,
|
|
305
|
+
sqlite: process.versions.sqlite || null,
|
|
306
|
+
os: readOsInfo(),
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
app: {
|
|
310
|
+
name: 'SynaBun',
|
|
311
|
+
package: text(pkg.name, 214),
|
|
312
|
+
version,
|
|
313
|
+
channel: version ? synabunReleaseChannel(version) : 'stable',
|
|
314
|
+
description: text(pkg.description),
|
|
315
|
+
license: licenseOf(pkg),
|
|
316
|
+
author,
|
|
317
|
+
copyright: copyrightOf({ license: readText(join(root, 'LICENSE')), notice: readText(join(root, 'NOTICE')), author }),
|
|
318
|
+
install: ['desktop', 'app', 'npm', 'git'].includes(install) ? install : null,
|
|
319
|
+
git: install === 'git' ? readGitHead(root) : null,
|
|
320
|
+
},
|
|
321
|
+
runtime: {
|
|
322
|
+
node: facts.node || null,
|
|
323
|
+
sqlite: facts.sqlite || null,
|
|
324
|
+
os: facts.os || null,
|
|
325
|
+
dataHome: typeof dataHome === 'string' ? dataHome : null,
|
|
326
|
+
port: Number(port) || null,
|
|
327
|
+
},
|
|
328
|
+
links: aboutLinks(pkg),
|
|
329
|
+
dependencies: { packages, vendored, counts },
|
|
330
|
+
builtAt: new Date().toISOString(),
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
get() {
|
|
336
|
+
if (!cached) cached = build();
|
|
337
|
+
return cached;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynaBun — is anything running that a restart would end?
|
|
3
|
+
*
|
|
4
|
+
* The desktop app installs an update only while this says idle
|
|
5
|
+
* (packaging/desktop/src/updater/controller.mjs): a restart ends every agent
|
|
6
|
+
* run, side-panel reply, loop and backup the server holds. server.js hands in
|
|
7
|
+
* one counter per kind, each reading the state the server already keeps
|
|
8
|
+
* (`GET /api/system/activity`); this module only adds them up. Pure, tested in
|
|
9
|
+
* tests/system-activity.test.mjs.
|
|
10
|
+
*
|
|
11
|
+
* A counter that throws counts as `unknown`: when SynaBun cannot tell, it is
|
|
12
|
+
* not idle.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The kinds an answer may name, in the order the interface lists them. */
|
|
16
|
+
export const ACTIVITY_KINDS = Object.freeze([
|
|
17
|
+
'assistant', 'claude-panel', 'codex-panel', 'opencode-panel', 'claude-cli', 'loop', 'schedule', 'backup', 'maintenance', 'unknown',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/** A Claude Code session that took a prompt this recently may still be working on it. */
|
|
21
|
+
export const CLI_RECENT_MS = 10 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `sources` is `{ kind: () => count }`. Returns `{ idle, busy: [{ kind,
|
|
25
|
+
* count }], checkedAt }`, busy in ACTIVITY_KINDS order, one entry per kind.
|
|
26
|
+
*/
|
|
27
|
+
export function collectActivity(sources = {}, { now = () => Date.now(), log = () => {} } = {}) {
|
|
28
|
+
const counts = new Map();
|
|
29
|
+
const add = (kind, count) => counts.set(kind, (counts.get(kind) || 0) + count);
|
|
30
|
+
for (const [name, read] of Object.entries(sources || {})) {
|
|
31
|
+
const kind = ACTIVITY_KINDS.includes(name) ? name : 'unknown';
|
|
32
|
+
let count;
|
|
33
|
+
try {
|
|
34
|
+
count = Number(typeof read === 'function' ? read() : read);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
log(`activity: ${name} could not be read: ${error?.message || error}`);
|
|
37
|
+
add('unknown', 1);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!Number.isFinite(count) || count < 0) { add('unknown', 1); continue; }
|
|
41
|
+
if (count >= 1) add(kind, Math.floor(count));
|
|
42
|
+
}
|
|
43
|
+
const busy = ACTIVITY_KINDS.filter(kind => counts.get(kind) > 0).map(kind => ({ kind, count: counts.get(kind) }));
|
|
44
|
+
return { idle: busy.length === 0, busy, checkedAt: new Date(now()).toISOString() };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** How many of `items` satisfy `test`, for a Set, a Map's values, an array or nothing. */
|
|
48
|
+
export function countWhere(items, test = Boolean) {
|
|
49
|
+
if (!items) return 0;
|
|
50
|
+
const values = items instanceof Map ? items.values() : items;
|
|
51
|
+
let count = 0;
|
|
52
|
+
for (const item of values) if (test(item)) count += 1;
|
|
53
|
+
return count;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Claude Code sessions (a SynaBun terminal or any other) that took a prompt
|
|
58
|
+
* within CLI_RECENT_MS: the hooks report a prompt, never its end, so a recent
|
|
59
|
+
* one counts as working. `sessions` is the server's session registry.
|
|
60
|
+
*/
|
|
61
|
+
export function recentCliSessions(sessions, { now = Date.now(), windowMs = CLI_RECENT_MS } = {}) {
|
|
62
|
+
return countWhere(sessions, (entry) => {
|
|
63
|
+
const at = Number(entry?.lastActivity) || 0;
|
|
64
|
+
return at > 0 && now - at >= 0 && now - at < windowMs;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// terminal happened to launch the SynaBun server.
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
|
-
import { basename } from 'node:path';
|
|
7
|
+
import { basename, win32 } from 'node:path';
|
|
8
|
+
import { pathEnvValue, withPathEnv } from './augmented-path.js';
|
|
8
9
|
|
|
9
10
|
// Identity/session variables that belong to the terminal or IDE the SERVER was
|
|
10
11
|
// started from. Leaking them makes CLIs believe they run inside VS Code, tmux,
|
|
@@ -62,7 +63,9 @@ export function buildTerminalEnv({
|
|
|
62
63
|
if (platform !== 'win32' && !env.LANG && !env.LC_ALL && !env.LC_CTYPE) {
|
|
63
64
|
env.LANG = localeFallback;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
const merged = { ...env, ...(profileEnv || {}), ...(extraEnv || {}) };
|
|
67
|
+
const pathValue = [extraEnv, profileEnv, base].find(layer => Object.keys(layer || {}).some(key => key.toUpperCase() === 'PATH'));
|
|
68
|
+
return withPathEnv(merged, pathEnvValue(pathValue || {}), platform);
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export function shellSupportsLoginCommand(shellPath) {
|
|
@@ -80,7 +83,12 @@ export function interactiveShellArgs(platform = process.platform) {
|
|
|
80
83
|
* (PATH from .zprofile, brew shellenv) before the CLI starts.
|
|
81
84
|
*/
|
|
82
85
|
export function cliWrapperArgs(shellPath, cmd, platform = process.platform) {
|
|
83
|
-
if (platform === 'win32')
|
|
86
|
+
if (platform === 'win32') {
|
|
87
|
+
const name = win32.basename(shellPath).toLowerCase();
|
|
88
|
+
if (/^(powershell|pwsh)(\.exe)?$/.test(name)) return ['-NoExit', '-Command', cmd];
|
|
89
|
+
if (/^(ba|z|fi)?sh(\.exe)?$/.test(name)) return ['-lc', `${cmd}; exec "${shellPath.replace(/\\/g, '/')}" -l`];
|
|
90
|
+
return ['/d', '/s', '/k', cmd];
|
|
91
|
+
}
|
|
84
92
|
if (shellSupportsLoginCommand(shellPath)) return ['-lc', `${cmd}; exec $SHELL -l`];
|
|
85
93
|
return ['-c', `${cmd}; exec $SHELL`];
|
|
86
94
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// User-facing terminals start from today's OS environment, not the runtime's
|
|
2
|
+
// private Node/npm PATH. Shared by every PTY spawn and PATH diagnostics.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathEnvValue, withPathEnv } from './augmented-path.js';
|
|
5
|
+
import { dedupePath, expandWindowsVars, splitPathValue } from './os-path.js';
|
|
6
|
+
import { cmdEscapeArgument, cmdEscapeCommand, spawnArgsFor } from './cli-resolver.js';
|
|
7
|
+
|
|
8
|
+
// synabunDirs: the packaged runtime, always last and never earlier. extraDirs:
|
|
9
|
+
// the global bin of the npm that came with it, added before them when nothing
|
|
10
|
+
// of the user's has put it on PATH already (then it stays where the user has it).
|
|
11
|
+
export function composeTerminalPath({ env = process.env, platform = process.platform, os = {}, launchers = [], synabunDirs = [], extraDirs = [] } = {}) {
|
|
12
|
+
const impl = platform === 'win32' ? path.win32 : path.posix;
|
|
13
|
+
const normalize = dir => platform === 'win32' ? dir.replace(/\//g, '\\').toLowerCase().replace(/[\\/]+$/, '') : dir.replace(/\/+$/, '') || '/';
|
|
14
|
+
const privateDirs = new Set(synabunDirs.filter(Boolean).map(normalize));
|
|
15
|
+
const entries = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
const add = (dir, source) => {
|
|
18
|
+
if (platform === 'win32') dir = expandWindowsVars(dir, env);
|
|
19
|
+
const key = normalize(dir);
|
|
20
|
+
if (!dir || seen.has(key) || (source !== 'SynaBun' && privateDirs.has(key))) return;
|
|
21
|
+
seen.add(key); entries.push({ dir, source });
|
|
22
|
+
};
|
|
23
|
+
for (const dir of os.entries || []) add(dir, os.sources?.find(entry => normalize(entry.dir) === normalize(dir))?.source || 'login-shell');
|
|
24
|
+
for (const found of launchers) if (found.path) add(impl.dirname(found.path), 'CLI folder');
|
|
25
|
+
// An unavailable OS reader falls back to the inherited environment. Never
|
|
26
|
+
// let packaged runtime folders lead user tools, even in this fallback.
|
|
27
|
+
if (!os.entries?.length) for (const dir of splitPathValue(pathEnvValue(env), platform)) add(dir, 'inherited');
|
|
28
|
+
for (const dir of extraDirs) if (dir) add(dir, 'npm global');
|
|
29
|
+
for (const dir of synabunDirs) if (dir) add(dir, 'SynaBun');
|
|
30
|
+
const value = dedupePath(entries.map(entry => entry.dir), platform).join(platform === 'win32' ? ';' : ':');
|
|
31
|
+
return { env: withPathEnv(env, value, platform), entries, value };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const posixQuote = value => `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
35
|
+
|
|
36
|
+
export function terminalArgument(value, shell, platform = process.platform) {
|
|
37
|
+
const name = path.win32.basename(shell).replace(/\.exe$/i, '').toLowerCase();
|
|
38
|
+
if (platform !== 'win32' || ['bash', 'zsh', 'fish', 'sh', 'dash', 'ksh'].includes(name)) return posixQuote(value);
|
|
39
|
+
if (name === 'powershell' || name === 'pwsh') return `'${String(value).replace(/'/g, "''")}'`;
|
|
40
|
+
return cmdEscapeArgument(value, false);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Render a resolver-owned absolute launch spec for the selected shell. */
|
|
44
|
+
export function terminalLaunchCommand(found, shell, { platform = process.platform, env = process.env, execPath = process.execPath } = {}) {
|
|
45
|
+
if (!found?.spec) return null;
|
|
46
|
+
const launch = spawnArgsFor(found.spec, [], { env, execPath });
|
|
47
|
+
const words = [launch.command, ...launch.args];
|
|
48
|
+
const name = path.win32.basename(shell).replace(/\.exe$/i, '').toLowerCase();
|
|
49
|
+
if (platform !== 'win32' || ['bash', 'zsh', 'fish', 'sh', 'dash', 'ksh'].includes(name)) return words.map(posixQuote).join(' ');
|
|
50
|
+
if (/(?:powershell|pwsh)(?:\.exe)?$/i.test(shell)) return `& ${words.map(word => `'${String(word).replace(/'/g, "''")}'`).join(' ')}`;
|
|
51
|
+
// cmd shims already have their inner escaping from spawnArgsFor. Keep that
|
|
52
|
+
// command intact; native executables use the same escaping as the resolver.
|
|
53
|
+
if (found.spec.kind === 'cmd') return `${cmdEscapeCommand(launch.command)} /d /s /c ${launch.args.at(-1)}`;
|
|
54
|
+
return [cmdEscapeCommand(launch.command), ...launch.args.map(word => cmdEscapeArgument(word, false))].join(' ');
|
|
55
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// ── The UI blob store's routes ──
|
|
2
|
+
// Per-entry storage for the fat keys of the interface's state (rendered
|
|
3
|
+
// transcript snapshots, graph node positions, the code editor's copies of
|
|
4
|
+
// unsaved buffers): a write sends one entry, not the whole map.
|
|
5
|
+
//
|
|
6
|
+
// GET /api/ui-blobs/:ns every entry of a namespace, { [id]: value }
|
|
7
|
+
// GET /api/ui-blobs/:ns/:id one entry (404 when there is none)
|
|
8
|
+
// PUT /api/ui-blobs/:ns/:id write one entry; ?maxEntries= / ?maxChars= evict the oldest → { ok, evicted }
|
|
9
|
+
// POST /api/ui-blobs/:ns/:id the same (navigator.sendBeacon can only POST)
|
|
10
|
+
// DELETE /api/ui-blobs/:ns/:id → { ok }
|
|
11
|
+
//
|
|
12
|
+
// A namespace is one of a fixed list, by its exact name as the router decoded
|
|
13
|
+
// it. One of them may be the owner's alone (the editor's copies hold file
|
|
14
|
+
// content): for it the `ownerGuard` is asked on every route, so the refusal
|
|
15
|
+
// holds however the path was spelled.
|
|
16
|
+
//
|
|
17
|
+
// A write or a delete may carry `?rev=<n>&writer=<id>` (public/shared/blob-store.js).
|
|
18
|
+
// Of two requests of one page for one entry, the one with the higher number is
|
|
19
|
+
// what stays, whichever arrives first: the other answers { ok: true, stale: true }
|
|
20
|
+
// and changes nothing (lib/ui-blob-store.js createBlobOrder). Without the two
|
|
21
|
+
// fields, and from another page, a request is applied as it arrives. A number
|
|
22
|
+
// counts once the store has done what was asked, not before: a write or a
|
|
23
|
+
// delete that failed (500) is applied when the page sends it again.
|
|
24
|
+
|
|
25
|
+
import { Router } from 'express';
|
|
26
|
+
import { blobRevision, createBlobOrder } from './ui-blob-store.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} deps
|
|
30
|
+
* @param {Iterable<string>} deps.namespaces the namespaces there are
|
|
31
|
+
* @param {{ getNamespace: Function, get: Function, put: Function, delete: Function, prune: Function }} deps.store
|
|
32
|
+
* lib/ui-blob-store.js: blobGetNamespace, blobGet, blobPut, blobDelete, blobPrune
|
|
33
|
+
* @param {string} [deps.ownerNamespace] the namespace only the owner reaches
|
|
34
|
+
* @param {{ refusal: (req: object) => ({ status: number, code: string, error: string } | null) }} [deps.ownerGuard]
|
|
35
|
+
* lib/http-guards.js ownerRouteGuard
|
|
36
|
+
* @param {{ stale: Function, applied: Function }} [deps.order] which request of a page is the newer
|
|
37
|
+
* (createBlobOrder). The store's `put` and `delete` are synchronous: nothing comes between the question
|
|
38
|
+
* (`stale`), the store's work and its record (`applied`).
|
|
39
|
+
*/
|
|
40
|
+
export function createUiBlobRouter({ namespaces, store, ownerNamespace = null, ownerGuard = null, order = createBlobOrder() } = {}) {
|
|
41
|
+
if (!store) throw new Error('createUiBlobRouter requires a store');
|
|
42
|
+
const known = new Set(namespaces || []);
|
|
43
|
+
const router = Router();
|
|
44
|
+
|
|
45
|
+
function namespaceOf(req, res) {
|
|
46
|
+
const ns = req.params.ns;
|
|
47
|
+
if (!known.has(ns)) { res.status(400).json({ error: `unknown namespace: ${ns}` }); return null; }
|
|
48
|
+
// The namespace as Express decoded it: the editor's copies of unsaved buffers hold file content.
|
|
49
|
+
if (ownerNamespace && ns === ownerNamespace && ownerGuard) {
|
|
50
|
+
const refused = ownerGuard.refusal(req);
|
|
51
|
+
if (refused) { res.status(refused.status).json({ ok: false, code: refused.code, error: refused.error }); return null; }
|
|
52
|
+
}
|
|
53
|
+
return ns;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
router.get('/api/ui-blobs/:ns', (req, res) => {
|
|
57
|
+
const ns = namespaceOf(req, res);
|
|
58
|
+
if (!ns) return;
|
|
59
|
+
try { res.json(store.getNamespace(ns)); }
|
|
60
|
+
catch (err) { res.status(500).json({ error: err.message }); }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
router.get('/api/ui-blobs/:ns/:id', (req, res) => {
|
|
64
|
+
const ns = namespaceOf(req, res);
|
|
65
|
+
if (!ns) return;
|
|
66
|
+
try {
|
|
67
|
+
const value = store.get(ns, req.params.id);
|
|
68
|
+
if (value === null) return res.status(404).json({ error: 'not found' });
|
|
69
|
+
res.json(value);
|
|
70
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function write(req, res) {
|
|
74
|
+
const ns = namespaceOf(req, res);
|
|
75
|
+
if (!ns) return;
|
|
76
|
+
try {
|
|
77
|
+
const revision = blobRevision(req.query);
|
|
78
|
+
// Older than what this page already had applied to the entry (a write that arrives after its own delete,
|
|
79
|
+
// or after a newer write): it changes nothing.
|
|
80
|
+
if (order.stale(ns, req.params.id, revision)) return res.json({ ok: true, evicted: [], stale: true });
|
|
81
|
+
store.put(ns, req.params.id, req.body);
|
|
82
|
+
// Counted now that it is stored, and not before: a write the store refused is sent again with its number.
|
|
83
|
+
order.applied(ns, req.params.id, revision);
|
|
84
|
+
const maxEntries = parseInt(req.query.maxEntries, 10) || 0;
|
|
85
|
+
const maxChars = parseInt(req.query.maxChars, 10) || 0;
|
|
86
|
+
let evicted = [];
|
|
87
|
+
if (maxEntries || maxChars) evicted = store.prune(ns, { maxEntries, maxTotalChars: maxChars });
|
|
88
|
+
res.json({ ok: true, evicted });
|
|
89
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
90
|
+
}
|
|
91
|
+
router.put('/api/ui-blobs/:ns/:id', write);
|
|
92
|
+
// POST alias — sendBeacon can only POST
|
|
93
|
+
router.post('/api/ui-blobs/:ns/:id', write);
|
|
94
|
+
|
|
95
|
+
router.delete('/api/ui-blobs/:ns/:id', (req, res) => {
|
|
96
|
+
const ns = namespaceOf(req, res);
|
|
97
|
+
if (!ns) return;
|
|
98
|
+
try {
|
|
99
|
+
const revision = blobRevision(req.query);
|
|
100
|
+
if (order.stale(ns, req.params.id, revision)) return res.json({ ok: true, stale: true });
|
|
101
|
+
store.delete(ns, req.params.id);
|
|
102
|
+
order.applied(ns, req.params.id, revision);
|
|
103
|
+
res.json({ ok: true });
|
|
104
|
+
} catch (err) { res.status(500).json({ error: err.message }); }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return router;
|
|
108
|
+
}
|