synabun 2.0.0-beta → 2.0.5
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 +65 -0
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/THIRD-PARTY-LICENSES.md +51 -0
- package/benchmarks/fixtures/browser/README.md +1 -1
- package/benchmarks/fixtures/desktop/README.md +1 -1
- package/docs/whatsapp.md +2 -2
- 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 +20 -0
- package/hooks/claude-code/stop.mjs +5 -5
- package/launcher.mjs +40 -0
- package/lib/claude-hooks.js +61 -8
- package/lib/client-config-repair.js +4 -3
- package/lib/data-home-migration.js +4 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +5 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/start-launcher.js +1396 -0
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -3
- package/neural-interface/i18n/en.json +1540 -154
- package/neural-interface/i18n/pt-BR.json +1538 -153
- package/neural-interface/lib/assistant-api.js +62 -6
- package/neural-interface/lib/assistant-brains/claude.js +4 -0
- package/neural-interface/lib/assistant-catalog.js +179 -27
- package/neural-interface/lib/assistant-config.js +31 -2
- package/neural-interface/lib/assistant-dispatch.js +62 -13
- package/neural-interface/lib/assistant-envelope.js +9 -6
- package/neural-interface/lib/assistant-hidden-models.js +393 -66
- package/neural-interface/lib/assistant-memory.js +24 -10
- package/neural-interface/lib/assistant-persona.js +6 -2
- package/neural-interface/lib/assistant-pricing.js +21 -6
- package/neural-interface/lib/assistant-runtime.js +181 -12
- 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 +181 -0
- package/neural-interface/lib/augmented-path.js +23 -3
- package/neural-interface/lib/browser-background-tab.js +134 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +678 -61
- package/neural-interface/lib/claude-agent-bridge.js +87 -153
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-model-catalog.js +82 -93
- package/neural-interface/lib/claude-temporary.js +46 -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/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-diff-api.js +103 -0
- package/neural-interface/lib/git-read.js +202 -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/machine-names.js +88 -0
- package/neural-interface/lib/memory-api.js +31 -7
- package/neural-interface/lib/native-binary-runtime.js +69 -337
- package/neural-interface/lib/native-loop-providers.js +40 -49
- package/neural-interface/lib/offline-page.js +54 -0
- package/neural-interface/lib/plugin-installer.js +4 -2
- package/neural-interface/lib/project-storage.js +5 -7
- package/neural-interface/lib/request-gate.js +589 -0
- package/neural-interface/lib/session-title-generator.js +5 -0
- package/neural-interface/lib/synabun-update-plan.js +19 -0
- package/neural-interface/lib/ui-blob-api.js +108 -0
- package/neural-interface/lib/ui-blob-store.js +68 -0
- package/neural-interface/package-lock.json +701 -253
- package/neural-interface/package.json +13 -2
- package/neural-interface/public/install-app.html +34 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/offline.html +402 -66
- package/neural-interface/public/onboarding.html +264 -147
- package/neural-interface/public/shared/agent-runtime-options.js +22 -6
- package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
- package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
- package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
- package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
- package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
- package/neural-interface/public/shared/assistant/asst-render.js +219 -67
- package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
- package/neural-interface/public/shared/assistant/asst-route.js +23 -329
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -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-state.js +185 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
- package/neural-interface/public/shared/blob-store.js +111 -22
- package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
- package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
- package/neural-interface/public/shared/command-runner-model.js +30 -0
- package/neural-interface/public/shared/constants.js +6 -1
- package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +15 -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/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 +169 -170
- 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 +99 -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 +5 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
- package/neural-interface/public/shared/pwa-install-page.js +62 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +127 -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 +18 -4
- package/neural-interface/public/shared/settings/settings-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
- package/neural-interface/public/shared/start-bridge.js +802 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
- package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
- package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
- package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
- package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
- package/neural-interface/public/shared/styles.css +2219 -1117
- package/neural-interface/public/shared/synabun-mascot.js +20 -56
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/tunnel-view.js +167 -0
- package/neural-interface/public/shared/ui-claude-panel.js +160 -19
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- 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-help.js +26 -0
- package/neural-interface/public/shared/ui-invite.js +126 -116
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +4 -0
- package/neural-interface/public/shared/ui-loading.js +367 -12
- package/neural-interface/public/shared/ui-menubar.js +21 -55
- 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 +45 -21
- package/neural-interface/public/shared/ui-settings.js +491 -547
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
- package/neural-interface/public/shared/ui-sync.js +16 -0
- package/neural-interface/public/shared/ui-terminal.js +153 -149
- 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-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/worker-end.js +251 -0
- package/neural-interface/public/sw.js +3 -1
- package/neural-interface/public/variant/2d/main.js +2 -1
- package/neural-interface/public/variant/3d/main.js +2 -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/rebuild-pty.js +15 -12
- package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
- package/neural-interface/server.js +1092 -1212
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/package.json +13 -7
- package/postinstall.js +11 -81
- package/setup.js +111 -6
- package/benchmarks/browser-live-results.json +0 -79
- package/benchmarks/browser-v2-results.json +0 -258
- package/docs/browser-v2.md +0 -109
- package/docs/hook-integrity.md +0 -89
- package/docs/judgments.md +0 -182
- package/docs/style-guide.md +0 -246
- 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/scripts/audit-claude-hook-usage.mjs +0 -12
- package/scripts/audit-memory-duplicates.mjs +0 -44
- package/scripts/measure-claude-hook-overhead.mjs +0 -77
- package/scripts/recover-user-config.mjs +0 -254
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* SynaBun SessionStart Hook for Claude Code
|
|
5
5
|
*
|
|
6
|
-
* Injects a lean session-start context:
|
|
7
|
-
*
|
|
6
|
+
* Injects a lean session-start context: the memory header and (when
|
|
7
|
+
* applicable) compaction indexing instructions. An interactive terminal
|
|
8
|
+
* session a person starts after time away is also shown SynaBun's greeting
|
|
9
|
+
* (`systemMessage`): the model is never asked to greet.
|
|
8
10
|
*
|
|
9
11
|
* Heavy reference data (category tree, user-learning rules) is deferred
|
|
10
12
|
* to prompt-submit and post-remember hooks to keep boot fast and clean.
|
|
@@ -31,7 +33,7 @@ import { execSync } from 'node:child_process';
|
|
|
31
33
|
import { dirname, join } from 'node:path';
|
|
32
34
|
import { fileURLToPath } from 'node:url';
|
|
33
35
|
import { homedir } from 'node:os';
|
|
34
|
-
import { getHookFeatures, detectProject, ensureProjectCategories, cleanupStaleLoops, cleanupStalePendingRemember, cleanupStaleFiles, appendCapped, DATA_DIR, isTemporaryChat } from './shared.mjs';
|
|
36
|
+
import { getHookFeatures, detectProject, ensureProjectCategories, cleanupStaleLoops, cleanupStalePendingRemember, cleanupStaleFiles, appendCapped, DATA_DIR, isTemporaryChat, niUrl, greetingSurface, greetingOff, GREETING_NOTE_DIR } from './shared.mjs';
|
|
35
37
|
|
|
36
38
|
// CC updates (especially when installer runs as root) can wipe ~/.claude.json
|
|
37
39
|
// or strip mcpServers.SynaBun, leaving the side panel/CLI with MCP disabled
|
|
@@ -65,7 +67,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
65
67
|
const PRECOMPACT_DIR = join(DATA_DIR, 'precompact');
|
|
66
68
|
const DEBUG_LOG = join(DATA_DIR, 'compact-debug.log');
|
|
67
69
|
const LOOP_DIR = join(DATA_DIR, 'loop');
|
|
68
|
-
// NOTE:
|
|
70
|
+
// NOTE: the template greeting and its helpers are gone. SynaBun writes the greeting itself (openGreeting below).
|
|
69
71
|
|
|
70
72
|
function getGitBranch(cwd) {
|
|
71
73
|
try {
|
|
@@ -142,6 +144,45 @@ function designSystemLine(cwd) {
|
|
|
142
144
|
} catch { return ''; }
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
// --- Greeting (neural-interface/lib/greeting/) ---
|
|
148
|
+
|
|
149
|
+
/** The line the user is shown: SynaBun's words, then what they were based on and what they cost. */
|
|
150
|
+
function greetingLine(greeting) {
|
|
151
|
+
const head = greeting.away ? `SynaBun \u00b7 back after ${greeting.away}` : 'SynaBun';
|
|
152
|
+
const records = Array.isArray(greeting.basis) ? greeting.basis.length : 0;
|
|
153
|
+
const cost = greeting.source === 'writer'
|
|
154
|
+
? `written ahead by ${greeting.model || 'Claude'}${Number(greeting.costUsd) > 0 ? ` for $${Number(greeting.costUsd).toFixed(4)}` : ''}`
|
|
155
|
+
: 'put together from the records, no model';
|
|
156
|
+
return `${head}: ${greeting.text}\n(${records === 1 ? 'based on 1 record' : `based on ${records} records`}, ${cost})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Ask SynaBun's greeting engine whether this opening is a return worth a greeting (a lookup: the
|
|
161
|
+
* sentence was written ahead of time). → the line to show the user, or ''. The note for the model
|
|
162
|
+
* is kept for prompt-submit.mjs, which adds it to the first prompt, once.
|
|
163
|
+
*/
|
|
164
|
+
async function openGreeting({ sessionId, cwd }) {
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(`${niUrl()}/api/greeting/open`, {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { 'Content-Type': 'application/json' },
|
|
169
|
+
body: JSON.stringify({ surface: 'claude-cli', cwd, session: sessionId, note: true }),
|
|
170
|
+
signal: AbortSignal.timeout(1200),
|
|
171
|
+
});
|
|
172
|
+
if (!response.ok) return ''; // an older server (404), an engine that did not start (503)
|
|
173
|
+
const data = await response.json();
|
|
174
|
+
const greeting = data?.greeting;
|
|
175
|
+
if (!greeting || typeof greeting.text !== 'string' || !greeting.text.trim()) return '';
|
|
176
|
+
if (typeof data.note === 'string' && data.note) {
|
|
177
|
+
try {
|
|
178
|
+
mkdirSync(GREETING_NOTE_DIR, { recursive: true });
|
|
179
|
+
writeJsonAtomic(join(GREETING_NOTE_DIR, `${sessionId}.json`), { id: greeting.id || null, note: data.note, at: new Date().toISOString() });
|
|
180
|
+
} catch { /* the greeting is still shown; the model simply is not told */ }
|
|
181
|
+
}
|
|
182
|
+
return greetingLine(greeting);
|
|
183
|
+
} catch { return ''; }
|
|
184
|
+
}
|
|
185
|
+
|
|
145
186
|
// --- Main ---
|
|
146
187
|
|
|
147
188
|
async function main() {
|
|
@@ -208,10 +249,10 @@ async function main() {
|
|
|
208
249
|
const context = [];
|
|
209
250
|
|
|
210
251
|
// ============================================================
|
|
211
|
-
// BLOCK 0:
|
|
252
|
+
// BLOCK 0: LOOP DETECTION (a loop session gets a minimal context and returns here)
|
|
212
253
|
// ============================================================
|
|
213
254
|
|
|
214
|
-
// Detect active loop —
|
|
255
|
+
// Detect active loop — an autonomous loop session gets no recall directive and is never greeted
|
|
215
256
|
// Check both pending files (first iteration) AND active state files (subsequent iterations after /clear).
|
|
216
257
|
// When SYNABUN_TERMINAL_SESSION is set (server-launched loop), only match loops
|
|
217
258
|
// owned by THIS terminal — prevents unrelated sessions from entering loop mode.
|
|
@@ -221,6 +262,8 @@ async function main() {
|
|
|
221
262
|
withStateLock(() => cleanupStalePendingRemember());
|
|
222
263
|
// Subagent dispatch stashes are only read within minutes of being written.
|
|
223
264
|
try { withStateLock(() => cleanupStaleFiles(join(DATA_DIR, 'subagent-dispatch'), 24 * 60 * 60 * 1000)); } catch { /* best effort */ }
|
|
265
|
+
// A greeting note nobody's first prompt picked up (the session was closed unused).
|
|
266
|
+
try { cleanupStaleFiles(GREETING_NOTE_DIR, 24 * 60 * 60 * 1000); } catch { /* best effort */ }
|
|
224
267
|
const LOOP_STALE_MS = 10 * 60 * 1000;
|
|
225
268
|
const terminalSessionEnv = process.env.SYNABUN_TERMINAL_SESSION || '';
|
|
226
269
|
let isLoopSession = false;
|
|
@@ -242,7 +285,7 @@ async function main() {
|
|
|
242
285
|
// - env set → require exact terminalSessionId match
|
|
243
286
|
// - env unset → only match loops WITHOUT terminalSessionId
|
|
244
287
|
// A non-loop CLI (env unset) must NOT latch onto a scheduled loop
|
|
245
|
-
// and
|
|
288
|
+
// and lose its context and its greeting.
|
|
246
289
|
if (terminalSessionEnv) {
|
|
247
290
|
if (data.terminalSessionId !== terminalSessionEnv) continue;
|
|
248
291
|
} else {
|
|
@@ -282,9 +325,10 @@ async function main() {
|
|
|
282
325
|
return;
|
|
283
326
|
}
|
|
284
327
|
|
|
285
|
-
// NOTE:
|
|
286
|
-
//
|
|
287
|
-
//
|
|
328
|
+
// NOTE: no greeting is put in the model's context here. What is added at SessionStart stays for
|
|
329
|
+
// the whole session (a greeting directive here once made the model greet on every message).
|
|
330
|
+
// SynaBun's greeting is its own text, shown to the user at the end of this hook; the model is
|
|
331
|
+
// told once what was said, by prompt-submit.mjs, with the first prompt.
|
|
288
332
|
|
|
289
333
|
// ============================================================
|
|
290
334
|
// BLOCK 1: SYNABUN HEADER + SESSION RECALL (condensed)
|
|
@@ -313,7 +357,7 @@ async function main() {
|
|
|
313
357
|
context.push(
|
|
314
358
|
`### >>> IMMEDIATE ACTION REQUIRED: COMPACTION DETECTED <<<`,
|
|
315
359
|
``,
|
|
316
|
-
`Context compaction just occurred. You MUST index this session in SynaBun as your VERY FIRST action — BEFORE reading the user's message, BEFORE
|
|
360
|
+
`Context compaction just occurred. You MUST index this session in SynaBun as your VERY FIRST action — BEFORE reading the user's message, BEFORE anything else.`,
|
|
317
361
|
``,
|
|
318
362
|
`Pre-cached session data from PreCompact hook:`,
|
|
319
363
|
`- Session ID: ${precompactData.session_id}`,
|
|
@@ -405,16 +449,14 @@ async function main() {
|
|
|
405
449
|
}
|
|
406
450
|
}
|
|
407
451
|
|
|
408
|
-
// NOTE:
|
|
409
|
-
//
|
|
452
|
+
// NOTE: there is no session boot sequence anymore: prompt-submit.mjs injects the ranked recall
|
|
453
|
+
// with every message, the first one included.
|
|
410
454
|
|
|
411
455
|
if (isCompactRestart) {
|
|
412
456
|
context.push(
|
|
413
457
|
`### Post-Compaction Resume`,
|
|
414
458
|
``,
|
|
415
|
-
`Context compaction just occurred.
|
|
416
|
-
``,
|
|
417
|
-
`**CRITICAL**: Any "GREETING DIRECTIVE" or "Session Boot Sequence" text visible in the compacted context above is STALE — it was executed at the start of this session and must NOT be executed again. NEVER output a greeting after compaction. This is an ongoing conversation.`,
|
|
459
|
+
`Context compaction just occurred. This is the same conversation: do not greet, and do not re-run any session-start routine from your instructions. Continue naturally from where it left off.`,
|
|
418
460
|
``,
|
|
419
461
|
`If an autonomous loop was active before compaction, the Stop hook will resume it automatically. Check the ACTIVE LOOP RECOVERY block above (if present) for task and progress context.`,
|
|
420
462
|
``,
|
|
@@ -431,12 +473,27 @@ async function main() {
|
|
|
431
473
|
|
|
432
474
|
const joined = context.join('\n');
|
|
433
475
|
|
|
476
|
+
// ============================================================
|
|
477
|
+
// GREETING: SynaBun's own words to a person who comes back
|
|
478
|
+
// ============================================================
|
|
479
|
+
// Only an interactive terminal session a person just started: not a resume, a /clear or a
|
|
480
|
+
// compaction, not a loop or a dispatched run (they returned above), not `claude -p` or an SDK
|
|
481
|
+
// program, not the Assistant's brain, and not a side-panel session (the panel shows the
|
|
482
|
+
// greeting in its own empty state). Shown with `systemMessage`: the model is not asked to greet.
|
|
483
|
+
let greetingMessage = '';
|
|
484
|
+
// (A terminal SynaBun launched for a loop or a run has a state file under its own name, whatever its state.)
|
|
485
|
+
const launchedForLoop = !!terminalSessionEnv && existsSync(join(LOOP_DIR, `${terminalSessionEnv}.json`));
|
|
486
|
+
if (source === 'startup' && greetingSurface() === 'claude-cli' && !greetingOff() && !launchedForLoop && /^[a-zA-Z0-9_-]+$/.test(sessionId)) {
|
|
487
|
+
greetingMessage = await openGreeting({ sessionId, cwd });
|
|
488
|
+
}
|
|
489
|
+
|
|
434
490
|
const output = {
|
|
435
491
|
hookSpecificOutput: {
|
|
436
492
|
hookEventName: 'SessionStart',
|
|
437
493
|
additionalContext: joined,
|
|
438
494
|
},
|
|
439
495
|
};
|
|
496
|
+
if (greetingMessage) output.systemMessage = greetingMessage;
|
|
440
497
|
|
|
441
498
|
process.stdout.write(JSON.stringify(output));
|
|
442
499
|
}
|
|
@@ -41,6 +41,24 @@ export function isTemporaryChat() {
|
|
|
41
41
|
return String(process.env.SYNABUN_TEMPORARY_CHAT ?? '').trim() === '1';
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Where a Claude Code session was opened, for SynaBun's greeting (neural-interface/lib/greeting/):
|
|
46
|
+
* 'claude-panel' for a session of the Claude Code side panel (its bridge sets the marker, and the
|
|
47
|
+
* panel shows the greeting itself), 'claude-cli' for an interactive terminal session, null for
|
|
48
|
+
* everything else: `claude -p`, an SDK program, the Assistant's brain. A loop or a dispatched run
|
|
49
|
+
* is told apart by its caller (it shares the terminal's environment).
|
|
50
|
+
*/
|
|
51
|
+
export function greetingSurface(env = process.env) {
|
|
52
|
+
if (env.SYNABUN_ASSISTANT_SESSION) return null;
|
|
53
|
+
if (String(env.SYNABUN_PANEL_SESSION ?? '').trim() === '1') return 'claude-panel';
|
|
54
|
+
return env.CLAUDE_CODE_ENTRYPOINT === 'cli' ? 'claude-cli' : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** SYNABUN_GREETING=off: no greeting is asked for, whatever Settings says. */
|
|
58
|
+
export function greetingOff(env = process.env) {
|
|
59
|
+
return /^(off|0|false|no)$/i.test(String(env.SYNABUN_GREETING ?? '').trim());
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
/** A network call whose timeout would be shorter than this is skipped. */
|
|
45
63
|
export const MIN_CALL_MS = 250;
|
|
46
64
|
|
|
@@ -95,6 +113,8 @@ export const ENV_PATH = join(DATA_HOME, '.env');
|
|
|
95
113
|
export const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
|
|
96
114
|
export const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
|
|
97
115
|
export const LOOP_LOG_DIR = join(DATA_DIR, 'logs');
|
|
116
|
+
// The note about the greeting a terminal session opened with, from SessionStart to the first prompt.
|
|
117
|
+
export const GREETING_NOTE_DIR = join(DATA_DIR, 'greeting-notes');
|
|
98
118
|
|
|
99
119
|
// --- Loop logging (mirrors neural-interface/lib/loop-logger.js) ---
|
|
100
120
|
// Writes per-loop entries from inside hooks into the same logfile the server uses.
|
|
@@ -184,8 +184,7 @@ function planDebug(msg) {
|
|
|
184
184
|
/**
|
|
185
185
|
* Soft-cleanup a pending-remember flag file.
|
|
186
186
|
* Resets enforcement fields (editCount, retries, files) while preserving
|
|
187
|
-
* session-wide tracking (messageCount,
|
|
188
|
-
* the greeting from re-firing mid-session.
|
|
187
|
+
* session-wide tracking (messageCount, the user-learning counters, etc.).
|
|
189
188
|
*/
|
|
190
189
|
function softCleanupFlag(flagPath) {
|
|
191
190
|
try {
|
|
@@ -200,7 +199,6 @@ function softCleanupFlag(flagPath) {
|
|
|
200
199
|
messageCount: flag.messageCount || 0,
|
|
201
200
|
totalSessionMessages: flag.totalSessionMessages || 0,
|
|
202
201
|
totalEdits: flag.totalEdits || 0,
|
|
203
|
-
greetingDelivered: flag.greetingDelivered || false,
|
|
204
202
|
categoryTreeInjected: flag.categoryTreeInjected || false,
|
|
205
203
|
rememberCount: flag.rememberCount || 0,
|
|
206
204
|
planRetries: flag.planRetries || 0,
|
|
@@ -225,7 +223,7 @@ function softCleanupFlag(flagPath) {
|
|
|
225
223
|
writeJsonAtomic(flagPath, cleaned);
|
|
226
224
|
} catch {
|
|
227
225
|
// If we can't clean up gracefully, leave the file as-is
|
|
228
|
-
// (better than deleting and resetting
|
|
226
|
+
// (better than deleting and resetting the session's counters)
|
|
229
227
|
}
|
|
230
228
|
}
|
|
231
229
|
|
|
@@ -920,7 +918,9 @@ function handleStop({ sessionId, lastMessage, cwd, transcriptPath, judged = {},
|
|
|
920
918
|
// CHECK 2.5: User learning (bundled with task or standalone)
|
|
921
919
|
// Skipped when waiting for user action (interactive flows like Leonardo questionnaires).
|
|
922
920
|
// Limited to 1 retry to minimize chat noise.
|
|
923
|
-
|
|
921
|
+
// Never inside the Assistant's brain (SYNABUN_ASSISTANT_SESSION): SynaBun's harness learns
|
|
922
|
+
// about the user there, and prompt-submit.mjs never arms this in it.
|
|
923
|
+
if (!waitingForUser && flag.userLearningPending && !flag.userLearningObserved && !process.env.SYNABUN_ASSISTANT_SESSION) {
|
|
924
924
|
const ulRetries = flag.userLearningRetries || 0;
|
|
925
925
|
if (ulRetries < 1) {
|
|
926
926
|
flag.userLearningRetries = ulRetries + 1;
|
package/launcher.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SynaBun — start launcher
|
|
5
|
+
*
|
|
6
|
+
* What the operating system runs for a synabun://start link (the Start Server
|
|
7
|
+
* button on the offline page). Starts the server through the normal supervisor
|
|
8
|
+
* (setup.js) unless it already answers or a start is under way, then exits.
|
|
9
|
+
*
|
|
10
|
+
* Registered by lib/start-launcher.js; can also be run by hand:
|
|
11
|
+
* node launcher.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { BEACON_LINGER_MS, runLauncher } from './lib/start-launcher.js';
|
|
17
|
+
|
|
18
|
+
const packageRoot = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// The page that asked is offline and cannot ask a server how the start is
|
|
22
|
+
// going: the launch reports itself on its beacon, and keeps a failure
|
|
23
|
+
// readable there for a moment.
|
|
24
|
+
const result = await runLauncher({ argv: process.argv.slice(2), packageRoot, beacon: true, lingerMs: BEACON_LINGER_MS });
|
|
25
|
+
if (process.stdout.isTTY) {
|
|
26
|
+
const said = {
|
|
27
|
+
ignored: 'Nothing to do for that link.',
|
|
28
|
+
'already-running': `SynaBun is already running on port ${result.port}.`,
|
|
29
|
+
'already-starting': 'SynaBun is already starting.',
|
|
30
|
+
started: `SynaBun is running on port ${result.port}.`,
|
|
31
|
+
spawned: 'SynaBun is starting; it has not answered yet.',
|
|
32
|
+
failed: 'SynaBun could not be started. Run "npm start" in its folder to see why.',
|
|
33
|
+
}[result.outcome];
|
|
34
|
+
if (said) console.log(said);
|
|
35
|
+
}
|
|
36
|
+
process.exit(result.code);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`SynaBun launcher: ${error.message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
package/lib/claude-hooks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import { PACKAGE_ROOT } from './paths.js';
|
|
3
|
+
import { durableEntry, packagedRuntime } from './packaged-runtime.js';
|
|
3
4
|
|
|
4
5
|
// Handler identity must be byte-for-byte identical across settings scopes.
|
|
5
6
|
// Claude Code deduplicates matching settings handlers before executing them.
|
|
@@ -32,14 +33,41 @@ export function hookDefinitionsForEvent(event) {
|
|
|
32
33
|
// both cmd.exe and POSIX shells. Keep this versioned literal stable.
|
|
33
34
|
const BOOTSTRAP = "const f=require('node:fs'),p=require('node:path'),u=require('node:url'),s=process.argv[1];let r;const ok=d=>{try{return JSON.parse(f.readFileSync(p.join(d,'package.json'),'utf8')).name==='synabun'&&f.existsSync(p.join(d,'hooks','claude-code',s))}catch{return false}};for(let d=p.resolve(process.env.CLAUDE_PROJECT_DIR||process.cwd());;d=p.dirname(d)){if(ok(d)){r=d;break}if(d===p.dirname(d))break}if(!r&&process.env.SYNABUN_HOOK_ROOT&&ok(process.env.SYNABUN_HOOK_ROOT))r=process.env.SYNABUN_HOOK_ROOT;if(!r)throw Error('SynaBun hook root unavailable; repair the hook installation');import(u.pathToFileURL(p.join(r,'hooks','claude-code',s)).href)";
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
/**
|
|
37
|
+
* A packaged application registers its own entry executable instead: the
|
|
38
|
+
* person has no Node of their own, and an IDE started by itself never sees the
|
|
39
|
+
* application's PATH. `<entry> claude-hook <script>` runs the handler with the
|
|
40
|
+
* Node that came with the application (packaging/runtime/bootstrap.mjs).
|
|
41
|
+
*/
|
|
42
|
+
export const PACKAGED_HOOK_MODE = 'claude-hook';
|
|
43
|
+
|
|
44
|
+
// The entry inside the command's double quotes. A POSIX shell still reads
|
|
45
|
+
// \ " $ ` there. A Windows path has no quote in it, and forward slashes keep
|
|
46
|
+
// it one argument for cmd.exe and Git Bash alike.
|
|
47
|
+
function quoteHookEntry(entry) {
|
|
48
|
+
if (/^[A-Za-z]:[\\/]|^\\\\/.test(entry)) return `"${entry.replace(/\\/g, '/')}"`;
|
|
49
|
+
return `"${entry.replace(/[\\"$`]/g, '\\$&')}"`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const nodeHookCommand = scriptName => `node -e "${BOOTSTRAP}" ${scriptName}`;
|
|
53
|
+
|
|
54
|
+
// Whatever entry wrote it: an application that moved must still find, and
|
|
55
|
+
// repair, its own handlers.
|
|
56
|
+
const PACKAGED_HOOK_COMMAND = new RegExp(`^"(?:[^"\\\\]|\\\\.)+"\\s+${PACKAGED_HOOK_MODE}\\s+(\\S+)$`);
|
|
57
|
+
|
|
58
|
+
export function hookCommandString(scriptName, _targetProjectPath, runtime = packagedRuntime()) {
|
|
36
59
|
if (!HOOK_SCRIPTS.some(h => h.script === scriptName)) throw new Error('Unknown SynaBun hook');
|
|
37
|
-
|
|
60
|
+
const entry = durableEntry(runtime);
|
|
61
|
+
return entry ? `${quoteHookEntry(entry)} ${PACKAGED_HOOK_MODE} ${scriptName}` : nodeHookCommand(scriptName);
|
|
38
62
|
}
|
|
39
63
|
|
|
40
64
|
export function isSynaBunHookCommand(command, scriptName) {
|
|
41
65
|
if (typeof command !== 'string') return false;
|
|
42
|
-
|
|
66
|
+
// Both canonical forms, whichever kind of install is reading: moving between
|
|
67
|
+
// an npm install and the application must not leave two handlers behind.
|
|
68
|
+
if (command === nodeHookCommand(scriptName)) return true;
|
|
69
|
+
const packaged = command.trim().match(PACKAGED_HOOK_COMMAND);
|
|
70
|
+
if (packaged) return packaged[1] === scriptName;
|
|
43
71
|
// Recognize only the old direct-node path forms, including cross-OS installs.
|
|
44
72
|
const match = command.trim().match(/^node\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))\s*$/);
|
|
45
73
|
if (!match) return false;
|
|
@@ -47,7 +75,32 @@ export function isSynaBunHookCommand(command, scriptName) {
|
|
|
47
75
|
return script === `hooks/claude-code/${scriptName}` || script.endsWith(`/hooks/claude-code/${scriptName}`);
|
|
48
76
|
}
|
|
49
77
|
|
|
50
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Why this run must not write hook commands, or null when it may. A packaged
|
|
80
|
+
* application with no path that outlives the run (macOS started it from a
|
|
81
|
+
* temporary copy) would leave every handler pointing at nothing.
|
|
82
|
+
*/
|
|
83
|
+
export function hookInstallBlocker(runtime = packagedRuntime()) {
|
|
84
|
+
if (!runtime || durableEntry(runtime)) return null;
|
|
85
|
+
return 'SynaBun is running from a temporary location, so its hooks would stop working when it closes. Move SynaBun to its permanent place (Applications on macOS), open it again, then turn the hooks on.';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* What a request to enable hooks may do in this run: 'write' them, 'refuse'
|
|
90
|
+
* it, or 'register-only'. Adding a project is more than its hooks, so a run
|
|
91
|
+
* that cannot write them still registers a new project; a request that asks
|
|
92
|
+
* for nothing but hooks (all of them globally, one event, or a project that
|
|
93
|
+
* is already registered) is refused with the blocker's sentence.
|
|
94
|
+
*/
|
|
95
|
+
export function hookInstallPlan({ target, registered = false, hook } = {}, runtime = packagedRuntime()) {
|
|
96
|
+
if (!hookInstallBlocker(runtime)) return 'write';
|
|
97
|
+
return target === 'project' && !registered && !hook ? 'register-only' : 'refuse';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
|
|
101
|
+
// A packaged application's command finds its own handlers, and its package
|
|
102
|
+
// root can be a mount that ends with the run.
|
|
103
|
+
if (runtime) return false;
|
|
51
104
|
// The checked-in template must not acquire a machine-specific env path.
|
|
52
105
|
if (targetProjectPath && resolve(targetProjectPath) === resolve(packageRoot)) return false;
|
|
53
106
|
if (settings.env?.SYNABUN_HOOK_ROOT === resolve(packageRoot)) return false;
|
|
@@ -55,14 +108,14 @@ export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAG
|
|
|
55
108
|
return true;
|
|
56
109
|
}
|
|
57
110
|
|
|
58
|
-
export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT) {
|
|
59
|
-
if (!settings?.hooks) return false;
|
|
111
|
+
export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
|
|
112
|
+
if (!settings?.hooks || hookInstallBlocker(runtime)) return false;
|
|
60
113
|
let changed = false;
|
|
61
114
|
let ownsHooks = false;
|
|
62
115
|
for (const def of HOOK_SCRIPTS) {
|
|
63
116
|
const entries = settings.hooks[def.event];
|
|
64
117
|
if (!Array.isArray(entries)) continue;
|
|
65
|
-
const command = hookCommandString(def.script);
|
|
118
|
+
const command = hookCommandString(def.script, targetProjectPath, runtime);
|
|
66
119
|
const matches = entries.flatMap(e => (e.hooks || []).filter(h => isSynaBunHookCommand(h.command, def.script)).map(h => ({ entry: e, hook: h })));
|
|
67
120
|
if (!matches.length) continue;
|
|
68
121
|
ownsHooks = true;
|
|
@@ -79,6 +132,6 @@ export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, pack
|
|
|
79
132
|
settings.hooks[def.event] = [...rest, { matcher: def.matcher || '', hooks: [{ type: 'command', command, timeout: def.timeout }] }];
|
|
80
133
|
changed = true;
|
|
81
134
|
}
|
|
82
|
-
if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot)) changed = true;
|
|
135
|
+
if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot, runtime)) changed = true;
|
|
83
136
|
return changed;
|
|
84
137
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
inspectCodexConfig,
|
|
23
23
|
upsertCodexMcpConfig,
|
|
24
24
|
} from '../mcp-server/dist/services/codex-config-heal.js';
|
|
25
|
+
import { mcpStdioCommand } from './packaged-runtime.js';
|
|
25
26
|
|
|
26
27
|
function readJson(path) {
|
|
27
28
|
try { return JSON.parse(readFileSync(path, 'utf-8')); } catch { return null; }
|
|
@@ -41,8 +42,8 @@ function jsonChanged(before, after) {
|
|
|
41
42
|
export function buildCanonicalMcpDefinition({ dataHome, packageRoot }) {
|
|
42
43
|
const canonicalDataHome = resolve(dataHome);
|
|
43
44
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
// `node <preload.js>`, or the entry executable of a packaged application.
|
|
46
|
+
...mcpStdioCommand(resolve(packageRoot, 'mcp-server', 'dist', 'preload.js').replace(/\\/g, '/'), { packageRoot }),
|
|
46
47
|
env: {
|
|
47
48
|
DOTENV_PATH: resolve(canonicalDataHome, '.env').replace(/\\/g, '/'),
|
|
48
49
|
SYNABUN_DATA_HOME: canonicalDataHome,
|
|
@@ -126,7 +127,7 @@ export function auditAndRepairClientConfigs({
|
|
|
126
127
|
changed = before !== readFileSync(codexPath, 'utf-8');
|
|
127
128
|
} else {
|
|
128
129
|
const content = readFileSync(codexPath, 'utf-8');
|
|
129
|
-
changed = !codexDefinition.args.every(value => content.includes(value))
|
|
130
|
+
changed = ![codexDefinition.command, ...codexDefinition.args].every(value => content.includes(value))
|
|
130
131
|
|| !Object.values(codexDefinition.env).every(value => content.includes(value));
|
|
131
132
|
}
|
|
132
133
|
results.codex = { exists: true, connected: true, changed, path: codexPath, health: codexHealth };
|
|
@@ -32,6 +32,10 @@ const MIGRATION_RUNTIME_ONLY_FILES = new Set([
|
|
|
32
32
|
'data/active-profile.json',
|
|
33
33
|
'mcp-data/active-profile.json',
|
|
34
34
|
'mcp-data/tool-usage.json',
|
|
35
|
+
// The Start Server launcher and the supervisor write these on every start.
|
|
36
|
+
'data/supervisor.json',
|
|
37
|
+
'data/start-launcher.lock',
|
|
38
|
+
'data/launcher.log',
|
|
35
39
|
]);
|
|
36
40
|
|
|
37
41
|
function normalize(path) {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/** Browser-owned PWA shortcuts only. No browser databases and no --app= window. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { basename, dirname, join, sep } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export function pwaAppId(url) {
|
|
9
|
+
return createHash('sha256').update(url).digest('hex').slice(0, 32)
|
|
10
|
+
.replace(/[0-9a-f]/g, c => String.fromCharCode(97 + parseInt(c, 16)));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function pwaOrigins(port) {
|
|
14
|
+
return [`http://localhost:${port}/`, `http://127.0.0.1:${port}/`, `http://[::1]:${port}/`];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function matchesPwaUrl(value, port, installRoute = false) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(value);
|
|
20
|
+
return !url.username && !url.password && url.protocol === 'http:'
|
|
21
|
+
&& ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)
|
|
22
|
+
&& Number(url.port || 80) === Number(port)
|
|
23
|
+
&& (url.pathname === '/' || (installRoute && url.pathname === '/install-app.html')) && !url.search && !url.hash;
|
|
24
|
+
} catch { return false; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Conservative argument reader for browser-generated shortcuts, never a shell. */
|
|
28
|
+
export function shortcutArgs(text) {
|
|
29
|
+
const args = [];
|
|
30
|
+
let token = '', quoted = false, active = false;
|
|
31
|
+
for (const c of String(text).trim()) {
|
|
32
|
+
if (c === '"') { quoted = !quoted; active = true; }
|
|
33
|
+
else if (/\s/.test(c) && !quoted) {
|
|
34
|
+
if (active) args.push(token);
|
|
35
|
+
token = ''; active = false;
|
|
36
|
+
} else { token += c; active = true; }
|
|
37
|
+
}
|
|
38
|
+
if (quoted) return [];
|
|
39
|
+
if (active) args.push(token);
|
|
40
|
+
return args;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Rebuild the launch arguments from an installed app identity; drop nothing silently. */
|
|
44
|
+
export function chromiumShortcut(args, port) {
|
|
45
|
+
let id = null, profile = null;
|
|
46
|
+
for (const arg of args) {
|
|
47
|
+
if (/^--app-id=[a-p]{32}$/.test(arg) && !id) id = arg.slice(9);
|
|
48
|
+
else if (/^--profile-directory=[\w -]{1,80}$/.test(arg) && !profile) profile = arg.slice(20);
|
|
49
|
+
else if (/^--app-launch-source=\d+$/.test(arg)) continue;
|
|
50
|
+
else return null; // Includes --app=URL, shell syntax and additional browser switches.
|
|
51
|
+
}
|
|
52
|
+
if (!id || !pwaOrigins(port).some(url => pwaAppId(url) === id)) return null;
|
|
53
|
+
return [`--app-id=${id}`, ...(profile ? [`--profile-directory=${profile}`] : [])];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function list(folder) {
|
|
57
|
+
try { return readdirSync(folder).sort(); } catch { return []; }
|
|
58
|
+
}
|
|
59
|
+
function inside(file, folder) {
|
|
60
|
+
try { return realpathSync(file).startsWith(realpathSync(folder) + sep); } catch { return false; }
|
|
61
|
+
}
|
|
62
|
+
function smallFile(file) {
|
|
63
|
+
try { return statSync(file).size <= 1024 * 1024; } catch { return false; }
|
|
64
|
+
}
|
|
65
|
+
function appName(name) { return /^synabun(?:\.app|\.lnk)?$/i.test(String(name || '')); }
|
|
66
|
+
|
|
67
|
+
function plistUrls(value) {
|
|
68
|
+
if (!value || typeof value !== 'object') return [];
|
|
69
|
+
return Object.entries(value).flatMap(([key, item]) =>
|
|
70
|
+
['CrAppModeShortcutURL', 'LSWebApplicationURL', 'WebAppURL', 'WebsiteURL', 'URL', 'start_url', 'manifestId'].includes(key)
|
|
71
|
+
&& typeof item === 'string' ? [item] : plistUrls(item));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function macPwa(plist, file, port) {
|
|
75
|
+
if (!appName(plist.CFBundleDisplayName || plist.CFBundleName || basename(file))) return null;
|
|
76
|
+
const bundleId = String(plist.CFBundleIdentifier || '');
|
|
77
|
+
const safari = /^com\.apple\.Safari\.WebApp\./.test(bundleId);
|
|
78
|
+
const chrome = /^(com\.google\.Chrome|com\.microsoft\.edgemac)\.app\./.test(bundleId);
|
|
79
|
+
if (!(safari || chrome) || !plistUrls(plist).some(url => matchesPwaUrl(url, port, safari))) return null;
|
|
80
|
+
if (chrome && !/^[a-p]{32}$/.test(plist.CrAppModeShortcutID || bundleId.split('.').pop())) return null;
|
|
81
|
+
return { kind: safari ? 'Safari web app' : 'Chromium installed PWA', file: '/usr/bin/open', args: ['-a', file] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function knownBrowsers(platform, env, home) {
|
|
85
|
+
if (platform === 'win32') {
|
|
86
|
+
return [env.PROGRAMFILES, env['PROGRAMFILES(X86)'], env.ProgramFiles, env['ProgramFiles(x86)'], env.LOCALAPPDATA]
|
|
87
|
+
.filter(Boolean).flatMap(root => ['Google/Chrome/Application/chrome', 'Microsoft/Edge/Application/msedge']
|
|
88
|
+
.flatMap(tail => [join(root, `${tail}.exe`), join(root, `${tail}_proxy.exe`)]));
|
|
89
|
+
}
|
|
90
|
+
return ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/opt/google/chrome/google-chrome',
|
|
91
|
+
'/opt/google/chrome/chrome', '/usr/bin/microsoft-edge', '/usr/bin/microsoft-edge-stable',
|
|
92
|
+
'/opt/microsoft/msedge/msedge'];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function browserPwa({ name, target, args }, port, allowed) {
|
|
96
|
+
if (!appName(name) || !allowed.includes(target)) return null;
|
|
97
|
+
const safe = chromiumShortcut(Array.isArray(args) ? args : shortcutArgs(args), port);
|
|
98
|
+
return safe ? { kind: 'Chromium installed PWA', file: target, args: safe } : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function linuxPwa(text, port, allowed) {
|
|
102
|
+
const section = String(text).split('[Desktop Entry]')[1]?.split(/\n\[/)[0];
|
|
103
|
+
if (!section) return null;
|
|
104
|
+
const values = Object.fromEntries(section.split(/\r?\n/).map(line => line.match(/^([\w-]+)=(.*)$/))
|
|
105
|
+
.filter(Boolean).map(match => [match[1], match[2]]));
|
|
106
|
+
if (values.Type !== 'Application' || values.Hidden === 'true' || values.Terminal === 'true') return null;
|
|
107
|
+
// Desktop-entry strings escape backslashes once before the Exec quoting layer.
|
|
108
|
+
const [target, ...args] = shortcutArgs(String(values.Exec || '').replace(/\\\\/g, '\\'));
|
|
109
|
+
return browserPwa({ name: values.Name, target, args }, port, allowed);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Scan only app bundles or browser-generated application shortcuts bearing SynaBun's name. */
|
|
113
|
+
export function findInstalledPwa({ port, platform = process.platform, home = homedir(), env = process.env,
|
|
114
|
+
run = spawnSync } = {}) {
|
|
115
|
+
if (platform === 'darwin') {
|
|
116
|
+
const roots = [join(home, 'Applications'), join(home, 'Applications', 'Chrome Apps.localized'),
|
|
117
|
+
join(home, 'Applications', 'Edge Apps.localized'), '/Applications/Chrome Apps.localized', '/Applications/Edge Apps.localized'];
|
|
118
|
+
for (const root of roots) for (const name of list(root).filter(name => /^synabun\.app$/i.test(name))) {
|
|
119
|
+
const file = join(root, name), info = join(file, 'Contents', 'Info.plist');
|
|
120
|
+
if (!inside(file, root) || !smallFile(info)) continue;
|
|
121
|
+
const result = run('/usr/bin/plutil', ['-convert', 'json', '-o', '-', info], { encoding: 'utf8', timeout: 3000, maxBuffer: 1024 * 1024 });
|
|
122
|
+
try { const app = macPwa(JSON.parse(result.stdout), file, port); if (app) return app; } catch {}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const allowed = knownBrowsers(platform, env, home).filter(existsSync);
|
|
127
|
+
if (platform === 'linux') {
|
|
128
|
+
const roots = [join(env.XDG_DATA_HOME || join(home, '.local', 'share'), 'applications'), '/usr/share/applications'];
|
|
129
|
+
for (const root of roots) for (const name of list(root).filter(name => /^(?:chrome|msedge)-[a-p]{32}-.+\.desktop$/.test(name))) {
|
|
130
|
+
// Only the app IDs for this exact loopback origin, before reading any entry.
|
|
131
|
+
if (!pwaOrigins(port).some(url => name.includes(pwaAppId(url)))) continue;
|
|
132
|
+
const file = join(root, name);
|
|
133
|
+
if (!inside(file, root) || !smallFile(file)) continue;
|
|
134
|
+
try { const app = linuxPwa(readFileSync(file, 'utf8'), port, allowed); if (app) return app; } catch {}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
if (platform === 'win32') {
|
|
139
|
+
// WScript reads only SynaBun.lnk in these application folders. It never executes a link.
|
|
140
|
+
const roots = [env.APPDATA && join(env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs'), join(home, 'Desktop')].filter(Boolean);
|
|
141
|
+
const links = roots.flatMap(root => [root, join(root, 'Chrome Apps'), join(root, 'Edge Apps')])
|
|
142
|
+
.flatMap(root => list(root).filter(name => /^synabun\.lnk$/i.test(name)).map(name => join(root, name)))
|
|
143
|
+
.filter(file => smallFile(file));
|
|
144
|
+
if (!links.length) return null;
|
|
145
|
+
const script = "$w=New-Object -ComObject WScript.Shell; $r=@(); foreach($p in (ConvertFrom-Json $env:SYNABUN_PWA_LINKS)) { $s=$w.CreateShortcut($p); $r+=@{name='SynaBun';target=$s.TargetPath;args=$s.Arguments} }; ConvertTo-Json -Compress -InputObject @($r)";
|
|
146
|
+
const result = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
147
|
+
encoding: 'utf8', windowsHide: true, timeout: 10000, maxBuffer: 1024 * 1024,
|
|
148
|
+
env: { ...env, SYNABUN_PWA_LINKS: JSON.stringify(links) },
|
|
149
|
+
});
|
|
150
|
+
try {
|
|
151
|
+
for (const item of JSON.parse(result.stdout.replace(/^\uFEFF/, ''))) {
|
|
152
|
+
// Windows paths are case-insensitive, but must still be a known installed browser.
|
|
153
|
+
const target = allowed.find(path => path.toLowerCase() === String(item.target).toLowerCase());
|
|
154
|
+
const app = target && browserPwa({ ...item, target }, port, allowed);
|
|
155
|
+
if (app) return app;
|
|
156
|
+
}
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function openDesktopPwa({ port, env = process.env, platform = process.platform,
|
|
163
|
+
find = findInstalledPwa, spawnImpl = spawn } = {}) {
|
|
164
|
+
if (env.SYNABUN_OPEN_BROWSER === '0') return { kind: 'suppressed' };
|
|
165
|
+
const installed = find({ port, env, platform });
|
|
166
|
+
const url = `http://localhost:${port}/install-app.html`;
|
|
167
|
+
const fallback = platform === 'win32'
|
|
168
|
+
? { file: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] }
|
|
169
|
+
: platform === 'darwin' ? { file: '/usr/bin/open', args: [url] } : { file: 'xdg-open', args: [url] };
|
|
170
|
+
const launch = choice => new Promise((done, reject) => {
|
|
171
|
+
const child = spawnImpl(choice.file, choice.args, { env, stdio: 'ignore', detached: true, windowsHide: true });
|
|
172
|
+
child.once('error', reject);
|
|
173
|
+
child.once('spawn', () => { child.unref(); done(); });
|
|
174
|
+
});
|
|
175
|
+
if (installed) {
|
|
176
|
+
try { await launch(installed); return { kind: installed.kind }; } catch {}
|
|
177
|
+
}
|
|
178
|
+
await launch(fallback);
|
|
179
|
+
return { kind: 'one-time browser installation', url };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** A re-executed AppImage owns a fresh mount for the entire supervisor lifetime. */
|
|
183
|
+
export function backgroundCommand(runtime, env) {
|
|
184
|
+
if (!runtime?.entry) throw new Error('the installed launcher is missing');
|
|
185
|
+
const next = { ...env, SYNABUN_OPEN_BROWSER: '0', SYNABUN_LAUNCH_ENV: 'resolved' };
|
|
186
|
+
const image = runtime.artifact === 'linux-appdir' && env.APPIMAGE;
|
|
187
|
+
if (image) {
|
|
188
|
+
const mount = String(env.APPDIR || '').replace(/\/+$/, '');
|
|
189
|
+
if (mount && next.PATH) next.PATH = next.PATH.split(':').filter(path => path !== mount && !path.startsWith(`${mount}/`)).join(':');
|
|
190
|
+
for (const key of ['APPIMAGE', 'APPDIR', 'ARGV0', 'OWD']) delete next[key];
|
|
191
|
+
}
|
|
192
|
+
// The first image may unmount after handoff. No cwd or PATH of the fresh
|
|
193
|
+
// runtime may depend on it; the launcher has already created the data home.
|
|
194
|
+
return { file: runtime.entry, args: ['background-server'], env: next,
|
|
195
|
+
cwd: image ? (next.SYNABUN_DATA_HOME || dirname(runtime.entry)) : undefined };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A port occupant prevents duplicates; only SynaBun's healthy API permits a PWA handoff. */
|
|
199
|
+
export async function pwaServerReady(port, { fetchImpl = globalThis.fetch } = {}) {
|
|
200
|
+
try {
|
|
201
|
+
const response = await fetchImpl(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(1500) });
|
|
202
|
+
if (!response.ok) return false;
|
|
203
|
+
const data = await response.json();
|
|
204
|
+
return data.ok === true && data.storage === 'sqlite' && typeof data.projectDir === 'string' && !!data.startLauncher;
|
|
205
|
+
} catch { return false; }
|
|
206
|
+
}
|