@zuzuucodes/cli 1.0.1 → 1.2.0
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/README.md +6 -4
- package/bin/zuzuu-web.mjs +15 -0
- package/bin/zuzuu.mjs +6 -3
- package/package.json +17 -4
- package/web-app/dist/browse.js +48 -0
- package/web-app/dist/config.js +52 -0
- package/web-app/dist/file-list.js +87 -0
- package/web-app/dist/frames.js +16 -0
- package/web-app/dist/fs-api.js +226 -0
- package/web-app/dist/git.js +73 -0
- package/web-app/dist/history.js +45 -0
- package/web-app/dist/index.js +160 -0
- package/web-app/dist/protocol/index.js +53 -0
- package/web-app/dist/protocol/zuzuu.js +2 -0
- package/web-app/dist/safe-path.js +60 -0
- package/web-app/dist/search.js +145 -0
- package/web-app/dist/server.js +407 -0
- package/web-app/dist/sessions.js +334 -0
- package/web-app/dist/shell-integration/inject.js +70 -0
- package/web-app/dist/shell-integration/webcode.bash +41 -0
- package/web-app/dist/shell-integration/webcode.fish +28 -0
- package/web-app/dist/shell-integration/webcode.zsh +35 -0
- package/web-app/dist/workflows.js +48 -0
- package/web-app/dist/ws-fs.js +69 -0
- package/web-app/dist/ws-term.js +42 -0
- package/web-app/dist/zuzuu-api.js +348 -0
- package/web-app/package.json +6 -0
- package/web-app/web-dist/assets/DiffTab-NYcg0Jn6.js +1 -0
- package/web-app/web-dist/assets/MonacoFile-C_nBCUL-.js +1 -0
- package/web-app/web-dist/assets/abap-08VXUWAP.js +1 -0
- package/web-app/web-dist/assets/abap-CLvhMVsD.js +1 -0
- package/web-app/web-dist/assets/actionscript-3--17pq3dv.js +1 -0
- package/web-app/web-dist/assets/ada-C5qYipkI.js +1 -0
- package/web-app/web-dist/assets/andromeeda-vGVdxbeo.js +1 -0
- package/web-app/web-dist/assets/angular-html-CmT26mqM.js +1 -0
- package/web-app/web-dist/assets/angular-ts-CD_OonCa.js +1 -0
- package/web-app/web-dist/assets/apache-U0d_L8uA.js +1 -0
- package/web-app/web-dist/assets/apex-BWPQTe0t.js +1 -0
- package/web-app/web-dist/assets/apex-VAyPSnFM.js +1 -0
- package/web-app/web-dist/assets/apl-uOGC3x4e.js +1 -0
- package/web-app/web-dist/assets/applescript-CCn79oCD.js +1 -0
- package/web-app/web-dist/assets/ara-4CJ0cIlV.js +1 -0
- package/web-app/web-dist/assets/asciidoc-DE70LPWp.js +1 -0
- package/web-app/web-dist/assets/asciinema-player-CZa1Uf8p.css +1 -0
- package/web-app/web-dist/assets/asm-Cmm7eHzH.js +1 -0
- package/web-app/web-dist/assets/astro-B6ybQmWG.js +1 -0
- package/web-app/web-dist/assets/aurora-x-CDeNXAV0.js +1 -0
- package/web-app/web-dist/assets/awk-BWXHIvNe.js +1 -0
- package/web-app/web-dist/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/web-app/web-dist/assets/ayu-light-C3h-C4tm.js +1 -0
- package/web-app/web-dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/web-app/web-dist/assets/azcli-Bc_sGQ0U.js +1 -0
- package/web-app/web-dist/assets/ballerina-B7ZEbQpA.js +1 -0
- package/web-app/web-dist/assets/bat-Bo4NYOV-.js +1 -0
- package/web-app/web-dist/assets/bat-i0X4ZdIN.js +1 -0
- package/web-app/web-dist/assets/beancount-D-usSTwE.js +1 -0
- package/web-app/web-dist/assets/berry-DKpUyyne.js +1 -0
- package/web-app/web-dist/assets/bibtex-Ci_nEsc7.js +1 -0
- package/web-app/web-dist/assets/bicep-B5-_aFwp.js +2 -0
- package/web-app/web-dist/assets/bicep-CUHmPFLl.js +1 -0
- package/web-app/web-dist/assets/bird2-C6vDhewU.js +1 -0
- package/web-app/web-dist/assets/blade-B1QGRlVx.js +1 -0
- package/web-app/web-dist/assets/bsl-BkkzgIyY.js +1 -0
- package/web-app/web-dist/assets/c-BvoqrSVH.js +1 -0
- package/web-app/web-dist/assets/c3-BFHwR3_K.js +1 -0
- package/web-app/web-dist/assets/cadence-CQ2zXKGN.js +1 -0
- package/web-app/web-dist/assets/cairo-DLTphjLi.js +1 -0
- package/web-app/web-dist/assets/cameligo-DMUM7wLl.js +1 -0
- package/web-app/web-dist/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
- package/web-app/web-dist/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
- package/web-app/web-dist/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
- package/web-app/web-dist/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
- package/web-app/web-dist/assets/chunk-QTnfLwEv.js +1 -0
- package/web-app/web-dist/assets/clarity-SemFz856.js +1 -0
- package/web-app/web-dist/assets/clojure-Cm7r79vr.js +1 -0
- package/web-app/web-dist/assets/clojure-DqKBuwfJ.js +1 -0
- package/web-app/web-dist/assets/cmake-Bj61d0ZC.js +1 -0
- package/web-app/web-dist/assets/cobol-BgqgtYWn.js +1 -0
- package/web-app/web-dist/assets/codeowners-C8r90Shi.js +1 -0
- package/web-app/web-dist/assets/codeql-oeQT6MSM.js +1 -0
- package/web-app/web-dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/web-app/web-dist/assets/coffee-0wIRKYlr.js +1 -0
- package/web-app/web-dist/assets/coffee-Ba7i2nA0.js +1 -0
- package/web-app/web-dist/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/web-app/web-dist/assets/coq-BrsZFFmf.js +1 -0
- package/web-app/web-dist/assets/cpp-BXsk94m0.js +1 -0
- package/web-app/web-dist/assets/cpp-C7h46wYY.js +1 -0
- package/web-app/web-dist/assets/crystal-CyTK3qFN.js +1 -0
- package/web-app/web-dist/assets/csharp-BKxtCVv1.js +1 -0
- package/web-app/web-dist/assets/csharp-oqKa8noW.js +1 -0
- package/web-app/web-dist/assets/csp-bTuwJoIa.js +1 -0
- package/web-app/web-dist/assets/css-DIMkf-bt.js +3 -0
- package/web-app/web-dist/assets/css-Z8oOGxII.js +1 -0
- package/web-app/web-dist/assets/css.worker-CvXBzhp8.js +89 -0
- package/web-app/web-dist/assets/cssMode-BXZOx0fG.js +1 -0
- package/web-app/web-dist/assets/csv-Dx-8-gkx.js +1 -0
- package/web-app/web-dist/assets/cue-CE9AQfxI.js +1 -0
- package/web-app/web-dist/assets/cypher-CVaqCwHa.js +1 -0
- package/web-app/web-dist/assets/cypher-ClKdZ_lG.js +1 -0
- package/web-app/web-dist/assets/d-qD-0Kul2.js +1 -0
- package/web-app/web-dist/assets/dark-plus-Cs2F2srj.js +1 -0
- package/web-app/web-dist/assets/dart-CnvKMtbv.js +1 -0
- package/web-app/web-dist/assets/dart-onAF5SnQ.js +1 -0
- package/web-app/web-dist/assets/dax-BkyTk9wS.js +1 -0
- package/web-app/web-dist/assets/desktop-Dlh5hvp9.js +1 -0
- package/web-app/web-dist/assets/diff-woXpYk--.js +1 -0
- package/web-app/web-dist/assets/dist-Cj6v7Kj8.js +153 -0
- package/web-app/web-dist/assets/dist-DNelLewU.js +4 -0
- package/web-app/web-dist/assets/docker-IyjqRm3v.js +1 -0
- package/web-app/web-dist/assets/dockerfile-DZFCIeNp.js +1 -0
- package/web-app/web-dist/assets/dotenv-_5a1GRtc.js +1 -0
- package/web-app/web-dist/assets/dracula-BHWKrbxM.js +1 -0
- package/web-app/web-dist/assets/dracula-soft-5eyTD99u.js +1 -0
- package/web-app/web-dist/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/web-app/web-dist/assets/ecl-D05T4iGw.js +1 -0
- package/web-app/web-dist/assets/edge-CvML9pwC.js +1 -0
- package/web-app/web-dist/assets/editor-Br_kD0ds.css +1 -0
- package/web-app/web-dist/assets/editor.api2-BmGoRSl4.js +872 -0
- package/web-app/web-dist/assets/editor.worker-Cn2oRESe.js +26 -0
- package/web-app/web-dist/assets/elixir-6RTg0lbw.js +1 -0
- package/web-app/web-dist/assets/elixir-CrjqTiSc.js +1 -0
- package/web-app/web-dist/assets/elm-C4JtJ0Au.js +1 -0
- package/web-app/web-dist/assets/emacs-lisp-B4R74twV.js +1 -0
- package/web-app/web-dist/assets/erb-Cmeb-29V.js +1 -0
- package/web-app/web-dist/assets/erlang-Cphh6RMH.js +1 -0
- package/web-app/web-dist/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/web-app/web-dist/assets/everforest-light-Df2xbC6M.js +1 -0
- package/web-app/web-dist/assets/fennel-DQxkIbk2.js +1 -0
- package/web-app/web-dist/assets/fish-BJitypiv.js +1 -0
- package/web-app/web-dist/assets/flow9-C5_-GSwl.js +1 -0
- package/web-app/web-dist/assets/fluent-C03EYrpw.js +1 -0
- package/web-app/web-dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/web-app/web-dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/web-app/web-dist/assets/freemarker2-B5LAi19B.js +3 -0
- package/web-app/web-dist/assets/fsharp-C8Ef5oNN.js +1 -0
- package/web-app/web-dist/assets/fsharp-D13ZGOAj.js +1 -0
- package/web-app/web-dist/assets/gdresource-C0sCabJj.js +1 -0
- package/web-app/web-dist/assets/gdscript-Cp2uCuqX.js +1 -0
- package/web-app/web-dist/assets/gdshader-CBce3t8t.js +1 -0
- package/web-app/web-dist/assets/genie-CV2tkWYe.js +1 -0
- package/web-app/web-dist/assets/gherkin-DExj1W_8.js +1 -0
- package/web-app/web-dist/assets/git-commit-BSykSTBG.js +1 -0
- package/web-app/web-dist/assets/git-rebase-CXqdToiP.js +1 -0
- package/web-app/web-dist/assets/github-dark-C-LZuMrd.js +1 -0
- package/web-app/web-dist/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/web-app/web-dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/web-app/web-dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/web-app/web-dist/assets/github-light-EUqPIrTm.js +1 -0
- package/web-app/web-dist/assets/github-light-default-BXViO-2h.js +1 -0
- package/web-app/web-dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/web-app/web-dist/assets/gleam-CSRkHgEL.js +1 -0
- package/web-app/web-dist/assets/glimmer-js-Kq-kdTyV.js +1 -0
- package/web-app/web-dist/assets/glimmer-ts-D0RKLJNf.js +1 -0
- package/web-app/web-dist/assets/glsl-KwyfU2aa.js +1 -0
- package/web-app/web-dist/assets/gn-ilITqXS6.js +1 -0
- package/web-app/web-dist/assets/gnuplot-7GGW24-e.js +1 -0
- package/web-app/web-dist/assets/go-C-y9NEjX.js +1 -0
- package/web-app/web-dist/assets/go-rLFTqkRN.js +1 -0
- package/web-app/web-dist/assets/graphql-DSeOUAa2.js +1 -0
- package/web-app/web-dist/assets/graphql-fmXr3nnJ.js +1 -0
- package/web-app/web-dist/assets/groovy-CacY0gHj.js +1 -0
- package/web-app/web-dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/web-app/web-dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/web-app/web-dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/web-app/web-dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/web-app/web-dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/web-app/web-dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/web-app/web-dist/assets/hack-trjVF3Po.js +1 -0
- package/web-app/web-dist/assets/haml-azVoxQRV.js +1 -0
- package/web-app/web-dist/assets/handlebars-B8_x7Zx7.js +1 -0
- package/web-app/web-dist/assets/handlebars-g7ZhGhI_.js +1 -0
- package/web-app/web-dist/assets/haskell-D8IpX4py.js +1 -0
- package/web-app/web-dist/assets/haxe-OTjmBuCE.js +1 -0
- package/web-app/web-dist/assets/hcl-CpzslTdj.js +1 -0
- package/web-app/web-dist/assets/hcl-Dh228itO.js +1 -0
- package/web-app/web-dist/assets/hjson-CxZEssPk.js +1 -0
- package/web-app/web-dist/assets/hlsl-Cvrh5tZx.js +1 -0
- package/web-app/web-dist/assets/horizon-CE9ld1lL.js +1 -0
- package/web-app/web-dist/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/web-app/web-dist/assets/houston-CsvMBhTu.js +1 -0
- package/web-app/web-dist/assets/html-CfvRMgoC.js +1 -0
- package/web-app/web-dist/assets/html-D_7P5S4m.js +1 -0
- package/web-app/web-dist/assets/html-derivative-BYX_F_XH.js +1 -0
- package/web-app/web-dist/assets/html.worker-BO6WuOEO.js +502 -0
- package/web-app/web-dist/assets/htmlMode-CScDVtoR.js +1 -0
- package/web-app/web-dist/assets/http-BIVDpHT-.js +1 -0
- package/web-app/web-dist/assets/hurl-CFsshMju.js +1 -0
- package/web-app/web-dist/assets/hxml-B0Qn7Nwc.js +1 -0
- package/web-app/web-dist/assets/hy-CZbG8q4J.js +1 -0
- package/web-app/web-dist/assets/imba-DsUTQ-LC.js +1 -0
- package/web-app/web-dist/assets/index--lqPz7z2.css +2 -0
- package/web-app/web-dist/assets/index-BTchnWPZ.js +270 -0
- package/web-app/web-dist/assets/ini-B5eOa1yu.js +1 -0
- package/web-app/web-dist/assets/ini-sBoK_t0W.js +1 -0
- package/web-app/web-dist/assets/java-BEtHBSE6.js +1 -0
- package/web-app/web-dist/assets/java-D4RbCvBe.js +1 -0
- package/web-app/web-dist/assets/javascript-Bxx2wV4w.js +1 -0
- package/web-app/web-dist/assets/javascript-Cb010CKM.js +1 -0
- package/web-app/web-dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/web-app/web-dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/web-app/web-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/web-app/web-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/web-app/web-dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/web-app/web-dist/assets/jinja-_ZS5zWwe.js +1 -0
- package/web-app/web-dist/assets/jison-D8mMEpcs.js +1 -0
- package/web-app/web-dist/assets/json-DWgqV4D1.js +1 -0
- package/web-app/web-dist/assets/json.worker-BkJRGcCJ.js +58 -0
- package/web-app/web-dist/assets/json5-BR5RXkoi.js +1 -0
- package/web-app/web-dist/assets/jsonMode-BMZXTtiq.js +7 -0
- package/web-app/web-dist/assets/jsonc-CYpm1nAK.js +1 -0
- package/web-app/web-dist/assets/jsonl-CmCQp5Yx.js +1 -0
- package/web-app/web-dist/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/web-app/web-dist/assets/jssm-DXw9l8Rf.js +1 -0
- package/web-app/web-dist/assets/jsx-CZjSJa1f.js +1 -0
- package/web-app/web-dist/assets/julia-Bri6UV-V.js +1 -0
- package/web-app/web-dist/assets/julia-D4h2DZrs.js +1 -0
- package/web-app/web-dist/assets/just-bMqQi3xg.js +1 -0
- package/web-app/web-dist/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
- package/web-app/web-dist/assets/kanagawa-lotus-BN08jTvb.js +1 -0
- package/web-app/web-dist/assets/kanagawa-wave-CTweb8Dz.js +1 -0
- package/web-app/web-dist/assets/kdl-CsD5j6eV.js +1 -0
- package/web-app/web-dist/assets/kotlin-BOotOW0E.js +1 -0
- package/web-app/web-dist/assets/kotlin-DhhofPvG.js +1 -0
- package/web-app/web-dist/assets/kusto-C7mF5XQf.js +1 -0
- package/web-app/web-dist/assets/laserwave-C_8bwKvT.js +1 -0
- package/web-app/web-dist/assets/latex-DThYi3CX.js +1 -0
- package/web-app/web-dist/assets/lean-CewbzKMR.js +1 -0
- package/web-app/web-dist/assets/less-B9JPFI3C.js +2 -0
- package/web-app/web-dist/assets/less-DVTAwKKz.js +1 -0
- package/web-app/web-dist/assets/lexon-CfSJPG6W.js +1 -0
- package/web-app/web-dist/assets/light-plus-DVQuIRkW.js +1 -0
- package/web-app/web-dist/assets/liquid-CUjzzP4r.js +1 -0
- package/web-app/web-dist/assets/liquid-CesB-zzl.js +1 -0
- package/web-app/web-dist/assets/llvm-Cm23YOpf.js +1 -0
- package/web-app/web-dist/assets/log-BNLmms1o.js +1 -0
- package/web-app/web-dist/assets/logo-Cluzi2Zq.js +1 -0
- package/web-app/web-dist/assets/lspLanguageFeatures-gTnJsses.js +4 -0
- package/web-app/web-dist/assets/lua-CsQS60Ue.js +1 -0
- package/web-app/web-dist/assets/lua-TGj_6NzO.js +1 -0
- package/web-app/web-dist/assets/luau-FMPmPwt6.js +1 -0
- package/web-app/web-dist/assets/m3-D-oSqn_W.js +1 -0
- package/web-app/web-dist/assets/make-Dixweg8N.js +1 -0
- package/web-app/web-dist/assets/markdown-BYOwaDjH.js +1 -0
- package/web-app/web-dist/assets/markdown-Cimd5fb3.js +1 -0
- package/web-app/web-dist/assets/marko-yoGoLK2m.js +1 -0
- package/web-app/web-dist/assets/material-theme-Bm3Qr25_.js +1 -0
- package/web-app/web-dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/web-app/web-dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/web-app/web-dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/web-app/web-dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/web-app/web-dist/assets/matlab-D7qyCx1q.js +1 -0
- package/web-app/web-dist/assets/mdc-BvtXU6eH.js +1 -0
- package/web-app/web-dist/assets/mdx-DQZ5AkYe.js +1 -0
- package/web-app/web-dist/assets/mdx-DrXGQbNB.js +1 -0
- package/web-app/web-dist/assets/mermaid-Bk4SNUv9.js +1 -0
- package/web-app/web-dist/assets/min-dark-BSWPekZh.js +1 -0
- package/web-app/web-dist/assets/min-light-DDpmG2fV.js +1 -0
- package/web-app/web-dist/assets/mips-CIPQ_RoX.js +1 -0
- package/web-app/web-dist/assets/mipsasm-BMqwQI7S.js +1 -0
- package/web-app/web-dist/assets/mojo-BgCJLMeH.js +1 -0
- package/web-app/web-dist/assets/monaco-setup-DAY1k6DB.js +16 -0
- package/web-app/web-dist/assets/monokai-CdkpiU2Y.js +1 -0
- package/web-app/web-dist/assets/moonbit-CaWjb8XO.js +1 -0
- package/web-app/web-dist/assets/move-B1IS1UjX.js +1 -0
- package/web-app/web-dist/assets/msdax-DauUninz.js +1 -0
- package/web-app/web-dist/assets/mysql-SOo6toE5.js +1 -0
- package/web-app/web-dist/assets/narrat-_X_XdTYD.js +1 -0
- package/web-app/web-dist/assets/nextflow-BJtWHP5T.js +1 -0
- package/web-app/web-dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
- package/web-app/web-dist/assets/nginx-DoUz032F.js +1 -0
- package/web-app/web-dist/assets/night-owl-DhmEMT88.js +1 -0
- package/web-app/web-dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/web-app/web-dist/assets/nim-B0Pl8B4R.js +1 -0
- package/web-app/web-dist/assets/nix-IvuFDN5E.js +1 -0
- package/web-app/web-dist/assets/nord-Cb4Vim4T.js +1 -0
- package/web-app/web-dist/assets/nushell-DcLAeLz5.js +1 -0
- package/web-app/web-dist/assets/objective-c-D1A_Heim.js +1 -0
- package/web-app/web-dist/assets/objective-c-FvmIjYaQ.js +1 -0
- package/web-app/web-dist/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/web-app/web-dist/assets/ocaml-O90oeIOV.js +1 -0
- package/web-app/web-dist/assets/odin-B1RWQWA5.js +1 -0
- package/web-app/web-dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/web-app/web-dist/assets/one-light-D7Lr4KcI.js +1 -0
- package/web-app/web-dist/assets/openscad-BUDT5pXO.js +1 -0
- package/web-app/web-dist/assets/pascal-4ZHwLPI5.js +1 -0
- package/web-app/web-dist/assets/pascal-DrH0SRf2.js +1 -0
- package/web-app/web-dist/assets/pascaligo-D-ptJ9y-.js +1 -0
- package/web-app/web-dist/assets/perl-D2tfAALb.js +1 -0
- package/web-app/web-dist/assets/perl-oz_6vUea.js +1 -0
- package/web-app/web-dist/assets/pgsql-DTj74zXo.js +1 -0
- package/web-app/web-dist/assets/php-BImCcX5X.js +1 -0
- package/web-app/web-dist/assets/php-nr791fC2.js +1 -0
- package/web-app/web-dist/assets/pkl-ot-7Btpt.js +1 -0
- package/web-app/web-dist/assets/pla-CopQ2nXW.js +1 -0
- package/web-app/web-dist/assets/plastic-DQwYfKfQ.js +1 -0
- package/web-app/web-dist/assets/plsql-DGHpHOYJ.js +1 -0
- package/web-app/web-dist/assets/po-BiJDBrnU.js +1 -0
- package/web-app/web-dist/assets/poimandres-DRFjx7u4.js +1 -0
- package/web-app/web-dist/assets/polar-C7UOKdEL.js +1 -0
- package/web-app/web-dist/assets/postcss-BXeXVLqQ.js +1 -0
- package/web-app/web-dist/assets/postiats-43DmfD33.js +1 -0
- package/web-app/web-dist/assets/powerquery-D3hlyOfw.js +1 -0
- package/web-app/web-dist/assets/powerquery-DNMTfnFr.js +1 -0
- package/web-app/web-dist/assets/powershell-DmHpPYUd.js +1 -0
- package/web-app/web-dist/assets/powershell-DshXNtvi.js +1 -0
- package/web-app/web-dist/assets/prisma-BsRQq5mF.js +1 -0
- package/web-app/web-dist/assets/prolog-iXnhIJG7.js +1 -0
- package/web-app/web-dist/assets/proto-DB4EqR-F.js +1 -0
- package/web-app/web-dist/assets/protobuf-C531GsRP.js +2 -0
- package/web-app/web-dist/assets/pug-BcnpC8P_.js +1 -0
- package/web-app/web-dist/assets/pug-Z5eAx3Zn.js +1 -0
- package/web-app/web-dist/assets/puppet-CDv2pdJW.js +1 -0
- package/web-app/web-dist/assets/purescript-9MfHhQsQ.js +1 -0
- package/web-app/web-dist/assets/python-gzcpVVnB.js +1 -0
- package/web-app/web-dist/assets/python-ypRCBnvu.js +1 -0
- package/web-app/web-dist/assets/qml-DFDAunHY.js +1 -0
- package/web-app/web-dist/assets/qmldir-DCQb3MpD.js +1 -0
- package/web-app/web-dist/assets/qsharp-DkqhCAOL.js +1 -0
- package/web-app/web-dist/assets/qss-Fe1Jh2GI.js +1 -0
- package/web-app/web-dist/assets/r-BwWrilGY.js +1 -0
- package/web-app/web-dist/assets/r-fCpuAR7u.js +1 -0
- package/web-app/web-dist/assets/racket-DcIDlBhZ.js +1 -0
- package/web-app/web-dist/assets/raku-B3gFvitq.js +1 -0
- package/web-app/web-dist/assets/razor-1_376SZM.js +1 -0
- package/web-app/web-dist/assets/razor-aqrhpwqZ.js +1 -0
- package/web-app/web-dist/assets/red-CJ3rzSJv.js +1 -0
- package/web-app/web-dist/assets/redis-ClamHrr6.js +1 -0
- package/web-app/web-dist/assets/redshift-DT7zqm-g.js +1 -0
- package/web-app/web-dist/assets/reg-CRGYupPL.js +1 -0
- package/web-app/web-dist/assets/regexp-B4yxx-Ty.js +1 -0
- package/web-app/web-dist/assets/rel-BtDbiS_P.js +1 -0
- package/web-app/web-dist/assets/restructuredtext-BYgofb2h.js +1 -0
- package/web-app/web-dist/assets/riscv-Ckw8ddFX.js +1 -0
- package/web-app/web-dist/assets/ron-VUp2lXgN.js +1 -0
- package/web-app/web-dist/assets/rose-pine-BthvhNj6.js +1 -0
- package/web-app/web-dist/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
- package/web-app/web-dist/assets/rose-pine-moon-hon4tzzS.js +1 -0
- package/web-app/web-dist/assets/rosmsg-CAekHB0j.js +1 -0
- package/web-app/web-dist/assets/rst-2vG6f11Y.js +1 -0
- package/web-app/web-dist/assets/ruby-DezsRK8O.js +1 -0
- package/web-app/web-dist/assets/ruby-Dj6bCFXR.js +1 -0
- package/web-app/web-dist/assets/rust-Cfkwpbl8.js +1 -0
- package/web-app/web-dist/assets/rust-DdL9SqIa.js +1 -0
- package/web-app/web-dist/assets/sas-BhVZ4qL2.js +1 -0
- package/web-app/web-dist/assets/sass-DXrisJhu.js +1 -0
- package/web-app/web-dist/assets/sb-CcwsVR0C.js +1 -0
- package/web-app/web-dist/assets/scala-DHpiXF5c.js +1 -0
- package/web-app/web-dist/assets/scala-DKOlJaKm.js +1 -0
- package/web-app/web-dist/assets/scheme-BeGwcela.js +1 -0
- package/web-app/web-dist/assets/scheme-DQCgrYNe.js +1 -0
- package/web-app/web-dist/assets/scss-QdjMO_xV.js +1 -0
- package/web-app/web-dist/assets/scss-gp-XZpBa.js +3 -0
- package/web-app/web-dist/assets/sdbl-bTVj8UrX.js +1 -0
- package/web-app/web-dist/assets/shaderlab-TOUzSsQk.js +1 -0
- package/web-app/web-dist/assets/shell-CC2rA5mh.js +1 -0
- package/web-app/web-dist/assets/shellscript-BnlgeVVx.js +1 -0
- package/web-app/web-dist/assets/shellsession-CyO2fnhB.js +1 -0
- package/web-app/web-dist/assets/slack-dark-DnToyrRv.js +1 -0
- package/web-app/web-dist/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/web-app/web-dist/assets/smalltalk-B16xEiuN.js +1 -0
- package/web-app/web-dist/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/web-app/web-dist/assets/solarized-dark-DV17i1UV.js +1 -0
- package/web-app/web-dist/assets/solarized-light-DSh2HLQt.js +1 -0
- package/web-app/web-dist/assets/solidity-BEEn4gHE.js +1 -0
- package/web-app/web-dist/assets/solidity-CKzVLygQ.js +1 -0
- package/web-app/web-dist/assets/sophia-CRfGWb83.js +1 -0
- package/web-app/web-dist/assets/soy-DIkw6E88.js +1 -0
- package/web-app/web-dist/assets/sparql-D_Lu-MrJ.js +1 -0
- package/web-app/web-dist/assets/sparql-D_iOobhT.js +1 -0
- package/web-app/web-dist/assets/splunk-BC2Px7Mm.js +1 -0
- package/web-app/web-dist/assets/sql-DGnQv6iD.js +1 -0
- package/web-app/web-dist/assets/sql-NEE52Syq.js +1 -0
- package/web-app/web-dist/assets/ssh-config-BgfXC-Er.js +1 -0
- package/web-app/web-dist/assets/st-DbInun42.js +1 -0
- package/web-app/web-dist/assets/stata-DvkM932O.js +1 -0
- package/web-app/web-dist/assets/stylus-B6D30XZt.js +1 -0
- package/web-app/web-dist/assets/surrealql-B4-Q8tqV.js +1 -0
- package/web-app/web-dist/assets/svelte-p6yBy-Ki.js +1 -0
- package/web-app/web-dist/assets/swift-Bxkupp3x.js +1 -0
- package/web-app/web-dist/assets/swift-DonLKvLd.js +1 -0
- package/web-app/web-dist/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/web-app/web-dist/assets/system-verilog-DJ5XKQeo.js +1 -0
- package/web-app/web-dist/assets/systemd-BxMlprV5.js +1 -0
- package/web-app/web-dist/assets/systemverilog-Bz4Y3fRF.js +1 -0
- package/web-app/web-dist/assets/talonscript-CohzipZa.js +1 -0
- package/web-app/web-dist/assets/tasl-DMoTqEGO.js +1 -0
- package/web-app/web-dist/assets/tcl-CZd0xW_V.js +1 -0
- package/web-app/web-dist/assets/tcl-DISqw1ZD.js +1 -0
- package/web-app/web-dist/assets/templ-C7EkuiZr.js +1 -0
- package/web-app/web-dist/assets/terraform-DswuEJGm.js +1 -0
- package/web-app/web-dist/assets/tex-DkmD8uFC.js +1 -0
- package/web-app/web-dist/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/web-app/web-dist/assets/toml-CcmNWLt0.js +1 -0
- package/web-app/web-dist/assets/ts-tags-U-hncHg4.js +1 -0
- package/web-app/web-dist/assets/ts.worker-B0J26iPs.js +67734 -0
- package/web-app/web-dist/assets/tsMode-D--bqr5b.js +11 -0
- package/web-app/web-dist/assets/tsv-sltzmVWM.js +1 -0
- package/web-app/web-dist/assets/tsx-MJ0-9sYG.js +1 -0
- package/web-app/web-dist/assets/turtle-ByJddavk.js +1 -0
- package/web-app/web-dist/assets/twig-CU0OP-IA.js +1 -0
- package/web-app/web-dist/assets/twig-De2hgUGE.js +1 -0
- package/web-app/web-dist/assets/typescript-C17ZkDe8.js +1 -0
- package/web-app/web-dist/assets/typescript-DnLjiKtn.js +1 -0
- package/web-app/web-dist/assets/typespec-B8J7ngcE.js +1 -0
- package/web-app/web-dist/assets/typespec-BRdr0IET.js +1 -0
- package/web-app/web-dist/assets/typst-DI99ib-x.js +1 -0
- package/web-app/web-dist/assets/v-DETTlOr0.js +1 -0
- package/web-app/web-dist/assets/vala-zf12oZj6.js +1 -0
- package/web-app/web-dist/assets/vb-DV3o63ZY.js +1 -0
- package/web-app/web-dist/assets/vb-Djn5o6TS.js +1 -0
- package/web-app/web-dist/assets/verilog-CiiDBU1e.js +1 -0
- package/web-app/web-dist/assets/vesper-DdrHHSXu.js +1 -0
- package/web-app/web-dist/assets/vhdl-BroJfC0k.js +1 -0
- package/web-app/web-dist/assets/viml-DvXPmvsu.js +1 -0
- package/web-app/web-dist/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/web-app/web-dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/web-app/web-dist/assets/vitesse-light-VbXTXTou.js +1 -0
- package/web-app/web-dist/assets/vue-Db7nY3ba.js +1 -0
- package/web-app/web-dist/assets/vue-html-BvAbiAw1.js +1 -0
- package/web-app/web-dist/assets/vue-vine-BEaIQIlA.js +1 -0
- package/web-app/web-dist/assets/vyper-CgoNMtux.js +1 -0
- package/web-app/web-dist/assets/wasm-BnjxR4X6.js +1 -0
- package/web-app/web-dist/assets/wasm-ByWQv1Qj.js +1 -0
- package/web-app/web-dist/assets/wenyan-C8pVoKbM.js +1 -0
- package/web-app/web-dist/assets/wgsl-BsKzXJz4.js +1 -0
- package/web-app/web-dist/assets/wgsl-DpFanUEy.js +298 -0
- package/web-app/web-dist/assets/wikitext-ClFFjSW2.js +1 -0
- package/web-app/web-dist/assets/wit-DdvCle-K.js +1 -0
- package/web-app/web-dist/assets/wolfram-DLL8P-h_.js +1 -0
- package/web-app/web-dist/assets/xml-CA9lHFQV.js +1 -0
- package/web-app/web-dist/assets/xml-an4Nuuqq.js +1 -0
- package/web-app/web-dist/assets/xsl-D3NQgH22.js +1 -0
- package/web-app/web-dist/assets/yaml-CwRYMJka.js +1 -0
- package/web-app/web-dist/assets/yaml-Diiu6O9P.js +1 -0
- package/web-app/web-dist/assets/zenscript-BnlCZFoB.js +1 -0
- package/web-app/web-dist/assets/zig-CMLA9XwU.js +1 -0
- package/web-app/web-dist/index.html +16 -0
- package/zuzuu/actions/adapter.mjs +2 -2
- package/zuzuu/actions/inbox.mjs +4 -4
- package/zuzuu/actions/manifest.mjs +3 -3
- package/zuzuu/actions/trail.mjs +1 -1
- package/zuzuu/commands/act-author.mjs +3 -3
- package/zuzuu/commands/act.mjs +58 -13
- package/zuzuu/commands/code.mjs +1 -1
- package/zuzuu/commands/digest.mjs +1 -1
- package/zuzuu/commands/doctor.mjs +3 -3
- package/zuzuu/commands/eval.mjs +44 -19
- package/zuzuu/commands/generation.mjs +40 -5
- package/zuzuu/commands/hook.mjs +6 -6
- package/zuzuu/commands/init.mjs +43 -18
- package/zuzuu/commands/knowledge.mjs +1 -1
- package/zuzuu/commands/migrate.mjs +109 -9
- package/zuzuu/commands/review.mjs +72 -5
- package/zuzuu/commands/status.mjs +3 -3
- package/zuzuu/commands/web.mjs +79 -0
- package/zuzuu/digest.mjs +2 -2
- package/zuzuu/faculty/generation.mjs +2 -2
- package/zuzuu/faculty/proposal.mjs +1 -1
- package/zuzuu/faculty/trail.mjs +2 -2
- package/zuzuu/guardrails/adapter.mjs +1 -1
- package/zuzuu/guardrails.mjs +1 -1
- package/zuzuu/inject.mjs +6 -6
- package/zuzuu/instructions/adapter.mjs +1 -1
- package/zuzuu/knowledge/inbox.mjs +1 -1
- package/zuzuu/knowledge/items.mjs +1 -1
- package/zuzuu/knowledge/proposals.mjs +1 -1
- package/zuzuu/knowledge/registry.mjs +2 -2
- package/zuzuu/live/install.mjs +3 -3
- package/zuzuu/live/live-store.mjs +2 -2
- package/zuzuu/memory/adapter.mjs +1 -1
- package/zuzuu/miners/guardrails.mjs +1 -1
- package/zuzuu/miners/instructions.mjs +1 -1
- package/zuzuu/miners/memory.mjs +3 -3
- package/zuzuu/scaffold.mjs +27 -22
- package/zuzuu/store.mjs +6 -5
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Your host agent — Claude Code, Codex, Gemini CLI, OpenCode — supplies the *brain* (the reasoning loop + the model). zuzuu wraps the host you already pay for: it **serves** faculties to it, **observes** every session as an OpenTelemetry trace, and (the end-game) **evolves** the faculties from those traces — human-gated, across versioned generations. We never run a competing agent loop and never drive the host headlessly.
|
|
8
8
|
|
|
9
|
-
>
|
|
9
|
+
> Install `npm i -g @zuzuucodes/cli` — the command is **`zz`** (or `zuzuu`). Published with provenance; releases auto-publish from `main` via GitHub OIDC.
|
|
10
10
|
|
|
11
11
|
> **Status (honest):** early build, moving fast. **Observe** works (5 real hosts, verified). **Serve** delivers the faculty home (`zuzuu init`), a session digest to every host, an **enforced guardrails gate** on all 5, and five faculties sharing one proposal/review spine. **Evolve** is now **wired and tested** — trace miners → a mechanical eval lens → human-gated `zuzuu review` → versioned **generations** (mint / rollback / drift-check) — but **not yet proven on a real graduation corpus** (the loop runs + passes hermetic tests; it hasn't yet improved an agent from real sessions end-to-end). Full design: [`docs/DESIGN.md`](docs/DESIGN.md).
|
|
12
12
|
|
|
@@ -18,8 +18,10 @@ npm install -g @zuzuucodes/cli # zero dependencies — installs the `zuzuu` co
|
|
|
18
18
|
# no coding agent yet? one command gives you a fully faculty-equipped one:
|
|
19
19
|
zuzuu code # scaffold the faculty home, install + wire OpenCode, launch it (capture + gate + grounding)
|
|
20
20
|
|
|
21
|
+
zuzuu web # prefer a visual app? the workbench: Home → review ceremony → embedded-terminal agent sessions
|
|
22
|
+
|
|
21
23
|
# already run Claude Code / Gemini / Codex / OpenCode / pi? wrap the one you have:
|
|
22
|
-
zuzuu init # scaffold your project's agent home (
|
|
24
|
+
zuzuu init # scaffold your project's agent home (.zuzuu/) — git-style, hidden like .git
|
|
23
25
|
zuzuu explain # the 5 faculties + how graduation works (you're always in the loop)
|
|
24
26
|
zuzuu inbox # what's pending your approval · zuzuu review to approve/reject
|
|
25
27
|
zuzuu capture # turn your latest agent session into an OpenTelemetry trace
|
|
@@ -42,11 +44,11 @@ All five verified against **real sessions** — never fixtures; every host's liv
|
|
|
42
44
|
|
|
43
45
|
**Prerequisites:** Node ≥ 22 — that's it. You need at least one supported agent you've already used, so a session exists to capture. (Hacking on zuzuu itself? `git clone https://github.com/h1902y/zuzuu && cd zuzuu && npm link`.)
|
|
44
46
|
|
|
45
|
-
**`zuzuu init`** behaves like `git init`: empty dir → scaffolds the agent home + `AGENTS.md`/`CLAUDE.md`; existing project → adds
|
|
47
|
+
**`zuzuu init`** behaves like `git init`: empty dir → scaffolds the agent home + `AGENTS.md`/`CLAUDE.md`; existing project → adds the home and injects a small delimiter-marked block into your existing instruction files (your text is never touched); already initialized → restores missing pieces only. The home is **hidden like `.git` and self-explaining** — a `.zuzuu/` dir you can read and version in git (the only visible footprint is the managed block + three `.gitignore` lines; transparency lives in `zuzuu status` / `explain` / `digest`): `.zuzuu/README.md` (the explainer) · `knowledge/` (verified facts) · `memory/` (curated episodes) · `actions/` (runbooks) · `instructions/` (steering) · `guardrails/` (enforced rules), plus `generations/` (your checkpoints). Machine internals are dot-prefixed + git-ignored (`.zuzuu/.traces/`, `.zuzuu/.live/`). *(Pre-2026-06-12 homes at `agent/` migrate automatically on `zuzuu init`, or via `zuzuu migrate --home`.)*
|
|
46
48
|
|
|
47
49
|
**Live capture** (`zuzuu enable`) is invisible by design: a minimal lifecycle hook set (Claude Code, Gemini CLI, Codex), a bus plugin (OpenCode), or an extension (pi) — each wrapped so it **always exits 0 / fails open — it can never break your agent**. The same hook carries the guardrails gate, applied in each host's own idiom (Claude/Codex `hookSpecificOutput`, Gemini `{decision:"deny"}`, OpenCode throws from `tool.execute.before`, pi returns `{block:true}` from `tool_call`). Most hosts emit no clean end-signal when a terminal is killed, so `zuzuu doctor` *reconciles* lost sessions afterward from the transcript still on disk (nothing lost).
|
|
48
50
|
|
|
49
|
-
**Where your data lives:** transcripts are read **read-only**; output is git-native in your repo —
|
|
51
|
+
**Where your data lives:** transcripts are read **read-only**; output is git-native in your repo — `.zuzuu/sessions.json` (small tracked index, each session linked to a commit) + `.zuzuu/.traces/*.otlp.jsonl` (local, git-ignored). **Nothing is uploaded**; no raw tool input/output on the trace (byte sizes only).
|
|
50
52
|
|
|
51
53
|
**Verify / troubleshoot:** `npm test` (hermetic) · `npm run playground` (⏭️ skip = that host isn't on *your* machine, not a failure) · `zuzuu doctor` (env + session health). "No host detected" → use a supported agent once in the repo, then retry.
|
|
52
54
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Direct entry to the bundled workbench daemon (the `zuzuu web` command is the
|
|
3
|
+
// porcelain; this bin exists for people who want the daemon itself).
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const entry = join(dirname(fileURLToPath(import.meta.url)), '..', 'web-app', 'dist', 'index.js');
|
|
9
|
+
if (!existsSync(entry)) {
|
|
10
|
+
console.error('zuzuu-web: the bundled workbench is not staged.');
|
|
11
|
+
console.error(' installed package → reinstall: npm i -g @zuzuucodes/cli (without --omit=optional)');
|
|
12
|
+
console.error(' repo checkout → build it: npm run build:web');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
await import(entry);
|
package/bin/zuzuu.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { migrate } from '../zuzuu/commands/migrate.mjs';
|
|
|
29
29
|
import { generation } from '../zuzuu/commands/generation.mjs';
|
|
30
30
|
import { evalCmd } from '../zuzuu/commands/eval.mjs';
|
|
31
31
|
import { code } from '../zuzuu/commands/code.mjs';
|
|
32
|
+
import { web } from '../zuzuu/commands/web.mjs';
|
|
32
33
|
import { explain } from '../zuzuu/commands/explain.mjs';
|
|
33
34
|
import { inbox } from '../zuzuu/commands/inbox.mjs';
|
|
34
35
|
|
|
@@ -59,9 +60,10 @@ function help() {
|
|
|
59
60
|
usage: zuzuu <command> [options]
|
|
60
61
|
|
|
61
62
|
code [dir] launch OpenCode as the bundled default host (faculty home + capture + gate + digest)
|
|
62
|
-
|
|
63
|
+
web [dir] launch the visual workbench (installs @zuzuucodes/web on demand)
|
|
64
|
+
init scaffold the faculty home (.zuzuu/) — git-style, idempotent
|
|
63
65
|
status detected hosts + recorded sessions
|
|
64
|
-
capture [--host NAME] capture a session →
|
|
66
|
+
capture [--host NAME] capture a session → .zuzuu/.traces + .zuzuu/sessions.json
|
|
65
67
|
[--session ID] [--file PATH]
|
|
66
68
|
trace [--last | FILE] print a captured trace's span tree
|
|
67
69
|
remember "fact" [--type t] [--attr k=v] [--rel type=target]
|
|
@@ -88,7 +90,7 @@ usage: zuzuu <command> [options]
|
|
|
88
90
|
enable background hooks: invisible live capture + guardrails gate
|
|
89
91
|
disable remove the background hooks
|
|
90
92
|
eval [--faculty f] rank pending proposals by eval score, highest first
|
|
91
|
-
migrate
|
|
93
|
+
migrate [--home] one-time migrators: proposal schema · --home moves agent/ → .zuzuu/
|
|
92
94
|
doctor environment + session health (reconciles lost sessions)
|
|
93
95
|
explain [topic] the 5 faculties + how graduation works
|
|
94
96
|
version print version
|
|
@@ -103,6 +105,7 @@ const args = parseArgs(rest);
|
|
|
103
105
|
|
|
104
106
|
switch (cmd) {
|
|
105
107
|
case 'code': process.exit(code(args)); break;
|
|
108
|
+
case 'web': web(args); break;
|
|
106
109
|
case 'init': init(args); break;
|
|
107
110
|
case 'remember': remember(args); break;
|
|
108
111
|
case 'recall': await recall(args); break;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuzuucodes/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
|
-
"description": "Give the coding agent you already run (Claude Code, Codex, Gemini CLI, OpenCode) evolving faculties
|
|
9
|
+
"description": "Give the coding agent you already run (Claude Code, Codex, Gemini CLI, OpenCode) evolving faculties \u2014 knowledge, memory, actions, instructions, guardrails \u2014 with host-agnostic OpenTelemetry session tracing. Zero dependencies.",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai-agents",
|
|
12
12
|
"claude-code",
|
|
@@ -35,11 +35,13 @@
|
|
|
35
35
|
},
|
|
36
36
|
"bin": {
|
|
37
37
|
"zz": "bin/zuzuu.mjs",
|
|
38
|
-
"zuzuu": "bin/zuzuu.mjs"
|
|
38
|
+
"zuzuu": "bin/zuzuu.mjs",
|
|
39
|
+
"zuzuu-web": "bin/zuzuu-web.mjs"
|
|
39
40
|
},
|
|
40
41
|
"files": [
|
|
41
42
|
"bin/",
|
|
42
43
|
"zuzuu/",
|
|
44
|
+
"web-app/",
|
|
43
45
|
"experiments/experiment-1-trace-capture/core/",
|
|
44
46
|
"experiments/experiment-1-trace-capture/adapters/",
|
|
45
47
|
"LICENSE",
|
|
@@ -51,6 +53,17 @@
|
|
|
51
53
|
"inspect": "node experiments/experiment-1-trace-capture/bin/inspect-trace.mjs",
|
|
52
54
|
"test": "node --test 'tests/**/*.test.mjs'",
|
|
53
55
|
"playground": "node tests/playground/run.mjs",
|
|
54
|
-
"prepublishOnly": "npm test"
|
|
56
|
+
"prepublishOnly": "npm test",
|
|
57
|
+
"build:web": "node scripts/build-web.mjs"
|
|
58
|
+
},
|
|
59
|
+
"optionalDependencies": {
|
|
60
|
+
"@hono/node-server": "^2.0.4",
|
|
61
|
+
"@lydell/node-pty": "^1.2.0-beta.12",
|
|
62
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
63
|
+
"@xterm/headless": "^6.0.0",
|
|
64
|
+
"archiver": "^8.0.0",
|
|
65
|
+
"chokidar": "^5.0.0",
|
|
66
|
+
"hono": "^4.12.25",
|
|
67
|
+
"ws": "^8.21.0"
|
|
55
68
|
}
|
|
56
69
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const MAX_ENTRIES = 1000;
|
|
5
|
+
/**
|
|
6
|
+
* List the subdirectories of an absolute path, for the vault folder picker.
|
|
7
|
+
*
|
|
8
|
+
* SECURITY: this deliberately reads OUTSIDE the workspace sandbox — choosing
|
|
9
|
+
* a new vault means browsing the user's own machine. It is intentionally not
|
|
10
|
+
* routed through `safePath`. Mitigations: auth-gated + loopback-only at the
|
|
11
|
+
* server layer, and it returns DIRECTORY NAMES ONLY (never file contents).
|
|
12
|
+
*/
|
|
13
|
+
export async function listDirs(input) {
|
|
14
|
+
const abs = input && path.isAbsolute(input) ? path.resolve(input) : os.homedir();
|
|
15
|
+
const parent = path.dirname(abs);
|
|
16
|
+
const dirs = [];
|
|
17
|
+
const entries = await fsp.readdir(abs, { withFileTypes: true });
|
|
18
|
+
for (const e of entries) {
|
|
19
|
+
if (dirs.length >= MAX_ENTRIES)
|
|
20
|
+
break;
|
|
21
|
+
if (e.name.startsWith("."))
|
|
22
|
+
continue;
|
|
23
|
+
let isDir = e.isDirectory();
|
|
24
|
+
if (e.isSymbolicLink()) {
|
|
25
|
+
try {
|
|
26
|
+
isDir = (await fsp.stat(path.join(abs, e.name))).isDirectory();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
isDir = false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (isDir)
|
|
33
|
+
dirs.push({ name: e.name, path: path.join(abs, e.name) });
|
|
34
|
+
}
|
|
35
|
+
dirs.sort((a, b) => a.name.localeCompare(b.name));
|
|
36
|
+
return { path: abs, parent: parent === abs ? null : parent, dirs };
|
|
37
|
+
}
|
|
38
|
+
/** Create a directory under an absolute parent, returning its absolute path. */
|
|
39
|
+
export async function mkdirIn(parent, name) {
|
|
40
|
+
if (!path.isAbsolute(parent))
|
|
41
|
+
throw new Error("parent must be absolute");
|
|
42
|
+
if (!name || name.includes("/") || name.includes("\\") || name === "." || name === "..") {
|
|
43
|
+
throw new Error("invalid folder name");
|
|
44
|
+
}
|
|
45
|
+
const abs = path.join(parent, name);
|
|
46
|
+
await fsp.mkdir(abs, { recursive: false });
|
|
47
|
+
return abs;
|
|
48
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const DIR = path.join(os.homedir(), ".webcode");
|
|
6
|
+
const FILE = path.join(DIR, "config.json");
|
|
7
|
+
const RECENT_MAX = 10;
|
|
8
|
+
const DEFAULT = { recent: [], onboarded: false };
|
|
9
|
+
/** Load persisted config, tolerating a missing/corrupt file. */
|
|
10
|
+
export async function load() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await fsp.readFile(FILE, "utf8");
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
return {
|
|
15
|
+
recent: Array.isArray(parsed.recent) ? parsed.recent.filter((p) => typeof p === "string") : [],
|
|
16
|
+
onboarded: parsed.onboarded === true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ...DEFAULT };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function save(cfg) {
|
|
24
|
+
await fsp.mkdir(DIR, { recursive: true });
|
|
25
|
+
await fsp.writeFile(FILE, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
26
|
+
}
|
|
27
|
+
/** Prepend a workspace to the recent list (deduped, most-recent-first, capped). */
|
|
28
|
+
export async function addRecent(root) {
|
|
29
|
+
const cfg = await load();
|
|
30
|
+
cfg.recent = [root, ...cfg.recent.filter((p) => p !== root)].slice(0, RECENT_MAX);
|
|
31
|
+
await save(cfg);
|
|
32
|
+
return cfg;
|
|
33
|
+
}
|
|
34
|
+
export async function setOnboarded() {
|
|
35
|
+
const cfg = await load();
|
|
36
|
+
cfg.onboarded = true;
|
|
37
|
+
await save(cfg);
|
|
38
|
+
return cfg;
|
|
39
|
+
}
|
|
40
|
+
/** Pure helper (exported for tests): compute the next recent list. */
|
|
41
|
+
export function nextRecent(recent, root) {
|
|
42
|
+
return [root, ...recent.filter((p) => p !== root)].slice(0, RECENT_MAX);
|
|
43
|
+
}
|
|
44
|
+
/** Synchronous best-effort load for startup paths that can't await. */
|
|
45
|
+
export function loadSync() {
|
|
46
|
+
try {
|
|
47
|
+
return { ...DEFAULT, ...JSON.parse(fs.readFileSync(FILE, "utf8")) };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return { ...DEFAULT };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { toRel } from "./safe-path.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const IGNORE = new Set([".git", "node_modules", "dist", ".next", ".cache"]);
|
|
8
|
+
let rgAvailable = null;
|
|
9
|
+
async function hasRg() {
|
|
10
|
+
if (rgAvailable === null) {
|
|
11
|
+
rgAvailable = await execFileAsync("rg", ["--version"], { timeout: 2000 })
|
|
12
|
+
.then(() => true)
|
|
13
|
+
.catch(() => false);
|
|
14
|
+
}
|
|
15
|
+
return rgAvailable;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Flat list of workspace files for the ⌘K palette. Prefers `rg --files`
|
|
19
|
+
* (honors .gitignore, fast); falls back to a bounded manual walk.
|
|
20
|
+
*/
|
|
21
|
+
export async function listFiles(root, limit = 5000) {
|
|
22
|
+
if (await hasRg())
|
|
23
|
+
return rgFiles(root, limit);
|
|
24
|
+
return walkFiles(root, limit);
|
|
25
|
+
}
|
|
26
|
+
function rgFiles(root, limit) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const proc = spawn("rg", ["--files", "--hidden", "-g", "!.git"], {
|
|
29
|
+
cwd: root,
|
|
30
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
31
|
+
});
|
|
32
|
+
const files = [];
|
|
33
|
+
let buf = "";
|
|
34
|
+
let truncated = false;
|
|
35
|
+
proc.stdout.on("data", (chunk) => {
|
|
36
|
+
buf += chunk.toString("utf8");
|
|
37
|
+
let nl;
|
|
38
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
39
|
+
const line = buf.slice(0, nl);
|
|
40
|
+
buf = buf.slice(nl + 1);
|
|
41
|
+
if (line)
|
|
42
|
+
files.push(line);
|
|
43
|
+
if (files.length >= limit) {
|
|
44
|
+
truncated = true;
|
|
45
|
+
proc.kill("SIGTERM");
|
|
46
|
+
resolve({ files, truncated });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
proc.on("close", () => resolve({ files, truncated }));
|
|
52
|
+
proc.on("error", () => resolve({ files, truncated }));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function walkFiles(root, limit) {
|
|
56
|
+
const files = [];
|
|
57
|
+
let truncated = false;
|
|
58
|
+
const stack = [root];
|
|
59
|
+
while (stack.length > 0 && files.length < limit) {
|
|
60
|
+
const dir = stack.pop();
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
if (e.name.startsWith(".") && e.name !== ".webcode")
|
|
70
|
+
continue;
|
|
71
|
+
if (IGNORE.has(e.name))
|
|
72
|
+
continue;
|
|
73
|
+
const full = path.join(dir, e.name);
|
|
74
|
+
if (e.isDirectory())
|
|
75
|
+
stack.push(full);
|
|
76
|
+
else if (e.isFile()) {
|
|
77
|
+
files.push(toRel(root, full));
|
|
78
|
+
if (files.length >= limit) {
|
|
79
|
+
truncated = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
files.sort();
|
|
86
|
+
return { files, truncated };
|
|
87
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary WS framing: 1 opcode byte followed by the payload
|
|
3
|
+
* (raw bytes for I/O frames, UTF-8 JSON for control frames).
|
|
4
|
+
*/
|
|
5
|
+
export function encodeFrame(op, payload = "") {
|
|
6
|
+
const body = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
7
|
+
const buf = Buffer.allocUnsafe(1 + body.length);
|
|
8
|
+
buf[0] = op;
|
|
9
|
+
buf.set(body, 1);
|
|
10
|
+
return buf;
|
|
11
|
+
}
|
|
12
|
+
export function decodeFrame(data) {
|
|
13
|
+
if (data.length === 0)
|
|
14
|
+
throw new Error("empty frame");
|
|
15
|
+
return { op: data[0], payload: data.subarray(1) };
|
|
16
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { ZipArchive } from "archiver";
|
|
8
|
+
import { PathError, resolveSafe, toRel } from "./safe-path.js";
|
|
9
|
+
const STAT_CONCURRENCY = 64;
|
|
10
|
+
const MIME = {
|
|
11
|
+
".txt": "text/plain; charset=utf-8",
|
|
12
|
+
".md": "text/markdown; charset=utf-8",
|
|
13
|
+
".json": "application/json",
|
|
14
|
+
".js": "text/javascript",
|
|
15
|
+
".ts": "text/plain; charset=utf-8",
|
|
16
|
+
".html": "text/html; charset=utf-8",
|
|
17
|
+
".css": "text/css",
|
|
18
|
+
".png": "image/png",
|
|
19
|
+
".jpg": "image/jpeg",
|
|
20
|
+
".jpeg": "image/jpeg",
|
|
21
|
+
".gif": "image/gif",
|
|
22
|
+
".svg": "image/svg+xml",
|
|
23
|
+
".webp": "image/webp",
|
|
24
|
+
".pdf": "application/pdf",
|
|
25
|
+
".zip": "application/zip",
|
|
26
|
+
".mp4": "video/mp4",
|
|
27
|
+
".m4v": "video/mp4",
|
|
28
|
+
".webm": "video/webm",
|
|
29
|
+
".mov": "video/quicktime",
|
|
30
|
+
".mp3": "audio/mpeg",
|
|
31
|
+
".wav": "audio/wav",
|
|
32
|
+
".ogg": "audio/ogg",
|
|
33
|
+
".m4a": "audio/mp4",
|
|
34
|
+
".aac": "audio/aac",
|
|
35
|
+
".flac": "audio/flac",
|
|
36
|
+
".avif": "image/avif",
|
|
37
|
+
".bmp": "image/bmp",
|
|
38
|
+
".ico": "image/x-icon",
|
|
39
|
+
".woff2": "font/woff2",
|
|
40
|
+
};
|
|
41
|
+
function contentType(file) {
|
|
42
|
+
return MIME[path.extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
43
|
+
}
|
|
44
|
+
function kindOf(dirent) {
|
|
45
|
+
if (dirent.isSymbolicLink())
|
|
46
|
+
return "symlink";
|
|
47
|
+
if (dirent.isDirectory())
|
|
48
|
+
return "dir";
|
|
49
|
+
if (dirent.isFile())
|
|
50
|
+
return "file";
|
|
51
|
+
return "other";
|
|
52
|
+
}
|
|
53
|
+
async function mapLimit(items, limit, fn) {
|
|
54
|
+
const results = new Array(items.length);
|
|
55
|
+
let next = 0;
|
|
56
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
57
|
+
for (;;) {
|
|
58
|
+
const i = next++;
|
|
59
|
+
if (i >= items.length)
|
|
60
|
+
return;
|
|
61
|
+
results[i] = await fn(items[i]);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
await Promise.all(workers);
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
export function createFsApi(getRoot) {
|
|
68
|
+
const app = new Hono();
|
|
69
|
+
// resolve the (mutable) workspace root once per request, so handlers below
|
|
70
|
+
// keep referencing a stable `root` while the daemon can re-root at runtime.
|
|
71
|
+
let root = getRoot();
|
|
72
|
+
app.use("*", async (_c, next) => {
|
|
73
|
+
root = getRoot();
|
|
74
|
+
await next();
|
|
75
|
+
});
|
|
76
|
+
app.onError((err, c) => {
|
|
77
|
+
if (err instanceof PathError)
|
|
78
|
+
return c.json({ error: err.message }, 403);
|
|
79
|
+
const code = err.code;
|
|
80
|
+
if (code === "ENOENT")
|
|
81
|
+
return c.json({ error: "not found" }, 404);
|
|
82
|
+
if (code === "EEXIST")
|
|
83
|
+
return c.json({ error: "already exists" }, 409);
|
|
84
|
+
console.error("[fs-api]", err);
|
|
85
|
+
return c.json({ error: "internal error" }, 500);
|
|
86
|
+
});
|
|
87
|
+
app.get("/list", async (c) => {
|
|
88
|
+
const abs = await resolveSafe(root, c.req.query("path") ?? "");
|
|
89
|
+
const dirents = await fsp.readdir(abs, { withFileTypes: true });
|
|
90
|
+
const entries = await mapLimit(dirents, STAT_CONCURRENCY, async (d) => {
|
|
91
|
+
const full = path.join(abs, d.name);
|
|
92
|
+
const kind = kindOf(d);
|
|
93
|
+
let size = 0;
|
|
94
|
+
let mtimeMs = 0;
|
|
95
|
+
let targetKind;
|
|
96
|
+
try {
|
|
97
|
+
const lst = await fsp.lstat(full);
|
|
98
|
+
size = lst.size;
|
|
99
|
+
mtimeMs = lst.mtimeMs;
|
|
100
|
+
if (kind === "symlink") {
|
|
101
|
+
// Only report a resolvable target if it stays inside the workspace.
|
|
102
|
+
await resolveSafe(root, toRel(root, full));
|
|
103
|
+
const st = await fsp.stat(full);
|
|
104
|
+
targetKind = st.isDirectory() ? "dir" : st.isFile() ? "file" : "other";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// broken symlink / vanished entry — report what we know
|
|
109
|
+
}
|
|
110
|
+
return { name: d.name, kind, targetKind, size, mtimeMs };
|
|
111
|
+
});
|
|
112
|
+
entries.sort((a, b) => {
|
|
113
|
+
const aDir = a.kind === "dir" || a.targetKind === "dir" ? 0 : 1;
|
|
114
|
+
const bDir = b.kind === "dir" || b.targetKind === "dir" ? 0 : 1;
|
|
115
|
+
return aDir - bDir || a.name.localeCompare(b.name);
|
|
116
|
+
});
|
|
117
|
+
const body = { path: toRel(root, abs), entries };
|
|
118
|
+
return c.json(body);
|
|
119
|
+
});
|
|
120
|
+
app.post("/mkdir", async (c) => {
|
|
121
|
+
const body = await c.req.json();
|
|
122
|
+
const abs = await resolveSafe(root, body.path ?? "");
|
|
123
|
+
if (abs === root)
|
|
124
|
+
return c.json({ error: "invalid path" }, 400);
|
|
125
|
+
await fsp.mkdir(abs, { recursive: true });
|
|
126
|
+
return c.json({ ok: true });
|
|
127
|
+
});
|
|
128
|
+
// Save an editor buffer to disk.
|
|
129
|
+
app.post("/write", async (c) => {
|
|
130
|
+
const body = await c.req.json();
|
|
131
|
+
const abs = await resolveSafe(root, body.path ?? "");
|
|
132
|
+
if (abs === root)
|
|
133
|
+
return c.json({ error: "invalid path" }, 400);
|
|
134
|
+
if (typeof body.content !== "string")
|
|
135
|
+
return c.json({ error: "content required" }, 400);
|
|
136
|
+
await fsp.mkdir(path.dirname(abs), { recursive: true });
|
|
137
|
+
await fsp.writeFile(abs, body.content, "utf8");
|
|
138
|
+
return c.json({ ok: true });
|
|
139
|
+
});
|
|
140
|
+
app.post("/rename", async (c) => {
|
|
141
|
+
const body = await c.req.json();
|
|
142
|
+
const from = await resolveSafe(root, body.from ?? "");
|
|
143
|
+
const to = await resolveSafe(root, body.to ?? "");
|
|
144
|
+
if (from === root || to === root)
|
|
145
|
+
return c.json({ error: "invalid path" }, 400);
|
|
146
|
+
await fsp.rename(from, to);
|
|
147
|
+
return c.json({ ok: true });
|
|
148
|
+
});
|
|
149
|
+
app.post("/delete", async (c) => {
|
|
150
|
+
const body = await c.req.json();
|
|
151
|
+
if (!Array.isArray(body.paths) || body.paths.length === 0) {
|
|
152
|
+
return c.json({ error: "paths required" }, 400);
|
|
153
|
+
}
|
|
154
|
+
for (const p of body.paths) {
|
|
155
|
+
const abs = await resolveSafe(root, p);
|
|
156
|
+
if (abs === root)
|
|
157
|
+
return c.json({ error: "cannot delete workspace root" }, 400);
|
|
158
|
+
await fsp.rm(abs, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
return c.json({ ok: true });
|
|
161
|
+
});
|
|
162
|
+
app.get("/download", async (c) => {
|
|
163
|
+
const abs = await resolveSafe(root, c.req.query("path") ?? "");
|
|
164
|
+
const st = await fsp.stat(abs);
|
|
165
|
+
const name = path.basename(abs) || "workspace";
|
|
166
|
+
if (st.isDirectory()) {
|
|
167
|
+
// store-only zip: local transfer, no point burning CPU on deflate
|
|
168
|
+
const archive = new ZipArchive({ store: true });
|
|
169
|
+
archive.on("error", (err) => console.error("[zip]", err));
|
|
170
|
+
archive.directory(abs, name);
|
|
171
|
+
void archive.finalize();
|
|
172
|
+
return new Response(Readable.toWeb(archive), {
|
|
173
|
+
headers: {
|
|
174
|
+
"Content-Type": "application/zip",
|
|
175
|
+
"Content-Disposition": `attachment; filename*=UTF-8''${encodeURIComponent(name)}.zip`,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const range = c.req.header("range");
|
|
180
|
+
const headers = {
|
|
181
|
+
"Content-Type": contentType(abs),
|
|
182
|
+
"Accept-Ranges": "bytes",
|
|
183
|
+
"Content-Disposition": c.req.query("inline") === "1"
|
|
184
|
+
? "inline"
|
|
185
|
+
: `attachment; filename*=UTF-8''${encodeURIComponent(name)}`,
|
|
186
|
+
};
|
|
187
|
+
const match = range ? /^bytes=(\d*)-(\d*)$/.exec(range) : null;
|
|
188
|
+
if (match && (match[1] || match[2])) {
|
|
189
|
+
const start = match[1] ? Number(match[1]) : st.size - Number(match[2]);
|
|
190
|
+
const end = match[1] && match[2] ? Math.min(Number(match[2]), st.size - 1) : st.size - 1;
|
|
191
|
+
if (Number.isNaN(start) || start < 0 || start > end) {
|
|
192
|
+
return c.body(null, 416, { "Content-Range": `bytes */${st.size}` });
|
|
193
|
+
}
|
|
194
|
+
headers["Content-Range"] = `bytes ${start}-${end}/${st.size}`;
|
|
195
|
+
headers["Content-Length"] = String(end - start + 1);
|
|
196
|
+
const stream = Readable.toWeb(fs.createReadStream(abs, { start, end }));
|
|
197
|
+
return new Response(stream, { status: 206, headers });
|
|
198
|
+
}
|
|
199
|
+
headers["Content-Length"] = String(st.size);
|
|
200
|
+
return new Response(Readable.toWeb(fs.createReadStream(abs)), { headers });
|
|
201
|
+
});
|
|
202
|
+
// Open with the OS default app, or reveal in the system file manager —
|
|
203
|
+
// the local-native replacement for download/upload.
|
|
204
|
+
app.post("/open", async (c) => {
|
|
205
|
+
const body = await c.req.json();
|
|
206
|
+
const abs = await resolveSafe(root, body.path ?? "");
|
|
207
|
+
await fsp.access(abs); // 404 if missing
|
|
208
|
+
let cmd;
|
|
209
|
+
let args;
|
|
210
|
+
if (process.platform === "darwin") {
|
|
211
|
+
cmd = "open";
|
|
212
|
+
args = body.reveal ? ["-R", abs] : [abs];
|
|
213
|
+
}
|
|
214
|
+
else if (process.platform === "win32") {
|
|
215
|
+
cmd = "explorer";
|
|
216
|
+
args = body.reveal ? [`/select,${abs}`] : [abs];
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
cmd = "xdg-open";
|
|
220
|
+
args = [body.reveal ? path.dirname(abs) : abs];
|
|
221
|
+
}
|
|
222
|
+
spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
223
|
+
return c.json({ ok: true });
|
|
224
|
+
});
|
|
225
|
+
return app;
|
|
226
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
async function git(root, args) {
|
|
5
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
6
|
+
cwd: root,
|
|
7
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
8
|
+
timeout: 10_000,
|
|
9
|
+
});
|
|
10
|
+
return stdout;
|
|
11
|
+
}
|
|
12
|
+
async function isRepo(root) {
|
|
13
|
+
try {
|
|
14
|
+
const out = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
15
|
+
return out.trim() === "true";
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function status(root) {
|
|
22
|
+
if (!(await isRepo(root)))
|
|
23
|
+
return { repo: false, branch: "", entries: [] };
|
|
24
|
+
let branch = "";
|
|
25
|
+
try {
|
|
26
|
+
branch = (await git(root, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
branch = "(detached)";
|
|
30
|
+
}
|
|
31
|
+
// -z gives NUL-separated records; renames emit an extra NUL with the old path
|
|
32
|
+
const raw = await git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
33
|
+
const entries = [];
|
|
34
|
+
const parts = raw.split("\0");
|
|
35
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36
|
+
const rec = parts[i];
|
|
37
|
+
if (!rec || rec.length < 3)
|
|
38
|
+
continue;
|
|
39
|
+
const index = rec[0];
|
|
40
|
+
const worktree = rec[1];
|
|
41
|
+
const path = rec.slice(3);
|
|
42
|
+
// a rename/copy ("R"/"C") consumes the following NUL field (old path)
|
|
43
|
+
if (index === "R" || index === "C")
|
|
44
|
+
i++;
|
|
45
|
+
entries.push({ path, index, worktree });
|
|
46
|
+
}
|
|
47
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
48
|
+
return { repo: true, branch, entries };
|
|
49
|
+
}
|
|
50
|
+
/** HEAD/index content of a path for the diff editor's left side. */
|
|
51
|
+
export async function diffOriginal(root, path) {
|
|
52
|
+
// staged content if present, else HEAD; empty for untracked/new files
|
|
53
|
+
for (const ref of [`:${path}`, `HEAD:${path}`]) {
|
|
54
|
+
try {
|
|
55
|
+
return await git(root, ["show", ref]);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// try next
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
export async function stage(root, paths) {
|
|
64
|
+
if (paths.length)
|
|
65
|
+
await git(root, ["add", "--", ...paths]);
|
|
66
|
+
}
|
|
67
|
+
export async function unstage(root, paths) {
|
|
68
|
+
if (paths.length)
|
|
69
|
+
await git(root, ["reset", "-q", "HEAD", "--", ...paths]);
|
|
70
|
+
}
|
|
71
|
+
export async function commit(root, message) {
|
|
72
|
+
await git(root, ["commit", "-m", message]);
|
|
73
|
+
}
|