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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2026.10.0",
|
|
3
|
+
"baseUrl": "https://github.com/cloudflare/cloudflared/releases/download/2026.10.0/",
|
|
4
|
+
"release": "https://github.com/cloudflare/cloudflared/releases/tag/2026.10.0",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"origin": "Cloudflare's own release files, the stable release of 2026-10-05. `sha256` is the digest GitHub publishes for the release asset (the `digest` of the asset in the releases API). `binary.sha256` is the checksum in the release's own notes (SHA256 Checksums): for the macOS archives that is the checksum of the cloudflared inside, for the other files it is the file itself. Each file was downloaded on 2026-10-13 and both values, and the sizes, were computed from it and found equal to what is published.",
|
|
7
|
+
"platforms": {
|
|
8
|
+
"darwin-arm64": {
|
|
9
|
+
"file": "cloudflared-darwin-arm64.tgz",
|
|
10
|
+
"sha256": "a2f79ff7b9420aa537d74af239f376da170bbabeb529aec416002adac6a72e70",
|
|
11
|
+
"size": 19809074,
|
|
12
|
+
"archive": "tgz",
|
|
13
|
+
"member": "cloudflared",
|
|
14
|
+
"binary": {
|
|
15
|
+
"sha256": "72edfd3eea463aef4d5cb89e2e209cecb048cc756c2b01915de2e0ad7cb39830",
|
|
16
|
+
"size": 39364384,
|
|
17
|
+
"format": "macho"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"darwin-x64": {
|
|
21
|
+
"file": "cloudflared-darwin-amd64.tgz",
|
|
22
|
+
"sha256": "903845b81828c8cb3c5d13d816a2de71c06a3da5785469df8eb0e1b736d92f9f",
|
|
23
|
+
"size": 21741581,
|
|
24
|
+
"archive": "tgz",
|
|
25
|
+
"member": "cloudflared",
|
|
26
|
+
"binary": {
|
|
27
|
+
"sha256": "0560c9ab7281ac3f746055323623ed23bc0405b6dab9400474020cba33a978da",
|
|
28
|
+
"size": 42328240,
|
|
29
|
+
"format": "macho"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"linux-x64": {
|
|
33
|
+
"file": "cloudflared-linux-amd64",
|
|
34
|
+
"sha256": "d33ff2d14475178d2012c2c56beba87389ac5ded27649519f198a7d3134a99db",
|
|
35
|
+
"size": 40129756,
|
|
36
|
+
"binary": {
|
|
37
|
+
"sha256": "d33ff2d14475178d2012c2c56beba87389ac5ded27649519f198a7d3134a99db",
|
|
38
|
+
"size": 40129756,
|
|
39
|
+
"format": "elf"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"linux-arm64": {
|
|
43
|
+
"file": "cloudflared-linux-arm64",
|
|
44
|
+
"sha256": "e6422b9d4f72d3194bc5a38676f13667c06666523217b842a877d72a80b5ac08",
|
|
45
|
+
"size": 37687584,
|
|
46
|
+
"binary": {
|
|
47
|
+
"sha256": "e6422b9d4f72d3194bc5a38676f13667c06666523217b842a877d72a80b5ac08",
|
|
48
|
+
"size": 37687584,
|
|
49
|
+
"format": "elf"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"win32-x64": {
|
|
53
|
+
"file": "cloudflared-windows-amd64.exe",
|
|
54
|
+
"sha256": "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
|
|
55
|
+
"size": 55365048,
|
|
56
|
+
"binary": {
|
|
57
|
+
"sha256": "86aee4017b26625cee8484c113558f48effa4cd47f7aa05fcf425604e5d2b23c",
|
|
58
|
+
"size": 55365048,
|
|
59
|
+
"format": "pe"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — the public address (a Cloudflare quick tunnel)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// `cloudflared tunnel --url http://localhost:<port>`: no Cloudflare account, an
|
|
6
|
+
// address under trycloudflare.com for as long as the process runs. One tunnel
|
|
7
|
+
// at a time. What server.js keeps of it are three thin routes (/api/tunnel/…)
|
|
8
|
+
// and the address itself, which it needs for its Host and remote rules: it
|
|
9
|
+
// gets every change through `onUrl`, the moment cloudflared prints the address.
|
|
10
|
+
//
|
|
11
|
+
// start() answers at once. Behind it: the copy of cloudflared to run
|
|
12
|
+
// (manager.js: a download on a machine that has none), then the
|
|
13
|
+
// process, then its address. A second start while one is on its
|
|
14
|
+
// way, or running, changes nothing.
|
|
15
|
+
// stop() ends the process, and whatever a start was still waiting for.
|
|
16
|
+
// status() what is happening, for the page: idle, downloading (with
|
|
17
|
+
// progress), starting, running (the address, which copy runs and
|
|
18
|
+
// its version), failed (a code and a sentence; a start tries again).
|
|
19
|
+
// shutdown() stop(), then waits (a bounded time) until cloudflared has
|
|
20
|
+
// really exited. killNow(): at the server's exit, kill it now.
|
|
21
|
+
//
|
|
22
|
+
// A copy of SynaBun's own is started with --no-autoupdate: it is a pinned file,
|
|
23
|
+
// and cloudflared would otherwise replace itself where it stands, inside an
|
|
24
|
+
// installed application too. The user's own cloudflared is started as before.
|
|
25
|
+
// Whatever copy it is, the manager checks it once more right before the start
|
|
26
|
+
// (confirm: the very file its check read, by its real path), and that path is
|
|
27
|
+
// what is started, at once.
|
|
28
|
+
//
|
|
29
|
+
// An ended process is not forgotten: it is asked to end (SIGTERM), killed when
|
|
30
|
+
// it is still there a moment later (SIGKILL), and kept until it has exited.
|
|
31
|
+
|
|
32
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
33
|
+
|
|
34
|
+
const exited = (proc) => !proc.pid || proc.exitCode !== null || proc.signalCode !== null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* End a child process for sure: SIGTERM now, SIGKILL when it is still there `killAfterMs` later. Resolves once it
|
|
38
|
+
* has exited (at once for one that is gone, or never started). How long to wait for that is the caller's choice.
|
|
39
|
+
*/
|
|
40
|
+
export function endChild(proc, { killAfterMs = 1000 } = {}) {
|
|
41
|
+
return new Promise((done) => {
|
|
42
|
+
if (!proc || exited(proc)) { done(); return; }
|
|
43
|
+
let timer = null;
|
|
44
|
+
proc.once('exit', () => { clearTimeout(timer); done(); });
|
|
45
|
+
try { proc.kill('SIGTERM'); } catch { /* gone already */ }
|
|
46
|
+
timer = setTimeout(() => {
|
|
47
|
+
if (!exited(proc)) { try { proc.kill('SIGKILL'); } catch { /* gone already */ } }
|
|
48
|
+
}, killAfterMs);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The address of a quick tunnel, as cloudflared prints it. */
|
|
53
|
+
const TUNNEL_URL = /https:\/\/([a-z0-9-]+)\.trycloudflare\.com/g;
|
|
54
|
+
/** Names under trycloudflare.com that are Cloudflare's own, never a tunnel's (they appear in its error lines). */
|
|
55
|
+
const NOT_A_TUNNEL = new Set(['api', 'www']);
|
|
56
|
+
const OUTPUT_KEPT = 4096;
|
|
57
|
+
|
|
58
|
+
/** The first quick-tunnel address in a piece of cloudflared's output, or null. */
|
|
59
|
+
export function tunnelUrlIn(text) {
|
|
60
|
+
TUNNEL_URL.lastIndex = 0;
|
|
61
|
+
for (let match = TUNNEL_URL.exec(text); match; match = TUNNEL_URL.exec(text)) {
|
|
62
|
+
if (!NOT_A_TUNNEL.has(match[1])) return match[0];
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The arguments cloudflared is started with for a copy from `source` (manager.js). */
|
|
68
|
+
export function tunnelArguments(port, source) {
|
|
69
|
+
return ['tunnel', ...(source === 'system' ? [] : ['--no-autoupdate']), '--url', `http://localhost:${port}`];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What cloudflared said last that reads like a reason: its last error line, without the timestamp. */
|
|
73
|
+
function lastReason(output) {
|
|
74
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
75
|
+
const said = [...lines].reverse().find((line) => /\b(?:ERR|error|failed)\b/i.test(line)) || '';
|
|
76
|
+
return said.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z?\s+/, '').replace(/^(?:ERR|INF|WRN)\s+/, '').slice(0, 240);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {object} o
|
|
81
|
+
* @param {{ ensure: Function, inspect: Function, status: Function, cancel: Function, confirm?: Function, killNow?: Function }} o.cloudflared manager.js
|
|
82
|
+
* @param {number | string} o.port the port of this server
|
|
83
|
+
* @param {(url: string | null) => void} [o.onUrl] every change of the address: set when it is known, null when it is gone
|
|
84
|
+
* @param {typeof nodeSpawn} [o.spawn]
|
|
85
|
+
* @param {number} [o.urlTimeoutMs] how long a started cloudflared may take to print its address
|
|
86
|
+
* @param {number} [o.killAfterMs] how long an ended cloudflared has to exit before it is killed
|
|
87
|
+
* @param {(line: string) => void} [o.log]
|
|
88
|
+
*/
|
|
89
|
+
export function createQuickTunnel({ cloudflared, port, onUrl = () => {}, spawn = nodeSpawn, urlTimeoutMs = 90_000, killAfterMs = 1000, log = () => {} }) {
|
|
90
|
+
let phase = 'idle'; // idle | preparing | starting | running | failed
|
|
91
|
+
let child = null;
|
|
92
|
+
let url = null;
|
|
93
|
+
let copy = null; // { source, version } of the cloudflared that runs
|
|
94
|
+
let error = null; // { code, message, reason } of the start that failed, until the next one (`reason`: cloudflared's own words, when it gave any)
|
|
95
|
+
let attempt = 0; // a start that is not the current attempt any more does nothing
|
|
96
|
+
let preparing = null; // the AbortController of the start that is getting its copy ready
|
|
97
|
+
const ending = new Map(); // a process that was asked to end → its endChild(): kept until it has really exited
|
|
98
|
+
|
|
99
|
+
/** Ask a process to end, kill it when it does not, and keep it until it has exited. */
|
|
100
|
+
function end(proc) {
|
|
101
|
+
if (!proc) return Promise.resolve();
|
|
102
|
+
if (!ending.has(proc)) ending.set(proc, endChild(proc, { killAfterMs }).then(() => { ending.delete(proc); }));
|
|
103
|
+
return ending.get(proc);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const setUrl = (value) => {
|
|
107
|
+
if (url === value) return;
|
|
108
|
+
url = value;
|
|
109
|
+
try { onUrl(value); } catch { /* the address is still ours to report */ }
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function fail(code, message, reason = null) {
|
|
113
|
+
phase = 'failed';
|
|
114
|
+
error = { code, message, reason };
|
|
115
|
+
copy = null;
|
|
116
|
+
setUrl(null);
|
|
117
|
+
log(` Tunnel: ${code === 'ended' ? 'stopped' : 'not started'} (${code}): ${message}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function launch(found, mine) {
|
|
121
|
+
// Checked once more as it is about to run (the manager's confirm: the very file its check read, still where
|
|
122
|
+
// nobody else can change it), and started at once by the real path that was checked: nothing runs in between.
|
|
123
|
+
let path = found.path;
|
|
124
|
+
try {
|
|
125
|
+
if (typeof cloudflared.confirm === 'function') path = cloudflared.confirm(found);
|
|
126
|
+
} catch (failure) {
|
|
127
|
+
return fail(typeof failure?.code === 'string' ? failure.code : 'launch', failure?.message || String(failure));
|
|
128
|
+
}
|
|
129
|
+
let proc;
|
|
130
|
+
try {
|
|
131
|
+
proc = spawn(path, tunnelArguments(port, found.source), { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
132
|
+
} catch (failure) {
|
|
133
|
+
return fail('launch', `cloudflared could not be started (${failure?.code || failure?.message || failure}).`);
|
|
134
|
+
}
|
|
135
|
+
child = proc;
|
|
136
|
+
copy = { source: found.source, version: found.version };
|
|
137
|
+
phase = 'starting';
|
|
138
|
+
let output = '';
|
|
139
|
+
const timer = setTimeout(() => {
|
|
140
|
+
if (mine !== attempt || phase !== 'starting') return;
|
|
141
|
+
child = null;
|
|
142
|
+
end(proc);
|
|
143
|
+
fail('no_address', `cloudflared gave no address within ${Math.round(urlTimeoutMs / 1000)} seconds.`, lastReason(output) || null);
|
|
144
|
+
}, urlTimeoutMs);
|
|
145
|
+
timer.unref?.();
|
|
146
|
+
// cloudflared prints the address to stderr, in pieces that can end anywhere: the search runs over what
|
|
147
|
+
// came last, not over one piece.
|
|
148
|
+
const read = (data) => {
|
|
149
|
+
output = (output + data.toString()).slice(-OUTPUT_KEPT);
|
|
150
|
+
if (mine !== attempt || phase !== 'starting') return;
|
|
151
|
+
const address = tunnelUrlIn(output);
|
|
152
|
+
if (!address) return;
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
phase = 'running';
|
|
155
|
+
setUrl(address);
|
|
156
|
+
log(` Tunnel: ${address} (cloudflared ${found.version}, ${found.source})`);
|
|
157
|
+
};
|
|
158
|
+
proc.stdout?.on('data', read);
|
|
159
|
+
proc.stderr?.on('data', read);
|
|
160
|
+
proc.on('error', (failure) => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
if (mine !== attempt || child !== proc) return;
|
|
163
|
+
child = null;
|
|
164
|
+
fail('launch', `cloudflared could not be started (${failure?.code || failure?.message || failure}).`);
|
|
165
|
+
});
|
|
166
|
+
proc.on('close', (code, signal) => {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
if (mine !== attempt || child !== proc) return; // stopped on request, or already accounted for
|
|
169
|
+
child = null;
|
|
170
|
+
const said = lastReason(output) || null;
|
|
171
|
+
if (phase === 'running') fail('ended', 'The tunnel stopped by itself. Start it again for a new address.', said);
|
|
172
|
+
else fail('exited', said || `cloudflared stopped before it gave an address (${signal || `exit ${code}`}).`, said);
|
|
173
|
+
});
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function answer(message) {
|
|
178
|
+
return { ok: true, state: phase, url, message };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function start() {
|
|
182
|
+
if (phase === 'running') return { ...answer('Already running.') };
|
|
183
|
+
if (phase === 'preparing' || phase === 'starting') return answer('Tunnel starting...');
|
|
184
|
+
const mine = ++attempt;
|
|
185
|
+
phase = 'preparing';
|
|
186
|
+
error = null;
|
|
187
|
+
copy = null;
|
|
188
|
+
setUrl(null);
|
|
189
|
+
// What the preparation runs (a hash, a `cloudflared --version`, a download) stops with this, on stop().
|
|
190
|
+
const ready = new AbortController();
|
|
191
|
+
preparing = ready;
|
|
192
|
+
const done = () => { if (preparing === ready) preparing = null; };
|
|
193
|
+
Promise.resolve().then(() => cloudflared.ensure({ signal: ready.signal })).then((found) => {
|
|
194
|
+
done();
|
|
195
|
+
if (mine === attempt) launch(found, mine);
|
|
196
|
+
}, (failure) => {
|
|
197
|
+
done();
|
|
198
|
+
if (mine !== attempt) return;
|
|
199
|
+
fail(typeof failure?.code === 'string' ? failure.code : 'unknown', failure?.message || String(failure));
|
|
200
|
+
});
|
|
201
|
+
return answer('Tunnel starting...');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function stop() {
|
|
205
|
+
const was = phase;
|
|
206
|
+
attempt++;
|
|
207
|
+
const proc = child;
|
|
208
|
+
child = null;
|
|
209
|
+
phase = 'idle';
|
|
210
|
+
error = null;
|
|
211
|
+
copy = null;
|
|
212
|
+
setUrl(null);
|
|
213
|
+
if (preparing) { preparing.abort(); preparing = null; }
|
|
214
|
+
if (was === 'preparing') { try { cloudflared.cancel(); } catch { /* nothing was running */ } }
|
|
215
|
+
end(proc);
|
|
216
|
+
return { ok: true, state: phase, message: was === 'idle' || was === 'failed' ? 'Not running.' : 'Tunnel stopped.' };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* stop(), and a wait until cloudflared has really exited (killed when it does not end by itself): at most
|
|
221
|
+
* `timeoutMs`, so the server's shutdown never waits on it for long. `exited` says whether it did by then.
|
|
222
|
+
*/
|
|
223
|
+
async function shutdown({ timeoutMs = 2000 } = {}) {
|
|
224
|
+
const stopped = stop();
|
|
225
|
+
let timer = null;
|
|
226
|
+
const exitedInTime = await Promise.race([
|
|
227
|
+
Promise.all([...ending.values()]).then(() => true),
|
|
228
|
+
new Promise((done) => { timer = setTimeout(() => done(false), timeoutMs); }),
|
|
229
|
+
]);
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
return { ...stopped, exited: exitedInTime && ending.size === 0 };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** At the server's exit, when nothing can be waited for: every cloudflared this started that is still there is killed now. */
|
|
235
|
+
function killNow() {
|
|
236
|
+
stop();
|
|
237
|
+
for (const proc of ending.keys()) { if (!exited(proc)) { try { proc.kill('SIGKILL'); } catch { /* gone already */ } } }
|
|
238
|
+
try { cloudflared.killNow?.(); } catch { /* nothing of its own runs */ }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function status() {
|
|
242
|
+
// While a start waits for the copy, the manager is asked what it knows, not sent to look again.
|
|
243
|
+
let tool;
|
|
244
|
+
try { tool = phase === 'preparing' ? cloudflared.status() : await cloudflared.inspect(); } catch (failure) { tool = { ...cloudflared.status(), state: 'failed', error: { code: 'unknown', message: failure?.message || String(failure) } }; }
|
|
245
|
+
const downloading = phase === 'preparing' && tool.state === 'downloading';
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
// What a page that predates the download reads: can a tunnel be started here, is one up, is one on its way.
|
|
249
|
+
available: tool.state !== 'unsupported',
|
|
250
|
+
running: phase === 'running' || phase === 'starting',
|
|
251
|
+
starting: phase === 'preparing' || phase === 'starting',
|
|
252
|
+
url,
|
|
253
|
+
state: downloading ? 'downloading' : phase === 'preparing' ? 'starting' : phase,
|
|
254
|
+
error,
|
|
255
|
+
cloudflared: {
|
|
256
|
+
...tool,
|
|
257
|
+
// The copy that runs, when one does; otherwise the one a start would run.
|
|
258
|
+
source: copy?.source ?? tool.source,
|
|
259
|
+
version: copy?.version ?? tool.version,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { start, stop, status, url: () => url, phase: () => phase, shutdown, killNow };
|
|
265
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// ── AI autocomplete through an endpoint of the user's own ──
|
|
2
|
+
// A local Ollama, or anything that speaks the OpenAI completions or chat
|
|
3
|
+
// protocol. What is sent (docs/design/code-editor/SPEC.md, section 9.6):
|
|
4
|
+
//
|
|
5
|
+
// ollama + fim POST {url}/api/generate { model, prompt: prefix, suffix, stream, options, keep_alive }
|
|
6
|
+
// ollama + chat POST {url}/api/chat { model, messages, stream, options, keep_alive }
|
|
7
|
+
// openai + fim POST {url} { model, prompt: prefix, suffix, max_tokens, temperature, stream }
|
|
8
|
+
// openai + chat POST {url} { model, messages, max_tokens, temperature, stream }
|
|
9
|
+
//
|
|
10
|
+
// `Authorization: Bearer` is added when SYNABUN_EDITOR_AI_KEY is set; the key is
|
|
11
|
+
// never part of an error, in any form: nothing an endpoint says about a failure
|
|
12
|
+
// (an error body, an error inside a stream) is passed on. An endpoint may repeat
|
|
13
|
+
// the request's Authorization header as sent, URL-encoded, JSON-escaped or in a
|
|
14
|
+
// form nobody listed, so its words are only read to pick one of a few sentences
|
|
15
|
+
// of this file's own (endpointFailure). Every failure of a call leaves here as
|
|
16
|
+
// an EndpointFailure with such a sentence. The answer is read as it streams
|
|
17
|
+
// (server-sent events, one JSON object per line, or one JSON body) until it
|
|
18
|
+
// ends, until the caller says it holds enough, or until the request is aborted.
|
|
19
|
+
|
|
20
|
+
const MAX_TOKENS = 256;
|
|
21
|
+
const TEMPERATURE = 0.1;
|
|
22
|
+
const MAX_REPLY_CHARS = 16_000;
|
|
23
|
+
|
|
24
|
+
const trimSlashes = (url) => String(url || '').replace(/\/+$/, '');
|
|
25
|
+
|
|
26
|
+
const REDACTED = '[redacted]';
|
|
27
|
+
// What follows "Bearer" in a header an endpoint echoes: the characters a token is made of (lib/editor-config.js).
|
|
28
|
+
const BEARER_TOKEN = /\b(Bearer)\s+[A-Za-z0-9._~+/=:@-]+/gi;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A text without the endpoint's key as it was sent: the key itself wherever it stands, and whatever follows
|
|
32
|
+
* "Bearer" (a credential whoever it belongs to). It knows the key in one form only, so it is not what keeps the
|
|
33
|
+
* key out of an endpoint's errors (nothing an endpoint says is passed on: endpointFailure); it is a second rule
|
|
34
|
+
* for text of this server's own.
|
|
35
|
+
* @param {string} text
|
|
36
|
+
* @param {string} [key] the endpoint's key (SYNABUN_EDITOR_AI_KEY), when one is set
|
|
37
|
+
*/
|
|
38
|
+
export function redactKey(text, key = '') {
|
|
39
|
+
let out = String(text ?? '');
|
|
40
|
+
if (key) out = out.split(String(key)).join(REDACTED);
|
|
41
|
+
return out.replace(BEARER_TOKEN, `$1 ${REDACTED}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A failure of an endpoint call, in words of this file only: safe for the page, a test's row and the log. */
|
|
45
|
+
export class EndpointFailure extends Error {
|
|
46
|
+
/** @param {string} message @param {{ status?: number | null, kind?: string | null }} [o] */
|
|
47
|
+
constructor(message, { status = null, kind = null } = {}) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = 'EndpointFailure';
|
|
50
|
+
this.status = status;
|
|
51
|
+
this.kind = kind;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// What a failure is called, by what the answer mentions and by its status. The first sentence whose words are
|
|
56
|
+
// found wins; with no words found, the first whose statuses hold this one.
|
|
57
|
+
const FAILURE_KINDS = [
|
|
58
|
+
{ kind: 'model', says: 'it does not have that model', statuses: [], words: /model.{0,60}(?:not found|does not exist|doesn't exist|unknown|not supported|not available)|(?:no such|unknown|invalid) model|try pulling/i },
|
|
59
|
+
{ kind: 'key', says: 'it refused the key', statuses: [401, 403], words: /unauthori[sz]ed|incorrect api key|invalid.{0,12}(?:api.?key|token|credential)|authentication|not authenticated|forbidden|permission denied/i },
|
|
60
|
+
{ kind: 'rate', says: 'it takes no more requests for now', statuses: [429], words: /rate.?limit|too many requests|quota|insufficient.{0,12}(?:credit|balance|funds|quota)/i },
|
|
61
|
+
{ kind: 'long', says: 'the request is too long for the model', statuses: [413], words: /context.{0,20}(?:length|window)|too long|maximum.{0,20}tokens|too many tokens/i },
|
|
62
|
+
{ kind: 'busy', says: 'it is busy or still loading the model', statuses: [502, 503, 504], words: /overloaded|unavailable|loading model|still loading|timed? ?out/i },
|
|
63
|
+
{ kind: 'address', says: 'nothing takes completions at that address', statuses: [404, 405], words: null },
|
|
64
|
+
];
|
|
65
|
+
// Why a request never got an answer, by the code of what ended it (fetch's `cause`).
|
|
66
|
+
const NETWORK_KINDS = {
|
|
67
|
+
ECONNREFUSED: 'nothing is listening at its address',
|
|
68
|
+
ENOTFOUND: 'its address is not a name that is known',
|
|
69
|
+
EAI_AGAIN: 'its address could not be looked up',
|
|
70
|
+
ECONNRESET: 'it closed the connection',
|
|
71
|
+
ETIMEDOUT: 'it did not answer in time',
|
|
72
|
+
UND_ERR_CONNECT_TIMEOUT: 'it did not answer in time',
|
|
73
|
+
UND_ERR_SOCKET: 'it closed the connection',
|
|
74
|
+
CERT_HAS_EXPIRED: 'its certificate is not accepted',
|
|
75
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: 'its certificate is not accepted',
|
|
76
|
+
SELF_SIGNED_CERT_IN_CHAIN: 'its certificate is not accepted',
|
|
77
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: 'its certificate is not accepted',
|
|
78
|
+
ERR_TLS_CERT_ALTNAME_INVALID: 'its certificate is not accepted',
|
|
79
|
+
ERR_INVALID_URL: 'its address is not a URL',
|
|
80
|
+
};
|
|
81
|
+
const READ_FOR_KIND = 4000;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* What an endpoint's failure is called. `said` (its error body, or the error inside its stream) is read for what
|
|
85
|
+
* it mentions and for nothing else: not a character of it is in the result.
|
|
86
|
+
* @param {number | null} status the HTTP status, or null for an error an answer carried in its stream
|
|
87
|
+
* @param {string} [said]
|
|
88
|
+
* @returns {EndpointFailure}
|
|
89
|
+
*/
|
|
90
|
+
export function endpointFailure(status, said = '') {
|
|
91
|
+
const words = String(said ?? '').slice(0, READ_FOR_KIND);
|
|
92
|
+
const code = Number.isInteger(status) ? status : null;
|
|
93
|
+
const found = FAILURE_KINDS.find((each) => each.words && each.words.test(words))
|
|
94
|
+
|| (code === null ? null : FAILURE_KINDS.find((each) => each.statuses.includes(code)));
|
|
95
|
+
const lead = code === null ? 'The endpoint reported an error' : `The endpoint answered ${code}`;
|
|
96
|
+
return new EndpointFailure(found ? `${lead}: ${found.says}` : lead, { status: code, kind: found?.kind ?? null });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whatever ended an endpoint call, as an EndpointFailure: its own when it is one; a request that got no answer by
|
|
101
|
+
* the code of what ended it; anything else without its words (they are not this file's).
|
|
102
|
+
*/
|
|
103
|
+
export function asEndpointFailure(error) {
|
|
104
|
+
if (error instanceof EndpointFailure) return error;
|
|
105
|
+
// fetch says "fetch failed" and keeps the reason in `cause` (several of them when a name has several addresses).
|
|
106
|
+
const causes = [error?.cause, error, error?.cause?.cause, ...(Array.isArray(error?.cause?.errors) ? error.cause.errors : [])];
|
|
107
|
+
const code = causes.map((each) => each?.code).find((each) => typeof each === 'string' && Object.hasOwn(NETWORK_KINDS, each));
|
|
108
|
+
return new EndpointFailure(code ? `The endpoint could not be reached: ${NETWORK_KINDS[code]}` : 'The endpoint could not be asked', { kind: code ? 'network' : null });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The HTTP request of one completion.
|
|
113
|
+
* @param {object} o
|
|
114
|
+
* @param {{ kind: 'ollama' | 'openai', url: string, model: string, shape: 'fim' | 'chat' }} o.endpoint `ai.endpoint`
|
|
115
|
+
* @param {string} [o.key] the bearer token, when one is set
|
|
116
|
+
* @param {{ prefix: string, suffix: string }} o.request
|
|
117
|
+
* @param {{ system: string, user: string }} o.prompt the chat messages (lib/editor-completion.js buildChatPrompt)
|
|
118
|
+
* @returns {{ url: string, headers: object, body: object }}
|
|
119
|
+
*/
|
|
120
|
+
export function endpointRequest({ endpoint, key = '', request, prompt }) {
|
|
121
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'text/event-stream, application/x-ndjson, application/json' };
|
|
122
|
+
if (key) headers.Authorization = `Bearer ${key}`;
|
|
123
|
+
const messages = [{ role: 'system', content: prompt.system }, { role: 'user', content: prompt.user }];
|
|
124
|
+
if (endpoint.kind === 'ollama') {
|
|
125
|
+
// The address is the server's; a pasted /api/generate or /api/chat is not doubled.
|
|
126
|
+
const base = trimSlashes(endpoint.url).replace(/\/api\/(?:generate|chat)$/, '');
|
|
127
|
+
const common = { model: endpoint.model, stream: true, options: { temperature: TEMPERATURE, num_predict: MAX_TOKENS }, keep_alive: '30m' };
|
|
128
|
+
return endpoint.shape === 'chat'
|
|
129
|
+
? { url: `${base}/api/chat`, headers, body: { ...common, messages } }
|
|
130
|
+
: { url: `${base}/api/generate`, headers, body: { ...common, prompt: request.prefix, suffix: request.suffix } };
|
|
131
|
+
}
|
|
132
|
+
const common = { model: endpoint.model, max_tokens: MAX_TOKENS, temperature: TEMPERATURE, stream: true };
|
|
133
|
+
return endpoint.shape === 'chat'
|
|
134
|
+
? { url: endpoint.url, headers, body: { ...common, messages } }
|
|
135
|
+
: { url: endpoint.url, headers, body: { ...common, prompt: request.prefix, suffix: request.suffix } };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* What one object of an answer holds, whichever of the four protocols it is.
|
|
140
|
+
* @param {object} chunk
|
|
141
|
+
* @returns {{ text: string, done: boolean, tokens: { input: number, output: number } | null, error: string | null }}
|
|
142
|
+
* `error`: what the failure is called (endpointFailure), never the endpoint's own words
|
|
143
|
+
*/
|
|
144
|
+
export function readChunk(chunk) {
|
|
145
|
+
const out = { text: '', done: false, tokens: null, error: null };
|
|
146
|
+
if (!chunk || typeof chunk !== 'object') return out;
|
|
147
|
+
if (chunk.error) {
|
|
148
|
+
let said = '';
|
|
149
|
+
try { said = typeof chunk.error === 'string' ? chunk.error : JSON.stringify(chunk.error); } catch { said = ''; }
|
|
150
|
+
out.error = endpointFailure(null, said).message;
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : null;
|
|
154
|
+
const text = chunk.response ?? chunk.message?.content ?? choice?.text ?? choice?.delta?.content ?? choice?.message?.content;
|
|
155
|
+
if (typeof text === 'string') out.text = text;
|
|
156
|
+
out.done = chunk.done === true || (choice != null && choice.finish_reason != null);
|
|
157
|
+
const input = chunk.prompt_eval_count ?? chunk.usage?.prompt_tokens;
|
|
158
|
+
const output = chunk.eval_count ?? chunk.usage?.completion_tokens;
|
|
159
|
+
if (Number.isFinite(input) || Number.isFinite(output)) out.tokens = { input: Math.round(input || 0), output: Math.round(output || 0) };
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Read an answer to its end, or until `isDone(text)` says it holds enough.
|
|
165
|
+
* @param {Response} res
|
|
166
|
+
* @param {{ isDone?: (text: string) => boolean }} [o]
|
|
167
|
+
* @returns {Promise<{ text: string, tokens: { input: number, output: number } | null, stoppedEarly: boolean }>}
|
|
168
|
+
* rejects with an EndpointFailure for an error the answer carries, and for an answer that cannot be read
|
|
169
|
+
*/
|
|
170
|
+
export async function readEndpointAnswer(res, { isDone = () => false } = {}) {
|
|
171
|
+
let text = '';
|
|
172
|
+
let tokens = null;
|
|
173
|
+
let pending = ''; // the line that is still arriving
|
|
174
|
+
let unparsed = ''; // lines that were not objects on their own: one JSON body spread over several lines
|
|
175
|
+
let finished = false;
|
|
176
|
+
|
|
177
|
+
const take = (chunk) => {
|
|
178
|
+
const part = readChunk(chunk);
|
|
179
|
+
if (part.error) throw new EndpointFailure(part.error, { kind: 'reported' });
|
|
180
|
+
text += part.text;
|
|
181
|
+
if (part.tokens) tokens = part.tokens;
|
|
182
|
+
if (part.done) finished = true;
|
|
183
|
+
};
|
|
184
|
+
const line = (raw) => {
|
|
185
|
+
let value = raw.trim();
|
|
186
|
+
if (!value || value.startsWith(':') || value.startsWith('event:') || value.startsWith('id:') || value.startsWith('retry:')) return;
|
|
187
|
+
if (value.startsWith('data:')) value = value.slice(5).trim();
|
|
188
|
+
if (value === '[DONE]') { finished = true; return; }
|
|
189
|
+
let chunk;
|
|
190
|
+
try { chunk = JSON.parse(value); } catch { unparsed += `${raw}\n`; return; }
|
|
191
|
+
take(chunk);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const reader = res.body?.getReader?.();
|
|
195
|
+
if (!reader) {
|
|
196
|
+
for (const each of (await res.text()).split('\n')) line(each);
|
|
197
|
+
} else {
|
|
198
|
+
const decoder = new TextDecoder();
|
|
199
|
+
let stoppedEarly = false;
|
|
200
|
+
try {
|
|
201
|
+
for (;;) {
|
|
202
|
+
const { value, done } = await reader.read();
|
|
203
|
+
if (done) break;
|
|
204
|
+
pending += decoder.decode(value, { stream: true });
|
|
205
|
+
const lines = pending.split('\n');
|
|
206
|
+
pending = lines.pop();
|
|
207
|
+
for (const each of lines) line(each);
|
|
208
|
+
if (finished) break;
|
|
209
|
+
if (text.length > MAX_REPLY_CHARS || isDone(text)) { stoppedEarly = true; break; }
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
// Whatever is still on its way is not wanted: the connection is given back.
|
|
213
|
+
try { await reader.cancel(); } catch { /* already closed */ }
|
|
214
|
+
}
|
|
215
|
+
if (stoppedEarly) return { text, tokens, stoppedEarly: true };
|
|
216
|
+
if (pending) line(pending);
|
|
217
|
+
}
|
|
218
|
+
if (unparsed.trim() && !text) {
|
|
219
|
+
let whole;
|
|
220
|
+
try { whole = JSON.parse(unparsed); } catch { throw new EndpointFailure('The endpoint did not answer in a form this editor reads', { kind: 'form' }); }
|
|
221
|
+
take(whole);
|
|
222
|
+
}
|
|
223
|
+
return { text, tokens, stoppedEarly: false };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** @param {{ fetchImpl?: typeof fetch }} [deps] */
|
|
227
|
+
export function createEndpointCompletionEngine({ fetchImpl = (...args) => fetch(...args) } = {}) {
|
|
228
|
+
return {
|
|
229
|
+
/**
|
|
230
|
+
* One completion.
|
|
231
|
+
* @param {object} o
|
|
232
|
+
* @param {{ kind: string, url: string, model: string, shape: string }} o.endpoint
|
|
233
|
+
* @param {string} [o.key]
|
|
234
|
+
* @param {{ prefix: string, suffix: string }} o.request
|
|
235
|
+
* @param {{ system: string, user: string }} o.prompt
|
|
236
|
+
* @param {AbortSignal} [o.signal]
|
|
237
|
+
* @param {(text: string) => boolean} [o.isDone] the first stop condition: the answer so far holds all that would be shown
|
|
238
|
+
* @returns {Promise<{ text: string, tokens: { input: number, output: number } | null }>} rejects with an
|
|
239
|
+
* EndpointFailure (its message holds nothing the endpoint said), or with the abort when `signal` fired
|
|
240
|
+
*/
|
|
241
|
+
async complete({ endpoint, key = '', request, prompt, signal, isDone } = {}) {
|
|
242
|
+
const { url, headers, body } = endpointRequest({ endpoint, key, request, prompt });
|
|
243
|
+
// Our own controller, so an answer that holds enough can be cut off without the caller aborting.
|
|
244
|
+
const own = new AbortController();
|
|
245
|
+
const onAbort = () => own.abort();
|
|
246
|
+
if (signal?.aborted) own.abort();
|
|
247
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
248
|
+
try {
|
|
249
|
+
const res = await fetchImpl(url, { method: 'POST', headers, body: JSON.stringify(body), signal: own.signal });
|
|
250
|
+
// What it says is read for what the failure is called, and stays here.
|
|
251
|
+
if (!res.ok) throw endpointFailure(res.status, await res.text().catch(() => ''));
|
|
252
|
+
const answer = await readEndpointAnswer(res, { isDone });
|
|
253
|
+
return { text: answer.text, tokens: answer.tokens };
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// The caller gave up: that is not the endpoint's failure.
|
|
256
|
+
if (signal?.aborted) throw error;
|
|
257
|
+
throw asEndpointFailure(error);
|
|
258
|
+
} finally {
|
|
259
|
+
signal?.removeEventListener('abort', onAbort);
|
|
260
|
+
own.abort();
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|