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
|
@@ -1,534 +1,317 @@
|
|
|
1
|
-
//
|
|
2
|
-
// SynaBun Neural Interface — Command Runner
|
|
3
|
-
// Save and one-click execute terminal commands,
|
|
4
|
-
// organized by category groups.
|
|
5
|
-
// ═══════════════════════════════════════════
|
|
6
|
-
|
|
1
|
+
// SynaBun — a searchable library of saved terminal commands.
|
|
7
2
|
import { emit, on } from './state.js';
|
|
8
3
|
import { KEYS, COLOR_PALETTE } from './constants.js';
|
|
9
4
|
import { storage } from './storage.js';
|
|
5
|
+
import { commandLaunchPayload, selectCommandGroups, isCommandLaunchAvailable } from './command-runner-model.js';
|
|
10
6
|
|
|
11
|
-
const $ =
|
|
12
|
-
const esc =
|
|
13
|
-
const genId =
|
|
14
|
-
|
|
15
|
-
// ── Module-local state ──
|
|
16
|
-
let _panel = null;
|
|
17
|
-
let _data = { categories: [], commands: [] };
|
|
18
|
-
let _editTarget = null; // { type: 'command'|'category', id: string|null } — null = creating new
|
|
19
|
-
|
|
7
|
+
const $ = id => document.getElementById(id);
|
|
8
|
+
const esc = value => String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
9
|
+
const genId = prefix => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
10
|
+
const color = value => CSS.supports('color', String(value)) ? value : COLOR_PALETTE[0];
|
|
20
11
|
const PANEL_KEY = KEYS.PANEL_PREFIX + 'command-runner';
|
|
21
12
|
const DATA_KEY = KEYS.COMMAND_RUNNER;
|
|
13
|
+
const canRun = () => isCommandLaunchAvailable(navigator.onLine, location.hostname);
|
|
14
|
+
const icon = path => `<svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${path}</svg>`;
|
|
15
|
+
const SVG = {
|
|
16
|
+
terminal: icon('<path d="m4 6 6 6-6 6M13 18h7"/>'),
|
|
17
|
+
play: icon('<path d="m8 5 11 7-11 7Z"/>'),
|
|
18
|
+
plus: icon('<path d="M12 5v14M5 12h14"/>'),
|
|
19
|
+
search: icon('<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 4 4"/>'),
|
|
20
|
+
folder: icon('<path d="M3 7V5h6l2 2h10v12H3Z"/>'),
|
|
21
|
+
edit: icon('<path d="m16 3 5 5-12 12-6 1 1-6Z"/><path d="m13 6 5 5"/>'),
|
|
22
|
+
trash: icon('<path d="M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7"/>'),
|
|
23
|
+
chevron: icon('<path d="m9 5 7 7-7 7"/>'),
|
|
24
|
+
close: icon('<path d="m6 6 12 12M6 18 18 6"/>'),
|
|
25
|
+
check: icon('<path d="m5 12 4 4L19 6"/>'),
|
|
26
|
+
};
|
|
27
|
+
let _panel = null, _editTarget = null, _modalOpener = null, _panelOpener = null;
|
|
28
|
+
let _data = { categories: [], commands: [] };
|
|
29
|
+
let _view = { query: '', categoryId: '', sort: 'saved' };
|
|
30
|
+
let _dataError = false, _launchTimer = null, _launchedId = null, _abort = null;
|
|
31
|
+
const _searchCollapsed = new Set();
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
const SVG_PLAY = `<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><polygon points="4,2 14,8 4,14"/></svg>`;
|
|
25
|
-
const SVG_EDIT = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11.5 2.5l2 2L5 13l-3 1 1-3z"/></svg>`;
|
|
26
|
-
const SVG_DELETE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="4" x2="13" y2="4"/><path d="M6 4V3a1 1 0 011-1h2a1 1 0 011 1v1"/><path d="M4.5 4l.5 9a1 1 0 001 1h4a1 1 0 001-1l.5-9"/></svg>`;
|
|
27
|
-
const SVG_CHEVRON = `<svg viewBox="0 0 16 16" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 4 10 8 6 12"/></svg>`;
|
|
28
|
-
const SVG_CLOSE = `<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>`;
|
|
29
|
-
const SVG_TERMINAL = `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`;
|
|
30
|
-
|
|
31
|
-
// ═══════════════════════════════════════════
|
|
32
|
-
// INIT
|
|
33
|
-
// ═══════════════════════════════════════════
|
|
33
|
+
export function initCommandRunner() { return on('command-runner:open', openPanel); }
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
function clampPanel() {
|
|
36
|
+
if (!_panel) return;
|
|
37
|
+
const width = _panel.offsetWidth, height = _panel.offsetHeight;
|
|
38
|
+
const top = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--navbar-height')) || 48;
|
|
39
|
+
_panel.style.left = Math.max(8, Math.min(parseFloat(_panel.style.left) || 8, window.innerWidth - width - 8)) + 'px';
|
|
40
|
+
_panel.style.top = Math.max(top + 8, Math.min(parseFloat(_panel.style.top) || top + 8, window.innerHeight - height - 8)) + 'px';
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
// ═══════════════════════════════════════════
|
|
40
|
-
// PANEL LIFECYCLE
|
|
41
|
-
// ═══════════════════════════════════════════
|
|
42
|
-
|
|
43
42
|
function openPanel() {
|
|
44
|
-
if (_panel) {
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
if (_panel) { $('cr-search')?.focus(); return; }
|
|
44
|
+
_panelOpener = document.activeElement;
|
|
45
|
+
_view = { query: '', categoryId: '', sort: 'saved' };
|
|
46
|
+
_searchCollapsed.clear();
|
|
47
|
+
_abort = new AbortController();
|
|
48
|
+
_panel = document.createElement('section');
|
|
47
49
|
_panel.className = 'command-runner-panel glass resizable';
|
|
48
50
|
_panel.id = 'command-runner-panel';
|
|
51
|
+
_panel.setAttribute('role', 'region');
|
|
52
|
+
_panel.setAttribute('aria-labelledby', 'cr-title');
|
|
49
53
|
_panel.innerHTML = buildPanelHTML();
|
|
50
54
|
document.body.appendChild(_panel);
|
|
51
|
-
|
|
52
|
-
// Restore saved position
|
|
53
55
|
try {
|
|
54
56
|
const saved = JSON.parse(storage.getItem(PANEL_KEY));
|
|
55
57
|
if (saved) {
|
|
56
|
-
if (saved.x
|
|
57
|
-
if (saved.y
|
|
58
|
-
if (saved.w) _panel.style.width = saved.w + 'px';
|
|
59
|
-
if (saved.h) _panel.style.height = saved.h + 'px';
|
|
58
|
+
if (Number.isFinite(saved.x)) _panel.style.left = saved.x + 'px';
|
|
59
|
+
if (Number.isFinite(saved.y)) _panel.style.top = saved.y + 'px';
|
|
60
|
+
if (saved.w > 0) _panel.style.width = saved.w + 'px';
|
|
61
|
+
if (saved.h > 0) _panel.style.height = saved.h + 'px';
|
|
60
62
|
}
|
|
61
|
-
} catch {}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
} catch { /* Use the default layout if old geometry cannot be read. */ }
|
|
64
|
+
if (!_panel.style.left) _panel.style.left = (window.innerWidth - _panel.offsetWidth) / 2 + 'px';
|
|
65
|
+
if (!_panel.style.top) _panel.style.top = (window.innerHeight - _panel.offsetHeight) / 2 + 'px';
|
|
66
|
+
clampPanel();
|
|
67
|
+
loadData(); wirePanel(); renderBody();
|
|
68
|
+
window.addEventListener('resize', clampPanel, { signal: _abort.signal });
|
|
69
|
+
window.addEventListener('online', renderBody, { signal: _abort.signal });
|
|
70
|
+
window.addEventListener('offline', renderBody, { signal: _abort.signal });
|
|
71
|
+
const panel = _panel;
|
|
72
|
+
requestAnimationFrame(() => {
|
|
73
|
+
if (_panel !== panel) return;
|
|
74
|
+
panel.classList.add('open');
|
|
75
|
+
$('cr-search')?.focus();
|
|
76
|
+
});
|
|
75
77
|
}
|
|
76
|
-
|
|
77
78
|
function closePanel() {
|
|
78
79
|
if (!_panel) return;
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
w: Math.round(rect.width), h: Math.round(rect.height),
|
|
85
|
-
}));
|
|
86
|
-
} catch {}
|
|
87
|
-
|
|
88
|
-
_panel.remove();
|
|
89
|
-
_panel = null;
|
|
90
|
-
_editTarget = null;
|
|
80
|
+
const rect = _panel.getBoundingClientRect();
|
|
81
|
+
try { storage.setItem(PANEL_KEY, JSON.stringify({ x: Math.round(rect.left), y: Math.round(rect.top), w: Math.round(rect.width), h: Math.round(rect.height) })); } catch {}
|
|
82
|
+
_abort?.abort(); clearTimeout(_launchTimer);
|
|
83
|
+
_panel.remove(); _panel = null; _editTarget = null; _launchedId = null;
|
|
84
|
+
if (_panelOpener?.isConnected) _panelOpener.focus();
|
|
91
85
|
}
|
|
92
|
-
|
|
93
|
-
// ═══════════════════════════════════════════
|
|
94
|
-
// DATA
|
|
95
|
-
// ═══════════════════════════════════════════
|
|
96
|
-
|
|
97
86
|
function loadData() {
|
|
87
|
+
_dataError = false;
|
|
98
88
|
try {
|
|
99
89
|
const raw = storage.getItem(DATA_KEY);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
106
|
-
} else {
|
|
107
|
-
_data = { categories: [], commands: [] };
|
|
108
|
-
}
|
|
90
|
+
const parsed = raw ? JSON.parse(raw) : { categories: [], commands: [] };
|
|
91
|
+
if (!Array.isArray(parsed.categories) || !Array.isArray(parsed.commands)
|
|
92
|
+
|| !parsed.categories.every(c => c && typeof c.id === 'string' && typeof c.name === 'string')
|
|
93
|
+
|| !parsed.commands.every(c => c && typeof c.id === 'string' && typeof c.name === 'string' && typeof c.command === 'string')) throw new Error('Invalid library');
|
|
94
|
+
_data = { categories: parsed.categories, commands: parsed.commands };
|
|
109
95
|
} catch {
|
|
110
96
|
_data = { categories: [], commands: [] };
|
|
97
|
+
_dataError = true; // Never overwrite an unreadable library with an empty one.
|
|
111
98
|
}
|
|
112
99
|
}
|
|
113
|
-
|
|
114
|
-
function saveData() {
|
|
115
|
-
storage.setItem(DATA_KEY, JSON.stringify(_data));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function getCategoryCommands(catId) {
|
|
119
|
-
return _data.commands
|
|
120
|
-
.filter(c => c.categoryId === catId)
|
|
121
|
-
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ═══════════════════════════════════════════
|
|
125
|
-
// HTML BUILDERS
|
|
126
|
-
// ═══════════════════════════════════════════
|
|
100
|
+
function saveData() { storage.setItem(DATA_KEY, JSON.stringify(_data)); }
|
|
127
101
|
|
|
128
102
|
function buildPanelHTML() {
|
|
129
103
|
return `
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<div class="
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div class="cr-
|
|
141
|
-
|
|
142
|
-
<h3>Command Runner</h3>
|
|
143
|
-
<span class="cr-count" id="cr-total-count"></span>
|
|
144
|
-
</div>
|
|
145
|
-
<div class="cr-header-actions">
|
|
146
|
-
<button class="cr-header-btn" id="cr-add-cat-btn">+ Group</button>
|
|
147
|
-
<button class="cr-header-btn cr-primary" id="cr-add-cmd-btn">+ Command</button>
|
|
148
|
-
</div>
|
|
149
|
-
<button class="cr-close" id="cr-close-btn">${SVG_CLOSE}</button>
|
|
104
|
+
${['t', 'r', 'b', 'l', 'tl', 'tr', 'bl', 'br'].map(edge => `<div aria-hidden="true" class="resize-handle resize-handle-${edge}" data-resize="${edge}"></div>`).join('')}
|
|
105
|
+
<header class="cr-header drag-handle" data-drag="command-runner-panel">
|
|
106
|
+
<div class="cr-header-left"><h3 id="cr-title">Command Runner</h3><span class="cr-count" id="cr-total-count">0</span></div>
|
|
107
|
+
<button type="button" class="cr-close" id="cr-close-btn" aria-label="Close Command Runner" title="Close (Escape)">${SVG.close}</button>
|
|
108
|
+
</header>
|
|
109
|
+
<div class="cr-main" id="cr-main">
|
|
110
|
+
<div class="cr-library-head"><div><h2>Saved commands</h2><p>Organize commands and run them in a new terminal.</p></div><button type="button" class="cr-button cr-primary" id="cr-add-cmd-btn">${SVG.plus}<span>New command</span></button></div>
|
|
111
|
+
<div class="cr-toolbar"><div class="cr-search-wrap">${SVG.search}<label class="cr-sr" for="cr-search">Search commands, groups or directories</label><input id="cr-search" type="search" placeholder="Search commands, groups or directories…" autocomplete="off" spellcheck="false"><button type="button" class="cr-icon-btn" id="cr-clear-search" aria-label="Clear search" hidden>${SVG.close}</button><kbd aria-hidden="true">/</kbd></div><label class="cr-sr" for="cr-sort">Command order</label><select id="cr-sort" class="cr-sort"><option value="saved">Saved order</option><option value="recent">Recently run</option></select></div>
|
|
112
|
+
<div class="cr-filter-bar"><nav id="cr-filters" class="cr-filters" aria-label="Filter by group"></nav><button type="button" class="cr-button cr-new-group" id="cr-add-cat-btn" title="Create a group">${SVG.plus}<span>Group</span></button></div>
|
|
113
|
+
<div class="cr-notice" id="cr-notice" role="status" hidden></div>
|
|
114
|
+
<div class="cr-body" id="cr-body" role="region" aria-label="Saved commands"></div>
|
|
115
|
+
<footer class="cr-footer"><span id="cr-results" role="status" aria-live="polite" aria-atomic="true"></span><span class="cr-footer-hint">${SVG.terminal}Runs in a new terminal</span></footer>
|
|
150
116
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
<div class="cr-modal-overlay hidden" id="cr-modal-overlay"><div class="cr-modal" id="cr-modal" role="dialog" aria-modal="true" aria-labelledby="cr-modal-title" tabindex="-1"></div></div>`;
|
|
118
|
+
}
|
|
119
|
+
function emptyState(title, description, svg, action = '') {
|
|
120
|
+
return `<div class="cr-empty"><div class="cr-empty-icon">${svg}</div><h3>${esc(title)}</h3><p>${esc(description)}</p>${action}</div>`;
|
|
121
|
+
}
|
|
122
|
+
function renderCommand(cmd) {
|
|
123
|
+
const launched = cmd.id === _launchedId;
|
|
124
|
+
return `<article class="cr-cmd${launched ? ' cr-cmd--launched' : ''}" data-cmd-id="${esc(cmd.id)}">
|
|
125
|
+
<div class="cr-cmd-content"><h4 class="cr-cmd-name" dir="auto">${esc(cmd.name)}</h4>
|
|
126
|
+
<div class="cr-code"><span class="cr-prompt" aria-hidden="true">$</span><code class="cr-cmd-text" dir="ltr">${esc(cmd.command)}</code></div>
|
|
127
|
+
<span class="cr-cmd-cwd" title="${esc(cmd.cwd || 'Default terminal directory')}">${SVG.folder}<span dir="ltr">${esc(cmd.cwd || 'Default terminal directory')}</span></span>
|
|
156
128
|
</div>
|
|
157
|
-
|
|
129
|
+
<div class="cr-cmd-actions"><button type="button" class="cr-icon-btn" data-action="edit-cmd" aria-label="Edit command ${esc(cmd.name)}" title="Edit command">${SVG.edit}</button><button type="button" class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cmd" aria-label="Delete command ${esc(cmd.name)}" title="Delete command">${SVG.trash}</button><button type="button" class="cr-run-btn${launched ? ' sent' : ''}" data-action="run" aria-label="Run ${esc(cmd.name)} in a new terminal"${!canRun() ? ' disabled title="Reconnect to run this command"' : ''}>${launched ? SVG.check : SVG.play}<span>${launched ? 'Sent' : 'Run'}</span></button></div>
|
|
130
|
+
</article>`;
|
|
158
131
|
}
|
|
159
|
-
|
|
160
132
|
function renderBody() {
|
|
161
133
|
const body = $('cr-body');
|
|
162
134
|
if (!body) return;
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
135
|
+
const focus = document.activeElement;
|
|
136
|
+
const focusedCommand = focus?.closest('.cr-cmd')?.dataset.cmdId;
|
|
137
|
+
const focusedGroup = focus?.closest('.cr-category')?.dataset.catId;
|
|
138
|
+
const focusedAction = focus?.dataset.action;
|
|
139
|
+
const focusedFilter = focus?.dataset.filter;
|
|
140
|
+
if (_view.categoryId && !_data.categories.some(c => c.id === _view.categoryId)) _view.categoryId = '';
|
|
167
141
|
const sorted = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const collapsed =
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
html += ` <button class="cr-icon-btn" data-action="edit-cat" title="Edit group">${SVG_EDIT}</button>`;
|
|
194
|
-
html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cat" title="Delete group">${SVG_DELETE}</button>`;
|
|
195
|
-
html += ` </span>`;
|
|
196
|
-
html += ` </div>`;
|
|
197
|
-
html += ` <div class="cr-cat-body">`;
|
|
198
|
-
|
|
199
|
-
if (cmds.length === 0) {
|
|
200
|
-
html += `<div class="cr-cat-empty">No commands in this group</div>`;
|
|
201
|
-
} else {
|
|
202
|
-
for (const cmd of cmds) {
|
|
203
|
-
html += `<div class="cr-cmd" data-cmd-id="${esc(cmd.id)}">`;
|
|
204
|
-
html += ` <button class="cr-run-btn" title="Run command">${SVG_PLAY}</button>`;
|
|
205
|
-
html += ` <div class="cr-cmd-info">`;
|
|
206
|
-
html += ` <div class="cr-cmd-name">${esc(cmd.name)}</div>`;
|
|
207
|
-
html += ` <div class="cr-cmd-text">${esc(cmd.command)}</div>`;
|
|
208
|
-
if (cmd.cwd) {
|
|
209
|
-
html += ` <div class="cr-cmd-cwd">${esc(cmd.cwd)}</div>`;
|
|
210
|
-
}
|
|
211
|
-
html += ` </div>`;
|
|
212
|
-
html += ` <span class="cr-cmd-actions">`;
|
|
213
|
-
html += ` <button class="cr-icon-btn" data-action="edit-cmd" title="Edit command">${SVG_EDIT}</button>`;
|
|
214
|
-
html += ` <button class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cmd" title="Delete command">${SVG_DELETE}</button>`;
|
|
215
|
-
html += ` </span>`;
|
|
216
|
-
html += `</div>`;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
html += ` </div>`;
|
|
221
|
-
html += `</div>`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
body.innerHTML = html;
|
|
142
|
+
$('cr-total-count').textContent = String(_data.commands.length);
|
|
143
|
+
$('cr-clear-search').hidden = !_view.query;
|
|
144
|
+
$('cr-add-cat-btn').disabled = _dataError; $('cr-add-cmd-btn').disabled = _dataError;
|
|
145
|
+
$('cr-filters').innerHTML = [{ id: '', name: 'All commands', count: _data.commands.length }, ...sorted.map(c => ({ ...c, count: _data.commands.filter(cmd => cmd.categoryId === c.id).length }))].map(c => `
|
|
146
|
+
<button type="button" class="cr-filter${_view.categoryId === c.id ? ' active' : ''}" data-filter="${esc(c.id)}" aria-pressed="${_view.categoryId === c.id}" title="${esc(c.name)}">${c.id ? `<span class="cr-cat-dot" style="background:${esc(color(c.color))}" aria-hidden="true"></span>` : ''}<span>${esc(c.name)}</span><span class="cr-filter-count">${c.count}</span></button>`).join('');
|
|
147
|
+
const notice = $('cr-notice');
|
|
148
|
+
notice.hidden = !_dataError && navigator.onLine !== false;
|
|
149
|
+
notice.innerHTML = _dataError ? `Your command library could not be loaded. <button type="button" class="cr-button" data-action="retry">Try again</button>` : canRun() ? 'You’re offline. Local terminal commands are still available.' : 'You’re offline. Your saved commands are available; reconnect to run them.';
|
|
150
|
+
const groups = selectCommandGroups(_data, _view);
|
|
151
|
+
const count = groups.reduce((n, group) => n + group.commands.length, 0);
|
|
152
|
+
$('cr-results').textContent = _dataError ? 'Library unavailable' : `${count} of ${_data.commands.length} command${_data.commands.length === 1 ? '' : 's'}`;
|
|
153
|
+
if (_dataError) body.innerHTML = emptyState('Your library is safe', 'Try loading it again before making changes.', SVG.folder);
|
|
154
|
+
else if (!_data.categories.length) body.innerHTML = emptyState('No saved commands', 'Create a group, then save the commands you use regularly.', SVG.terminal, `<button type="button" class="cr-button cr-primary" data-action="first-group">${SVG.plus}Create a group</button>`);
|
|
155
|
+
else if (!groups.length || (!count && _view.query.trim())) body.innerHTML = emptyState('No commands found', `Try a different search${_view.categoryId ? ' or another group' : ''}.`, SVG.search, '<button type="button" class="cr-button" data-action="reset-filters">Clear filters</button>');
|
|
156
|
+
else body.innerHTML = groups.map(({ category: cat, commands }) => {
|
|
157
|
+
const collapsed = _view.query.trim() ? _searchCollapsed.has(cat.id) : cat.collapsed;
|
|
158
|
+
const listId = 'cr-list-' + cat.id;
|
|
159
|
+
return `<section class="cr-category${collapsed ? ' collapsed' : ''}" data-cat-id="${esc(cat.id)}">
|
|
160
|
+
<div class="cr-cat-header"><button type="button" class="cr-cat-toggle" data-action="collapse" aria-expanded="${!collapsed}" aria-controls="${esc(listId)}" title="${esc(cat.name)}"><span class="cr-cat-chevron">${SVG.chevron}</span><span class="cr-cat-dot" style="background:${esc(color(cat.color))}" aria-hidden="true"></span><span class="cr-cat-name">${esc(cat.name)}</span><span class="cr-cat-count">${commands.length}</span></button>
|
|
161
|
+
<span class="cr-cat-actions"><button type="button" class="cr-icon-btn" data-action="add-in-group" aria-label="Add command to ${esc(cat.name)}" title="Add command">${SVG.plus}</button><button type="button" class="cr-icon-btn" data-action="edit-cat" aria-label="Edit group ${esc(cat.name)}" title="Edit group">${SVG.edit}</button><button type="button" class="cr-icon-btn cr-icon-btn--danger" data-action="delete-cat" aria-label="Delete group ${esc(cat.name)}" title="Delete group">${SVG.trash}</button></span></div>
|
|
162
|
+
<div class="cr-cat-body" id="${esc(listId)}"${collapsed ? ' hidden' : ''}>${commands.length ? commands.map(renderCommand).join('') : `<div class="cr-cat-empty"><span>No commands in this group yet.</span><button type="button" class="cr-button" data-action="add-in-group">${SVG.plus}Add a command</button></div>`}</div></section>`;
|
|
163
|
+
}).join('');
|
|
164
|
+
if (focusedFilter != null) [...$('cr-filters').querySelectorAll('button')].find(b => b.dataset.filter === focusedFilter)?.focus();
|
|
165
|
+
else if (focusedCommand) [...body.querySelectorAll('.cr-cmd')].find(e => e.dataset.cmdId === focusedCommand)?.querySelector(`[data-action="${focusedAction}"]`)?.focus();
|
|
166
|
+
else if (focusedGroup) [...body.querySelectorAll('.cr-category')].find(e => e.dataset.catId === focusedGroup)?.querySelector(`[data-action="${focusedAction}"]`)?.focus();
|
|
225
167
|
}
|
|
226
168
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
function openModal(type, id) {
|
|
232
|
-
_editTarget = { type, id: id || null };
|
|
233
|
-
const overlay = $('cr-modal-overlay');
|
|
169
|
+
function openModal(type, id = null, categoryId = '', opener = document.activeElement) {
|
|
170
|
+
if (_dataError) return;
|
|
171
|
+
_modalOpener = opener;
|
|
172
|
+
_editTarget = { type, id, categoryId };
|
|
234
173
|
const modal = $('cr-modal');
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const existing = id ? _data.commands.find(c => c.id === id) : null;
|
|
242
|
-
modal.innerHTML = buildCommandForm(existing);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
overlay.classList.remove('hidden');
|
|
174
|
+
if (type === 'category') modal.innerHTML = buildCategoryForm(id ? _data.categories.find(c => c.id === id) : null);
|
|
175
|
+
else if (type === 'command') modal.innerHTML = buildCommandForm(id ? _data.commands.find(c => c.id === id) : null, categoryId);
|
|
176
|
+
else modal.innerHTML = buildDeleteDialog(type, id);
|
|
177
|
+
$('cr-modal-overlay').classList.remove('hidden');
|
|
178
|
+
$('cr-main').inert = true; _panel.querySelector('.cr-header').inert = true;
|
|
179
|
+
modal.querySelector('input:not([type="hidden"]), #cr-modal-cancel')?.focus();
|
|
246
180
|
}
|
|
247
|
-
|
|
248
181
|
function closeModal() {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
182
|
+
$('cr-modal-overlay')?.classList.add('hidden');
|
|
183
|
+
$('cr-main').inert = false; _panel.querySelector('.cr-header').inert = false; _editTarget = null;
|
|
184
|
+
if (_modalOpener?.isConnected) _modalOpener.focus(); else $('cr-search')?.focus();
|
|
185
|
+
}
|
|
186
|
+
function modalHeader(title, description = '') {
|
|
187
|
+
return `<div class="cr-modal-header"><div><h4 id="cr-modal-title">${esc(title)}</h4>${description ? `<p>${esc(description)}</p>` : ''}</div><button type="button" class="cr-icon-btn" id="cr-modal-close" aria-label="Close dialog">${SVG.close}</button></div>`;
|
|
188
|
+
}
|
|
189
|
+
function modalFooter(label) {
|
|
190
|
+
return `<p class="cr-form-error" id="cr-form-error" role="alert" hidden></p><div class="cr-modal-footer"><button type="button" class="cr-button" id="cr-modal-cancel">Cancel</button><button type="submit" class="cr-button cr-primary" id="cr-modal-save">${label}</button></div>`;
|
|
252
191
|
}
|
|
253
|
-
|
|
254
192
|
function buildCategoryForm(existing) {
|
|
255
|
-
const name = existing ? esc(existing.name) : '';
|
|
256
193
|
const activeColor = existing?.color || COLOR_PALETTE[0];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
for (const c of COLOR_PALETTE.slice(0, 16)) {
|
|
260
|
-
const sel = c === activeColor ? ' selected' : '';
|
|
261
|
-
swatches += `<button type="button" class="cr-color-swatch${sel}" data-color="${c}" style="background:${c}"></button>`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return `
|
|
265
|
-
<div class="cr-modal-header">
|
|
266
|
-
<h4>${existing ? 'Edit' : 'New'} Group</h4>
|
|
267
|
-
<button class="cr-icon-btn" id="cr-modal-close">${SVG_CLOSE}</button>
|
|
268
|
-
</div>
|
|
269
|
-
<div class="cr-modal-body">
|
|
270
|
-
<label class="cr-form-label">Name</label>
|
|
271
|
-
<input class="cr-form-input" id="cr-cat-name" type="text" value="${name}" placeholder="e.g. Dev Servers" autocomplete="off" />
|
|
272
|
-
<label class="cr-form-label" style="margin-top:12px">Color</label>
|
|
273
|
-
<div class="cr-color-picker" id="cr-cat-color">${swatches}</div>
|
|
274
|
-
<input type="hidden" id="cr-cat-color-val" value="${esc(activeColor)}" />
|
|
275
|
-
</div>
|
|
276
|
-
<div class="cr-modal-footer">
|
|
277
|
-
<button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
|
|
278
|
-
<button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
|
|
279
|
-
</div>
|
|
280
|
-
`;
|
|
194
|
+
const swatches = COLOR_PALETTE.slice(0, 16).map(c => `<button type="button" class="cr-color-swatch${c === activeColor ? ' selected' : ''}" data-color="${c}" aria-label="Group color ${c}" aria-pressed="${c === activeColor}" style="--swatch:${c}">${SVG.check}</button>`).join('');
|
|
195
|
+
return `<form id="cr-form" novalidate>${modalHeader(existing ? 'Edit group' : 'Create a group', 'Keep related commands together.')}<div class="cr-modal-body"><div class="cr-form-fields"><div class="cr-form-row"><label class="cr-form-label" for="cr-cat-name">Group name</label><input class="cr-form-input" id="cr-cat-name" required type="text" value="${esc(existing?.name)}" placeholder="Development, checks, deployment…" autocomplete="off"></div></div><fieldset class="cr-color-field"><legend class="cr-form-label">Group color</legend><div class="cr-color-picker" id="cr-cat-color">${swatches}</div></fieldset><input type="hidden" id="cr-cat-color-val" value="${esc(activeColor)}"></div>${modalFooter(existing ? 'Save changes' : 'Create group')}</form>`;
|
|
281
196
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
<label class="cr-form-label">Group</label>
|
|
303
|
-
<select class="cr-form-input cr-form-select" id="cr-cmd-cat">${catOptions}</select>
|
|
304
|
-
|
|
305
|
-
<label class="cr-form-label" style="margin-top:12px">Name</label>
|
|
306
|
-
<input class="cr-form-input" id="cr-cmd-name" type="text" value="${name}" placeholder="e.g. Start Dev Server" autocomplete="off" />
|
|
307
|
-
|
|
308
|
-
<label class="cr-form-label" style="margin-top:12px">Command</label>
|
|
309
|
-
<input class="cr-form-input cr-form-mono" id="cr-cmd-command" type="text" value="${command}" placeholder="e.g. npm run dev" autocomplete="off" />
|
|
310
|
-
|
|
311
|
-
<label class="cr-form-label" style="margin-top:12px">Working Directory <span class="cr-form-hint">(optional)</span></label>
|
|
312
|
-
<input class="cr-form-input cr-form-mono" id="cr-cmd-cwd" type="text" value="${cwd}" placeholder="e.g. /home/user/myproject" autocomplete="off" />
|
|
313
|
-
</div>
|
|
314
|
-
<div class="cr-modal-footer">
|
|
315
|
-
<button class="cr-modal-btn" id="cr-modal-cancel">Cancel</button>
|
|
316
|
-
<button class="cr-modal-btn cr-modal-btn--primary" id="cr-modal-save">${existing ? 'Save' : 'Create'}</button>
|
|
317
|
-
</div>
|
|
318
|
-
`;
|
|
197
|
+
function buildCommandForm(existing, categoryId) {
|
|
198
|
+
const catId = existing?.categoryId || categoryId || _view.categoryId;
|
|
199
|
+
const options = [..._data.categories].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)).map(c => `<option value="${esc(c.id)}"${c.id === catId ? ' selected' : ''}>${esc(c.name)}</option>`).join('');
|
|
200
|
+
return `<form id="cr-form" novalidate>${modalHeader(existing ? 'Edit command' : 'Save a command', 'Choose a group and enter the command to save.')}<div class="cr-modal-body"><div class="cr-form-fields">
|
|
201
|
+
<div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-name">Command name</label><input class="cr-form-input" id="cr-cmd-name" required type="text" value="${esc(existing?.name)}" placeholder="Start dev server" autocomplete="off"></div>
|
|
202
|
+
<div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-cat">Group</label><select class="cr-form-input" id="cr-cmd-cat">${options}</select></div>
|
|
203
|
+
<div class="cr-form-row cr-form-row--command"><label class="cr-form-label" for="cr-cmd-command">Command</label><textarea class="cr-form-input cr-form-mono" id="cr-cmd-command" required rows="3" placeholder="npm run dev" spellcheck="false" autocomplete="off">${esc(existing?.command)}</textarea></div>
|
|
204
|
+
<div class="cr-form-row"><label class="cr-form-label" for="cr-cmd-cwd">Working directory <span class="cr-form-hint">Optional</span></label><div><input class="cr-form-input cr-form-mono" id="cr-cmd-cwd" type="text" value="${esc(existing?.cwd)}" placeholder="/path/to/your/project" autocomplete="off" aria-describedby="cr-cwd-help"><p id="cr-cwd-help" class="cr-form-help">Leave blank to use the default terminal directory.</p></div></div>
|
|
205
|
+
</div></div>${modalFooter(existing ? 'Save changes' : 'Save command')}</form>`;
|
|
206
|
+
}
|
|
207
|
+
function buildDeleteDialog(type, id) {
|
|
208
|
+
const group = type === 'delete-category';
|
|
209
|
+
const entry = (group ? _data.categories : _data.commands).find(e => e.id === id);
|
|
210
|
+
const count = _data.commands.filter(c => c.categoryId === id).length;
|
|
211
|
+
const description = group ? `This removes the group${count ? ` and its ${count} saved command${count === 1 ? '' : 's'}` : ''}.` : 'This removes the saved command from your library.';
|
|
212
|
+
return `${modalHeader(group ? 'Delete this group?' : 'Delete this command?')}<div class="cr-modal-body"><p class="cr-delete-name" dir="auto">${esc(entry?.name)}</p><p class="cr-form-help">${description} Existing terminals stay open.</p></div><div class="cr-modal-footer"><button type="button" class="cr-button" id="cr-modal-cancel">Cancel</button><button type="button" class="cr-button cr-danger" data-action="confirm-delete">Delete ${group ? 'group' : 'command'}</button></div>`;
|
|
213
|
+
}
|
|
214
|
+
function invalid(field, message) {
|
|
215
|
+
field?.setAttribute('aria-invalid', 'true'); field?.setAttribute('aria-describedby', 'cr-form-error');
|
|
216
|
+
$('cr-form-error').textContent = message; $('cr-form-error').hidden = false; field?.focus();
|
|
319
217
|
}
|
|
320
|
-
|
|
321
|
-
// ═══════════════════════════════════════════
|
|
322
|
-
// CRUD
|
|
323
|
-
// ═══════════════════════════════════════════
|
|
324
|
-
|
|
325
218
|
function saveFromModal() {
|
|
326
|
-
if (!_editTarget) return;
|
|
327
|
-
|
|
219
|
+
if (!_editTarget || _dataError) return;
|
|
220
|
+
const nameEl = $(_editTarget.type === 'category' ? 'cr-cat-name' : 'cr-cmd-name'), name = nameEl?.value.trim();
|
|
221
|
+
if (!name) return invalid(nameEl, 'Give this ' + (_editTarget.type === 'category' ? 'group' : 'command') + ' a name.');
|
|
328
222
|
if (_editTarget.type === 'category') {
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
if (!name) { nameEl?.focus(); return; }
|
|
333
|
-
const color = colorEl?.value || COLOR_PALETTE[0];
|
|
334
|
-
|
|
335
|
-
if (_editTarget.id) {
|
|
336
|
-
// Update existing
|
|
337
|
-
const cat = _data.categories.find(c => c.id === _editTarget.id);
|
|
338
|
-
if (cat) { cat.name = name; cat.color = color; }
|
|
339
|
-
} else {
|
|
340
|
-
// Create new
|
|
341
|
-
_data.categories.push({
|
|
342
|
-
id: genId('cat'),
|
|
343
|
-
name,
|
|
344
|
-
color,
|
|
345
|
-
collapsed: false,
|
|
346
|
-
order: _data.categories.length,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
223
|
+
const selectedColor = $('cr-cat-color-val').value || COLOR_PALETTE[0];
|
|
224
|
+
if (_editTarget.id) { const cat = _data.categories.find(c => c.id === _editTarget.id); if (cat) Object.assign(cat, { name, color: selectedColor }); }
|
|
225
|
+
else _data.categories.push({ id: genId('cat'), name, color: selectedColor, collapsed: false, order: _data.categories.length });
|
|
349
226
|
} else {
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
const cwd = cwdEl?.value.trim() || null;
|
|
359
|
-
|
|
360
|
-
if (!categoryId) { catEl?.focus(); return; }
|
|
361
|
-
if (!name) { nameEl?.focus(); return; }
|
|
362
|
-
if (!command) { cmdEl?.focus(); return; }
|
|
363
|
-
|
|
364
|
-
if (_editTarget.id) {
|
|
365
|
-
// Update existing
|
|
366
|
-
const cmd = _data.commands.find(c => c.id === _editTarget.id);
|
|
367
|
-
if (cmd) {
|
|
368
|
-
cmd.categoryId = categoryId;
|
|
369
|
-
cmd.name = name;
|
|
370
|
-
cmd.command = command;
|
|
371
|
-
cmd.cwd = cwd;
|
|
372
|
-
}
|
|
373
|
-
} else {
|
|
374
|
-
// Create new
|
|
375
|
-
const sibling = getCategoryCommands(categoryId);
|
|
376
|
-
_data.commands.push({
|
|
377
|
-
id: genId('cmd'),
|
|
378
|
-
categoryId,
|
|
379
|
-
name,
|
|
380
|
-
command,
|
|
381
|
-
cwd,
|
|
382
|
-
order: sibling.length,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
227
|
+
const categoryId = $('cr-cmd-cat').value, command = $('cr-cmd-command').value.trim(), cwd = $('cr-cmd-cwd').value.trim() || null;
|
|
228
|
+
if (!categoryId) return invalid($('cr-cmd-cat'), 'Choose a group for this command.');
|
|
229
|
+
if (!command) return invalid($('cr-cmd-command'), 'Enter the command you want to save.');
|
|
230
|
+
if (_editTarget.id) { const cmd = _data.commands.find(c => c.id === _editTarget.id); if (cmd) Object.assign(cmd, { categoryId, name, command, cwd }); }
|
|
231
|
+
else _data.commands.push({ id: genId('cmd'), categoryId, name, command, cwd, order: _data.commands.filter(c => c.categoryId === categoryId).length });
|
|
232
|
+
_view.categoryId = categoryId;
|
|
233
|
+
const category = _data.categories.find(c => c.id === categoryId);
|
|
234
|
+
if (category) category.collapsed = false;
|
|
385
235
|
}
|
|
386
|
-
|
|
387
|
-
saveData();
|
|
388
|
-
closeModal();
|
|
389
|
-
renderBody();
|
|
236
|
+
_view.query = ''; $('cr-search').value = ''; // Reveal the saved entry after editing.
|
|
237
|
+
saveData(); closeModal(); renderBody(); setStatus('Saved to your command library.');
|
|
390
238
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
_data.commands = _data.commands.filter(c => c.id !== id);
|
|
394
|
-
|
|
395
|
-
renderBody();
|
|
239
|
+
function confirmDelete() {
|
|
240
|
+
const { type, id } = _editTarget;
|
|
241
|
+
_data.commands = _data.commands.filter(c => type === 'delete-category' ? c.categoryId !== id : c.id !== id);
|
|
242
|
+
if (type === 'delete-category') _data.categories = _data.categories.filter(c => c.id !== id);
|
|
243
|
+
saveData(); closeModal(); renderBody(); $('cr-search')?.focus();
|
|
244
|
+
setStatus(type === 'delete-category' ? 'Group deleted.' : 'Command deleted.');
|
|
396
245
|
}
|
|
397
|
-
|
|
398
|
-
function
|
|
399
|
-
const
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
renderBody();
|
|
246
|
+
function setStatus(text) { $('cr-results').textContent = text; }
|
|
247
|
+
function runCommand(id) {
|
|
248
|
+
const cmd = _data.commands.find(c => c.id === id);
|
|
249
|
+
if (!cmd || !canRun()) return;
|
|
250
|
+
emit('terminal:run-command', commandLaunchPayload(cmd));
|
|
251
|
+
cmd.lastRunAt = Date.now(); saveData(); _launchedId = id;
|
|
252
|
+
renderBody(); setStatus(`Sent “${cmd.name}” to a new terminal.`);
|
|
253
|
+
clearTimeout(_launchTimer);
|
|
254
|
+
_launchTimer = setTimeout(() => { if (!_panel) return; _launchedId = null; renderBody(); }, 2500);
|
|
407
255
|
}
|
|
408
|
-
|
|
409
|
-
function toggleCategoryCollapse(catId) {
|
|
410
|
-
const cat = _data.categories.find(c => c.id === catId);
|
|
411
|
-
if (cat) {
|
|
412
|
-
cat.collapsed = !cat.collapsed;
|
|
413
|
-
saveData();
|
|
414
|
-
renderBody();
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// ═══════════════════════════════════════════
|
|
419
|
-
// RUN COMMAND
|
|
420
|
-
// ═══════════════════════════════════════════
|
|
421
|
-
|
|
422
|
-
function runCommand(cmdId) {
|
|
423
|
-
const cmd = _data.commands.find(c => c.id === cmdId);
|
|
424
|
-
if (!cmd) return;
|
|
425
|
-
emit('terminal:run-command', {
|
|
426
|
-
command: cmd.command,
|
|
427
|
-
cwd: cmd.cwd || null,
|
|
428
|
-
label: cmd.name,
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// ═══════════════════════════════════════════
|
|
433
|
-
// EVENT WIRING
|
|
434
|
-
// ═══════════════════════════════════════════
|
|
256
|
+
function resetFilters() { _view.query = ''; _view.categoryId = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); }
|
|
435
257
|
|
|
436
258
|
function wirePanel() {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
259
|
+
_panel.addEventListener('input', event => {
|
|
260
|
+
if (event.target.id === 'cr-search') { _view.query = event.target.value; _searchCollapsed.clear(); renderBody(); }
|
|
261
|
+
else if (event.target.closest('#cr-form')) { event.target.removeAttribute('aria-invalid'); if ($('cr-form-error')) $('cr-form-error').hidden = true; }
|
|
262
|
+
});
|
|
263
|
+
_panel.addEventListener('change', event => { if (event.target.id === 'cr-sort') { _view.sort = event.target.value; renderBody(); } });
|
|
264
|
+
_panel.addEventListener('submit', event => { if (event.target.id !== 'cr-form') return; event.preventDefault(); saveFromModal(); });
|
|
265
|
+
_panel.addEventListener('click', event => {
|
|
266
|
+
const button = event.target.closest('button');
|
|
267
|
+
if (!button || button.disabled) return;
|
|
268
|
+
if (button.id === 'cr-close-btn') return closePanel();
|
|
269
|
+
if (button.id === 'cr-clear-search') { _view.query = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); return; }
|
|
270
|
+
if (button.id === 'cr-add-cat-btn' || button.dataset.action === 'first-group') return openModal('category', null, '', button);
|
|
271
|
+
if (button.id === 'cr-add-cmd-btn') return openModal(_data.categories.length ? 'command' : 'category', null, '', button);
|
|
272
|
+
if (button.id === 'cr-modal-close' || button.id === 'cr-modal-cancel') return closeModal();
|
|
273
|
+
if (button.hasAttribute('data-filter')) { _view.categoryId = button.dataset.filter; renderBody(); return; }
|
|
274
|
+
const action = button.dataset.action, catId = button.closest('.cr-category')?.dataset.catId, cmdId = button.closest('.cr-cmd')?.dataset.cmdId;
|
|
275
|
+
if (action === 'run') return runCommand(cmdId);
|
|
276
|
+
if (action === 'add-in-group') return openModal('command', null, catId, button);
|
|
277
|
+
if (action === 'edit-cat') return openModal('category', catId, '', button);
|
|
278
|
+
if (action === 'delete-cat') return openModal('delete-category', catId, '', button);
|
|
279
|
+
if (action === 'edit-cmd') return openModal('command', cmdId, '', button);
|
|
280
|
+
if (action === 'delete-cmd') return openModal('delete-command', cmdId, '', button);
|
|
281
|
+
if (action === 'confirm-delete') return confirmDelete();
|
|
282
|
+
if (action === 'collapse') {
|
|
283
|
+
const cat = _data.categories.find(c => c.id === catId);
|
|
284
|
+
if (cat) {
|
|
285
|
+
if (_view.query.trim()) {
|
|
286
|
+
if (_searchCollapsed.has(catId)) _searchCollapsed.delete(catId); else _searchCollapsed.add(catId);
|
|
287
|
+
} else { cat.collapsed = !cat.collapsed; saveData(); }
|
|
288
|
+
renderBody();
|
|
452
289
|
}
|
|
453
|
-
return;
|
|
454
290
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (cmdEl) return runCommand(cmdEl.dataset.cmdId);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Category actions
|
|
464
|
-
const editCat = e.target.closest('[data-action="edit-cat"]');
|
|
465
|
-
if (editCat) {
|
|
466
|
-
const catEl = editCat.closest('.cr-category');
|
|
467
|
-
if (catEl) return openModal('category', catEl.dataset.catId);
|
|
468
|
-
}
|
|
469
|
-
const delCat = e.target.closest('[data-action="delete-cat"]');
|
|
470
|
-
if (delCat) {
|
|
471
|
-
const catEl = delCat.closest('.cr-category');
|
|
472
|
-
if (catEl) return deleteCategory(catEl.dataset.catId);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Command actions
|
|
476
|
-
const editCmd = e.target.closest('[data-action="edit-cmd"]');
|
|
477
|
-
if (editCmd) {
|
|
478
|
-
const cmdEl = editCmd.closest('.cr-cmd');
|
|
479
|
-
if (cmdEl) return openModal('command', cmdEl.dataset.cmdId);
|
|
480
|
-
}
|
|
481
|
-
const delCmd = e.target.closest('[data-action="delete-cmd"]');
|
|
482
|
-
if (delCmd) {
|
|
483
|
-
const cmdEl = delCmd.closest('.cr-cmd');
|
|
484
|
-
if (cmdEl) return deleteCommand(cmdEl.dataset.cmdId);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Category header click → toggle collapse (but not if clicking actions)
|
|
488
|
-
const catHeader = e.target.closest('.cr-cat-header');
|
|
489
|
-
if (catHeader && !e.target.closest('.cr-cat-actions')) {
|
|
490
|
-
const catEl = catHeader.closest('.cr-category');
|
|
491
|
-
if (catEl) return toggleCategoryCollapse(catEl.dataset.catId);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Modal buttons
|
|
495
|
-
if (e.target.closest('#cr-modal-close') || e.target.closest('#cr-modal-cancel')) return closeModal();
|
|
496
|
-
if (e.target.closest('#cr-modal-save')) return saveFromModal();
|
|
497
|
-
|
|
498
|
-
// Color swatch selection
|
|
499
|
-
const swatch = e.target.closest('.cr-color-swatch');
|
|
500
|
-
if (swatch) {
|
|
501
|
-
const picker = swatch.closest('.cr-color-picker');
|
|
502
|
-
if (picker) {
|
|
503
|
-
picker.querySelectorAll('.cr-color-swatch').forEach(s => s.classList.remove('selected'));
|
|
504
|
-
swatch.classList.add('selected');
|
|
505
|
-
const hidden = $('cr-cat-color-val');
|
|
506
|
-
if (hidden) hidden.value = swatch.dataset.color;
|
|
507
|
-
}
|
|
508
|
-
return;
|
|
291
|
+
if (action === 'reset-filters') return resetFilters();
|
|
292
|
+
if (action === 'retry') { loadData(); renderBody(); return; }
|
|
293
|
+
if (button.dataset.color) {
|
|
294
|
+
$('cr-cat-color').querySelectorAll('button').forEach(swatch => { const selected = swatch === button; swatch.classList.toggle('selected', selected); swatch.setAttribute('aria-pressed', String(selected)); });
|
|
295
|
+
$('cr-cat-color-val').value = button.dataset.color;
|
|
509
296
|
}
|
|
510
297
|
});
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
// ── Enter key submits modal form ──
|
|
521
|
-
_panel.addEventListener('keydown', (e) => {
|
|
522
|
-
if (e.key === 'Enter' && _editTarget) {
|
|
523
|
-
e.preventDefault();
|
|
524
|
-
saveFromModal();
|
|
298
|
+
$('cr-modal-overlay').addEventListener('click', event => { if (event.target.id === 'cr-modal-overlay') closeModal(); });
|
|
299
|
+
_panel.addEventListener('keydown', event => {
|
|
300
|
+
const editing = event.target.closest('input, textarea, select, [contenteditable]');
|
|
301
|
+
if (_editTarget && event.key === 'Tab') {
|
|
302
|
+
const focusable = [...$('cr-modal').querySelectorAll('button:not(:disabled), input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled)')];
|
|
303
|
+
// Explicit traversal includes buttons even when Safari skips them by default.
|
|
304
|
+
const index = focusable.indexOf(document.activeElement);
|
|
305
|
+
const next = index < 0 ? (event.shiftKey ? focusable.length - 1 : 0) : (index + (event.shiftKey ? -1 : 1) + focusable.length) % focusable.length;
|
|
306
|
+
event.preventDefault(); focusable[next]?.focus();
|
|
525
307
|
}
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
308
|
+
if (event.key === 'Escape') {
|
|
309
|
+
event.preventDefault(); event.stopPropagation();
|
|
310
|
+
if (_editTarget) closeModal();
|
|
311
|
+
else if (_view.query) { _view.query = ''; $('cr-search').value = ''; renderBody(); $('cr-search').focus(); }
|
|
312
|
+
else closePanel();
|
|
532
313
|
}
|
|
314
|
+
if (!_editTarget && ((!editing && event.key === '/') || ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'f'))) { event.preventDefault(); event.stopPropagation(); $('cr-search').focus(); }
|
|
315
|
+
if (_editTarget && (event.ctrlKey || event.metaKey) && event.key === 'Enter' && $('cr-form')) { event.preventDefault(); saveFromModal(); }
|
|
533
316
|
});
|
|
534
317
|
}
|