@bytespell/shella 0.1.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 +79 -0
- package/bin/cli.js +191 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +94 -0
- package/dist/server/middleware/cors.d.ts +6 -0
- package/dist/server/middleware/cors.js +8 -0
- package/dist/server/routes/logs.d.ts +2 -0
- package/dist/server/routes/logs.js +29 -0
- package/dist/server/routes/model-state.d.ts +2 -0
- package/dist/server/routes/model-state.js +81 -0
- package/dist/server/routes/proxy.d.ts +2 -0
- package/dist/server/routes/proxy.js +122 -0
- package/dist/server/routes/windows.d.ts +2 -0
- package/dist/server/routes/windows.js +125 -0
- package/dist/server/schema.d.ts +204 -0
- package/dist/server/schema.js +18 -0
- package/dist/server/services/database.d.ts +5 -0
- package/dist/server/services/database.js +110 -0
- package/dist/server/services/sse-manager.d.ts +16 -0
- package/dist/server/services/sse-manager.js +44 -0
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/_baseUniq-Dj1Rdun9.js +1 -0
- package/dist/web/assets/abap-BdImnpbu.js +1 -0
- package/dist/web/assets/actionscript-3-CfeIJUat.js +1 -0
- package/dist/web/assets/ada-bCR0ucgS.js +1 -0
- package/dist/web/assets/amazon-bedrock-B_u4Gv8g.svg +3 -0
- package/dist/web/assets/andromeeda-C-Jbm3Hp.js +1 -0
- package/dist/web/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/web/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/web/assets/apache-Pmp26Uib.js +1 -0
- package/dist/web/assets/apex-DDbsPZ6N.js +1 -0
- package/dist/web/assets/apl-dKokRX4l.js +1 -0
- package/dist/web/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/web/assets/ara-BRHolxvo.js +1 -0
- package/dist/web/assets/arc-BcAe_L9C.js +1 -0
- package/dist/web/assets/architectureDiagram-VXUJARFQ-CUydP-HB.js +36 -0
- package/dist/web/assets/asciidoc-Dv7Oe6Be.js +1 -0
- package/dist/web/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/web/assets/astro-CbQHKStN.js +1 -0
- package/dist/web/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/web/assets/awk-DMzUqQB5.js +1 -0
- package/dist/web/assets/ayu-dark-Cv9koXgw.js +1 -0
- package/dist/web/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/web/assets/bat-BkioyH1T.js +1 -0
- package/dist/web/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/web/assets/berry-uYugtg8r.js +1 -0
- package/dist/web/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/web/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/web/assets/blade-DVc8C-J4.js +1 -0
- package/dist/web/assets/blockDiagram-VD42YOAC-D7ENTm-e.js +122 -0
- package/dist/web/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/web/assets/c-BIGW1oBm.js +1 -0
- package/dist/web/assets/c4Diagram-YG6GDRKO-DE6z4ano.js +10 -0
- package/dist/web/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/web/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/web/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/web/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/web/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/web/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/web/assets/channel-C1Y873om.js +1 -0
- package/dist/web/assets/chunk-4BX2VUAB-f1AgJ5-4.js +1 -0
- package/dist/web/assets/chunk-55IACEB6-CRc_DE4C.js +1 -0
- package/dist/web/assets/chunk-B4BG7PRW-BeFQ_8yC.js +165 -0
- package/dist/web/assets/chunk-DI55MBZ5-_SEo9TtQ.js +220 -0
- package/dist/web/assets/chunk-FMBD7UC4-6M3K7zkj.js +15 -0
- package/dist/web/assets/chunk-QN33PNHL-BGGg7fLk.js +1 -0
- package/dist/web/assets/chunk-QZHKN3VN-Q6HkNG6r.js +1 -0
- package/dist/web/assets/chunk-TZMSLE5B-D3UMAT7-.js +1 -0
- package/dist/web/assets/clarity-D53aC0YG.js +1 -0
- package/dist/web/assets/classDiagram-2ON5EDUG-DLBhGros.js +1 -0
- package/dist/web/assets/classDiagram-v2-WZHVMYZB-DLBhGros.js +1 -0
- package/dist/web/assets/clojure-P80f7IUj.js +1 -0
- package/dist/web/assets/clone-DVBZ10mH.js +1 -0
- package/dist/web/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/web/assets/cobol-nwyudZeR.js +1 -0
- package/dist/web/assets/code-block-QI2IAROF-BzpuLcBt.js +2 -0
- package/dist/web/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/web/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/web/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/web/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/web/assets/coq-DkFqJrB1.js +1 -0
- package/dist/web/assets/cose-bilkent-S5V4N54A-BIcssrhL.js +1 -0
- package/dist/web/assets/cpp-CofmeUqb.js +1 -0
- package/dist/web/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/web/assets/csharp-K5feNrxe.js +1 -0
- package/dist/web/assets/css-DPfMkruS.js +1 -0
- package/dist/web/assets/csv-fuZLfV_i.js +1 -0
- package/dist/web/assets/cue-D82EKSYY.js +1 -0
- package/dist/web/assets/cypher-COkxafJQ.js +1 -0
- package/dist/web/assets/cytoscape.esm-5J0xJHOV.js +321 -0
- package/dist/web/assets/d-85-TOEBH.js +1 -0
- package/dist/web/assets/dagre-6UL2VRFP-YaA_gyjx.js +4 -0
- package/dist/web/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/web/assets/dart-CF10PKvl.js +1 -0
- package/dist/web/assets/dax-CEL-wOlO.js +1 -0
- package/dist/web/assets/deepinfra-Dq_BB8I0.svg +4 -0
- package/dist/web/assets/defaultLocale-C4B-KCzX.js +1 -0
- package/dist/web/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/web/assets/diagram-PSM6KHXK-r6WVaeIu.js +24 -0
- package/dist/web/assets/diagram-QEK2KX5R-DtTKn621.js +43 -0
- package/dist/web/assets/diagram-S2PKOQOG-BINNPg42.js +24 -0
- package/dist/web/assets/diff-D97Zzqfu.js +1 -0
- package/dist/web/assets/docker-BcOcwvcX.js +1 -0
- package/dist/web/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/web/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/web/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/web/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/web/assets/edge-BkV0erSs.js +1 -0
- package/dist/web/assets/elixir-CDX3lj18.js +1 -0
- package/dist/web/assets/elm-DbKCFpqz.js +1 -0
- package/dist/web/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/web/assets/erDiagram-Q2GNP2WA-DXIwYH9k.js +60 -0
- package/dist/web/assets/erb-BOJIQeun.js +1 -0
- package/dist/web/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/web/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/web/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/web/assets/fennel-BYunw83y.js +1 -0
- package/dist/web/assets/fish-BvzEVeQv.js +1 -0
- package/dist/web/assets/flowDiagram-NV44I4VS-D8Bad7wU.js +162 -0
- package/dist/web/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/web/assets/fortran-fixed-form-BZjJHVRy.js +1 -0
- package/dist/web/assets/fortran-free-form-D22FLkUw.js +1 -0
- package/dist/web/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/web/assets/ganttDiagram-JELNMOA3-DSiZGBoG.js +267 -0
- package/dist/web/assets/gdresource-B7Tvp0Sc.js +1 -0
- package/dist/web/assets/gdscript-DTMYz4Jt.js +1 -0
- package/dist/web/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/web/assets/genie-D0YGMca9.js +1 -0
- package/dist/web/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/web/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/web/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/web/assets/gitGraphDiagram-NY62KEGX-CCxrAUDj.js +65 -0
- package/dist/web/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/web/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/web/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/web/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/web/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/web/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/web/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/web/assets/gleam-BspZqrRM.js +1 -0
- package/dist/web/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/web/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/web/assets/glsl-DplSGwfg.js +1 -0
- package/dist/web/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/web/assets/go-Dn2_MT6a.js +1 -0
- package/dist/web/assets/google-vertex-BCu5nGvk.svg +10 -0
- package/dist/web/assets/graph-CcI3NtJu.js +1 -0
- package/dist/web/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/web/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/web/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/web/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/web/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/web/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/web/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/web/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/web/assets/hack-CaT9iCJl.js +1 -0
- package/dist/web/assets/haml-B8DHNrY2.js +1 -0
- package/dist/web/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/web/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/web/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/web/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/web/assets/hjson-D5-asLiD.js +1 -0
- package/dist/web/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/web/assets/houston-DnULxvSX.js +1 -0
- package/dist/web/assets/html-GMplVEZG.js +1 -0
- package/dist/web/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/web/assets/http-jrhK8wxY.js +1 -0
- package/dist/web/assets/huggingface-DlwzPJmc.svg +3 -0
- package/dist/web/assets/hurl-irOxFIW8.js +1 -0
- package/dist/web/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/web/assets/hy-DFXneXwc.js +1 -0
- package/dist/web/assets/imba-DGztddWO.js +1 -0
- package/dist/web/assets/index-B0jWvqrS.css +1 -0
- package/dist/web/assets/index-BCTWtQQB.js +1716 -0
- package/dist/web/assets/infoDiagram-WHAUD3N6-C3OGZjzs.js +2 -0
- package/dist/web/assets/ini-BEwlwnbL.js +1 -0
- package/dist/web/assets/init-Gi6I4Gst.js +1 -0
- package/dist/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/dist/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/dist/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/dist/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/dist/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/dist/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/dist/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/dist/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/dist/web/assets/java-CylS5w8V.js +1 -0
- package/dist/web/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/web/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/web/assets/jison-wvAkD_A8.js +1 -0
- package/dist/web/assets/journeyDiagram-XKPGCS4Q-aT-Wsw7y.js +139 -0
- package/dist/web/assets/json-Cp-IABpG.js +1 -0
- package/dist/web/assets/json5-C9tS-k6U.js +1 -0
- package/dist/web/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/web/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/web/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/web/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/web/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/web/assets/julia-C8NyazO9.js +1 -0
- package/dist/web/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/web/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/web/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/web/assets/kanban-definition-3W4ZIXB7-DpUj9tx6.js +89 -0
- package/dist/web/assets/katex-DvXFAOB1.css +1 -0
- package/dist/web/assets/kdl-DV7GczEv.js +1 -0
- package/dist/web/assets/kimi-for-coding-eVEHug_h.svg +3 -0
- package/dist/web/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/web/assets/kusto-BvAqAH-y.js +1 -0
- package/dist/web/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/web/assets/latex-BdAV_C_H.js +1 -0
- package/dist/web/assets/layout-Ujef10t5.js +1 -0
- package/dist/web/assets/lean-Bc6EcWN3.js +1 -0
- package/dist/web/assets/less-B1dDrJ26.js +1 -0
- package/dist/web/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/web/assets/linear-CLfbwX-c.js +1 -0
- package/dist/web/assets/liquid-DYVedYrR.js +1 -0
- package/dist/web/assets/llvm-BtvRca6l.js +1 -0
- package/dist/web/assets/log-2UxHyX5q.js +1 -0
- package/dist/web/assets/logo-BtOb2qkB.js +1 -0
- package/dist/web/assets/lua-BbnMAYS6.js +1 -0
- package/dist/web/assets/luau-CXu1NL6O.js +1 -0
- package/dist/web/assets/lucidquery-DRFbix82.svg +3 -0
- package/dist/web/assets/make-CHLpvVh8.js +1 -0
- package/dist/web/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/web/assets/marko-CPi9NSCl.js +1 -0
- package/dist/web/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/web/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/web/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/web/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/web/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/web/assets/matlab-D7o27uSR.js +1 -0
- package/dist/web/assets/mdc-DUICxH0z.js +1 -0
- package/dist/web/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/web/assets/mermaid-DKYwYmdq.js +1 -0
- package/dist/web/assets/mermaid.core-BAK_ixpL.js +191 -0
- package/dist/web/assets/min-D8VL4G-w.js +1 -0
- package/dist/web/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/web/assets/min-light-CTRr51gU.js +1 -0
- package/dist/web/assets/mindmap-definition-VGOIOE7T-ChCIxiAQ.js +68 -0
- package/dist/web/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/web/assets/mojo-1DNp92w6.js +1 -0
- package/dist/web/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/web/assets/moonshotai-BeF2wtIV.svg +3 -0
- package/dist/web/assets/move-Bu9oaDYs.js +1 -0
- package/dist/web/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/web/assets/nextflow-BrzmwbiE.js +1 -0
- package/dist/web/assets/nginx-DknmC5AR.js +1 -0
- package/dist/web/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/web/assets/nim-CVrawwO9.js +1 -0
- package/dist/web/assets/nix-c8nO5XWb.js +1 -0
- package/dist/web/assets/nord-Ddv68eIx.js +1 -0
- package/dist/web/assets/nushell-C-sUppwS.js +1 -0
- package/dist/web/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/web/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/web/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/web/assets/ollama-cloud-BIdeu-re.svg +7 -0
- package/dist/web/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/web/assets/one-light-PoHY5YXO.js +1 -0
- package/dist/web/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/web/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/web/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/web/assets/perl-C0TMdlhV.js +1 -0
- package/dist/web/assets/php-CDn_0X-4.js +1 -0
- package/dist/web/assets/pieDiagram-ADFJNKIX-jlQ1USe2.js +30 -0
- package/dist/web/assets/pierre-dark-BXuwtOqb.js +1 -0
- package/dist/web/assets/pierre-light-dckrK0oj.js +1 -0
- package/dist/web/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/web/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/web/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/web/assets/po-BTJTHyun.js +1 -0
- package/dist/web/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/web/assets/polar-C0HS_06l.js +1 -0
- package/dist/web/assets/postcss-CXtECtnM.js +1 -0
- package/dist/web/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/web/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/web/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/web/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/web/assets/proto-DyJlTyXw.js +1 -0
- package/dist/web/assets/pug-CGlum2m_.js +1 -0
- package/dist/web/assets/puppet-BMWR74SV.js +1 -0
- package/dist/web/assets/purescript-CklMAg4u.js +1 -0
- package/dist/web/assets/python-B6aJPvgy.js +1 -0
- package/dist/web/assets/qml-3beO22l8.js +1 -0
- package/dist/web/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/web/assets/qss-IeuSbFQv.js +1 -0
- package/dist/web/assets/quadrantDiagram-AYHSOK5B-Dj2wmV1N.js +7 -0
- package/dist/web/assets/r-DiinP2Uv.js +1 -0
- package/dist/web/assets/racket-BqYA7rlc.js +1 -0
- package/dist/web/assets/raku-DXvB9xmW.js +1 -0
- package/dist/web/assets/razor-CE9lU5zL.js +1 -0
- package/dist/web/assets/red-bN70gL4F.js +1 -0
- package/dist/web/assets/reg-C-SQnVFl.js +1 -0
- package/dist/web/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/web/assets/rel-C3B-1QV4.js +1 -0
- package/dist/web/assets/requirementDiagram-UZGBJVZJ-Umq3-C8j.js +64 -0
- package/dist/web/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/web/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/web/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/web/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/web/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/web/assets/rst-B0xPkSld.js +1 -0
- package/dist/web/assets/ruby-BvKwtOVI.js +1 -0
- package/dist/web/assets/rust-B1yitclQ.js +1 -0
- package/dist/web/assets/sankeyDiagram-TZEHDZUN-Ce6gdeOT.js +10 -0
- package/dist/web/assets/sas-cz2c8ADy.js +1 -0
- package/dist/web/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/web/assets/scala-C151Ov-r.js +1 -0
- package/dist/web/assets/scheme-C98Dy4si.js +1 -0
- package/dist/web/assets/scss-OYdSNvt2.js +1 -0
- package/dist/web/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/web/assets/sequenceDiagram-WL72ISMW-DJGrGFRW.js +145 -0
- package/dist/web/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/web/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/web/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/web/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/web/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/web/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/web/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/web/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/web/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/web/assets/solidity-rGO070M0.js +1 -0
- package/dist/web/assets/soy-Brmx7dQM.js +1 -0
- package/dist/web/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/web/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/web/assets/sql-BLtJtn59.js +1 -0
- package/dist/web/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/web/assets/stata-BH5u7GGu.js +1 -0
- package/dist/web/assets/stateDiagram-FKZM4ZOC-D-zz3TRT.js +1 -0
- package/dist/web/assets/stateDiagram-v2-4FDKWEC3-CARvDj2s.js +1 -0
- package/dist/web/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/web/assets/svelte-3Dk4HxPD.js +1 -0
- package/dist/web/assets/swift-Dg5xB15N.js +1 -0
- package/dist/web/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/web/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/web/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/web/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/web/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/web/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/web/assets/templ-W15q3VgB.js +1 -0
- package/dist/web/assets/terraform-BETggiCN.js +1 -0
- package/dist/web/assets/tex-CxkMU7Pf.js +1 -0
- package/dist/web/assets/timeline-definition-IT6M3QCI-C8M7N84Y.js +61 -0
- package/dist/web/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/web/assets/toml-vGWfd6FD.js +1 -0
- package/dist/web/assets/treemap-KMMF4GRG-B_8-FlJu.js +128 -0
- package/dist/web/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/web/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/web/assets/tsx-COt5Ahok.js +1 -0
- package/dist/web/assets/turtle-BsS91CYL.js +1 -0
- package/dist/web/assets/twig-CO9l9SDP.js +1 -0
- package/dist/web/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/web/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/web/assets/typst-DHCkPAjA.js +1 -0
- package/dist/web/assets/v-BcVCzyr7.js +1 -0
- package/dist/web/assets/vala-CsfeWuGM.js +1 -0
- package/dist/web/assets/vb-D17OF-Vu.js +1 -0
- package/dist/web/assets/venice-CeIHfrIB.svg +4 -0
- package/dist/web/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/web/assets/vesper-DU1UobuO.js +1 -0
- package/dist/web/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/web/assets/viml-CJc9bBzg.js +1 -0
- package/dist/web/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/web/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/web/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/web/assets/vue-DnHKYNfI.js +1 -0
- package/dist/web/assets/vue-html-CChd_i61.js +1 -0
- package/dist/web/assets/vue-vine-8moa0y9V.js +1 -0
- package/dist/web/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/web/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/web/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/web/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/web/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/web/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/web/assets/wit-5i3qLPDT.js +1 -0
- package/dist/web/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/web/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/web/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/web/assets/xychartDiagram-PRI3JC2R-DDPGTO3C.js +7 -0
- package/dist/web/assets/yaml-Buea-lGh.js +1 -0
- package/dist/web/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/web/assets/zig-VOosw3JB.js +1 -0
- package/dist/web/index.html +20 -0
- package/package.json +99 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Shella
|
|
2
|
+
|
|
3
|
+
Self-hosted AI coding agents. Access from your phone.
|
|
4
|
+
|
|
5
|
+
Run multiple AI agents in parallel, persist sessions across devices, and manage everything from a touch-friendly mobile interface.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### npx (Developers)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @bytespell/shella
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Opens at `http://localhost:3067`. Access from your phone using the LAN IP shown.
|
|
16
|
+
|
|
17
|
+
### Docker (Homelab)
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
# docker-compose.yml
|
|
21
|
+
services:
|
|
22
|
+
shella:
|
|
23
|
+
image: ghcr.io/bytespell/shella
|
|
24
|
+
ports:
|
|
25
|
+
- '3067:3067'
|
|
26
|
+
volumes:
|
|
27
|
+
- /path/to/projects:/project
|
|
28
|
+
- shella-data:/root/.config/shella
|
|
29
|
+
restart: unless-stopped
|
|
30
|
+
|
|
31
|
+
volumes:
|
|
32
|
+
shella-data:
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
docker compose up -d
|
|
37
|
+
# Access at http://<server-ip>:3067
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Parallel agents** - Run multiple agents on different tasks simultaneously
|
|
43
|
+
- **Session persistence** - Sessions survive restarts, pick up from any device
|
|
44
|
+
- **Cross-device sync** - Same windows, same state, real-time sync
|
|
45
|
+
- **Mobile-first** - Touch-optimized for iOS Safari
|
|
46
|
+
|
|
47
|
+
## CLI Options
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
shella # Start with defaults
|
|
51
|
+
shella --port 3000 # Custom web port
|
|
52
|
+
shella --opencode-port 5000 # Custom OpenCode port
|
|
53
|
+
shella --no-open # Don't open browser
|
|
54
|
+
shella --help # Show help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Install dependencies
|
|
61
|
+
npm install
|
|
62
|
+
|
|
63
|
+
# Start dev servers (Vite + Express + OpenCode)
|
|
64
|
+
npm run start:dev
|
|
65
|
+
|
|
66
|
+
# Build for production
|
|
67
|
+
npm run build
|
|
68
|
+
|
|
69
|
+
# Test production build locally
|
|
70
|
+
node bin/cli.js --no-open
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
- Node.js 20.19+ or 22.12+
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { createOpencodeServer } from '@opencode-ai/sdk';
|
|
4
|
+
import { networkInterfaces } from 'os';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { createConnection } from 'net';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const VERSION = '0.1.0';
|
|
11
|
+
/**
|
|
12
|
+
* Check if a port is available
|
|
13
|
+
*/
|
|
14
|
+
async function isPortAvailable(port) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const socket = createConnection({ port, host: '127.0.0.1' });
|
|
17
|
+
socket.setTimeout(1000);
|
|
18
|
+
socket.on('connect', () => {
|
|
19
|
+
socket.destroy();
|
|
20
|
+
resolve(false); // Port is in use
|
|
21
|
+
});
|
|
22
|
+
socket.on('timeout', () => {
|
|
23
|
+
socket.destroy();
|
|
24
|
+
resolve(true); // Port is available
|
|
25
|
+
});
|
|
26
|
+
socket.on('error', () => {
|
|
27
|
+
resolve(true); // Port is available (connection refused)
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the LAN IP address for mobile access
|
|
33
|
+
*/
|
|
34
|
+
function getLanIp() {
|
|
35
|
+
const interfaces = networkInterfaces();
|
|
36
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
37
|
+
// Skip loopback, docker, veth, bridge interfaces
|
|
38
|
+
if (/^(lo|docker|veth|br-|virbr)/.test(name))
|
|
39
|
+
continue;
|
|
40
|
+
for (const addr of addrs || []) {
|
|
41
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
42
|
+
return addr.address;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return 'localhost';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Print a nice boxed message with the access URL
|
|
50
|
+
*/
|
|
51
|
+
function printAccessBox(lanIp, port) {
|
|
52
|
+
const url = `http://${lanIp}:${port}`;
|
|
53
|
+
const urlLine = ` ${url}`;
|
|
54
|
+
const width = Math.max(44, urlLine.length + 4);
|
|
55
|
+
const border = '-'.repeat(width);
|
|
56
|
+
const pad = (s) => s + ' '.repeat(width - s.length);
|
|
57
|
+
console.log(`
|
|
58
|
+
+${border}+
|
|
59
|
+
|${pad('')}|
|
|
60
|
+
|${pad(' Access from any device:')}|
|
|
61
|
+
|${pad('')}|
|
|
62
|
+
|${pad(urlLine)}|
|
|
63
|
+
|${pad('')}|
|
|
64
|
+
+${border}+
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
let opencodeServer = null;
|
|
68
|
+
let expressProcess = null;
|
|
69
|
+
async function startCommand(options) {
|
|
70
|
+
const port = parseInt(options.port);
|
|
71
|
+
const opencodePort = parseInt(options.opencodePort);
|
|
72
|
+
console.log(`Shella v${VERSION}\n`);
|
|
73
|
+
// Check if ports are available
|
|
74
|
+
const webPortAvailable = await isPortAvailable(port);
|
|
75
|
+
const ocPortAvailable = await isPortAvailable(opencodePort);
|
|
76
|
+
if (!webPortAvailable) {
|
|
77
|
+
console.error(`Error: Port ${port} is already in use.`);
|
|
78
|
+
console.error(`\nTry one of these:`);
|
|
79
|
+
console.error(` - Stop the process using port ${port}`);
|
|
80
|
+
console.error(` - Use a different port: shella --port 3070`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
if (!ocPortAvailable) {
|
|
84
|
+
console.error(`Error: Port ${opencodePort} is already in use (OpenCode port).`);
|
|
85
|
+
console.error(`\nThis usually means OpenCode is already running.`);
|
|
86
|
+
console.error(`\nTry one of these:`);
|
|
87
|
+
console.error(` - Kill the existing OpenCode: pkill -f "opencode serve"`);
|
|
88
|
+
console.error(` - Use a different port: shella --opencode-port 4097`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// Start OpenCode server using the bundled binary
|
|
92
|
+
console.log('Starting OpenCode...');
|
|
93
|
+
try {
|
|
94
|
+
opencodeServer = await createOpencodeServer({
|
|
95
|
+
port: opencodePort,
|
|
96
|
+
timeout: 30000,
|
|
97
|
+
});
|
|
98
|
+
console.log(` OpenCode ready (port ${opencodePort})`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
102
|
+
console.error(`\nFailed to start OpenCode: ${message}`);
|
|
103
|
+
if (message.includes('port')) {
|
|
104
|
+
console.error(`\nThe port ${opencodePort} may be in use. Try:`);
|
|
105
|
+
console.error(` shella --opencode-port 4097`);
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
// Start Express server in production mode
|
|
110
|
+
console.log('Starting Shella server...');
|
|
111
|
+
const serverPath = path.join(__dirname, '..', 'dist', 'server', 'index.js');
|
|
112
|
+
expressProcess = spawn('node', [serverPath], {
|
|
113
|
+
env: {
|
|
114
|
+
...process.env,
|
|
115
|
+
NODE_ENV: 'production',
|
|
116
|
+
PORT: String(port),
|
|
117
|
+
},
|
|
118
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
119
|
+
});
|
|
120
|
+
// Wait for server to be ready
|
|
121
|
+
await new Promise((resolve, reject) => {
|
|
122
|
+
const timeout = setTimeout(() => {
|
|
123
|
+
reject(new Error('Server startup timeout (10s). Check logs for errors.'));
|
|
124
|
+
}, 10000);
|
|
125
|
+
expressProcess.stdout?.on('data', (data) => {
|
|
126
|
+
const output = data.toString();
|
|
127
|
+
if (output.includes('Running on')) {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
resolve();
|
|
130
|
+
}
|
|
131
|
+
// Forward output but strip the prefix since we add our own
|
|
132
|
+
process.stdout.write(output.replace(/\[shella-server\] /g, ' '));
|
|
133
|
+
});
|
|
134
|
+
expressProcess.stderr?.on('data', (data) => {
|
|
135
|
+
process.stderr.write(data);
|
|
136
|
+
});
|
|
137
|
+
expressProcess.on('error', (err) => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
reject(new Error(`Failed to spawn server: ${err.message}`));
|
|
140
|
+
});
|
|
141
|
+
expressProcess.on('exit', (code) => {
|
|
142
|
+
if (code !== 0 && code !== null) {
|
|
143
|
+
clearTimeout(timeout);
|
|
144
|
+
reject(new Error(`Server exited with code ${code}`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
console.log(` Shella ready (port ${port})`);
|
|
149
|
+
// Print access box
|
|
150
|
+
const lanIp = getLanIp();
|
|
151
|
+
printAccessBox(lanIp, port);
|
|
152
|
+
console.log('Press Ctrl+C to stop\n');
|
|
153
|
+
// Open browser if requested
|
|
154
|
+
if (options.open) {
|
|
155
|
+
const url = `http://localhost:${port}`;
|
|
156
|
+
const openCommand = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
157
|
+
spawn(openCommand, [url], { stdio: 'ignore', detached: true }).unref();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Graceful shutdown handler
|
|
162
|
+
*/
|
|
163
|
+
function shutdown() {
|
|
164
|
+
console.log('\nShutting down...');
|
|
165
|
+
if (expressProcess) {
|
|
166
|
+
expressProcess.kill('SIGTERM');
|
|
167
|
+
expressProcess = null;
|
|
168
|
+
}
|
|
169
|
+
if (opencodeServer) {
|
|
170
|
+
opencodeServer.close();
|
|
171
|
+
opencodeServer = null;
|
|
172
|
+
}
|
|
173
|
+
console.log('Goodbye!');
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
// Handle shutdown signals
|
|
177
|
+
process.on('SIGINT', shutdown);
|
|
178
|
+
process.on('SIGTERM', shutdown);
|
|
179
|
+
// CLI definition
|
|
180
|
+
program
|
|
181
|
+
.name('shella')
|
|
182
|
+
.description('Self-hosted AI coding agents. Access from your phone.')
|
|
183
|
+
.version(VERSION);
|
|
184
|
+
program
|
|
185
|
+
.command('start', { isDefault: true })
|
|
186
|
+
.description('Start Shella')
|
|
187
|
+
.option('-p, --port <port>', 'Web server port', '3067')
|
|
188
|
+
.option('--opencode-port <port>', 'OpenCode server port', '4096')
|
|
189
|
+
.option('--no-open', "Don't open browser automatically")
|
|
190
|
+
.action(startCommand);
|
|
191
|
+
program.parse();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { corsMiddleware } from './middleware/cors.js';
|
|
7
|
+
import windowsRoutes from './routes/windows.js';
|
|
8
|
+
import modelStateRoutes from './routes/model-state.js';
|
|
9
|
+
import logsRoutes from './routes/logs.js';
|
|
10
|
+
import proxyRoutes from './routes/proxy.js';
|
|
11
|
+
import { getDatabase } from './services/database.js';
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
// Runtime mode detection
|
|
14
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
15
|
+
// Port: 3067 in prod, 3068 in dev (Vite uses 3067 in dev)
|
|
16
|
+
const PORT = process.env.PORT || (isProd ? 3067 : 3068);
|
|
17
|
+
// Config directory: ~/.config/shella/
|
|
18
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'shella');
|
|
19
|
+
// Log file: ~/.config/shella/shella.log in prod, ./dev.log in dev
|
|
20
|
+
const LOG_FILE = isProd
|
|
21
|
+
? path.join(CONFIG_DIR, 'shella.log')
|
|
22
|
+
: path.resolve(process.cwd(), 'dev.log');
|
|
23
|
+
/**
|
|
24
|
+
* Ensure config directory exists
|
|
25
|
+
*/
|
|
26
|
+
function ensureConfigDir() {
|
|
27
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
+
console.log(`[shella-server] Created config directory: ${CONFIG_DIR}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const app = express();
|
|
33
|
+
// CORS - allow all origins for mobile debugging
|
|
34
|
+
app.use(corsMiddleware);
|
|
35
|
+
// JSON body parsing
|
|
36
|
+
app.use(express.json());
|
|
37
|
+
// Health check
|
|
38
|
+
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
|
|
39
|
+
// Mount API routes
|
|
40
|
+
app.use('/__shella', windowsRoutes);
|
|
41
|
+
app.use('/__model-state', modelStateRoutes);
|
|
42
|
+
app.use('/__logs', logsRoutes);
|
|
43
|
+
app.use('/api', proxyRoutes);
|
|
44
|
+
// Production: serve static files from dist/web/
|
|
45
|
+
if (isProd) {
|
|
46
|
+
const webDir = path.join(__dirname, '..', 'web');
|
|
47
|
+
if (fs.existsSync(webDir)) {
|
|
48
|
+
console.log(`[shella-server] Serving static files from ${webDir}`);
|
|
49
|
+
app.use(express.static(webDir));
|
|
50
|
+
// SPA fallback: serve index.html for non-API routes
|
|
51
|
+
// Express 5 requires named wildcards, use {*path} syntax
|
|
52
|
+
app.get('{*path}', (req, res, next) => {
|
|
53
|
+
// Skip API routes
|
|
54
|
+
if (req.path.startsWith('/api') ||
|
|
55
|
+
req.path.startsWith('/__shella') ||
|
|
56
|
+
req.path.startsWith('/__model-state') ||
|
|
57
|
+
req.path.startsWith('/__logs') ||
|
|
58
|
+
req.path.startsWith('/health')) {
|
|
59
|
+
return next();
|
|
60
|
+
}
|
|
61
|
+
res.sendFile(path.join(webDir, 'index.html'));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.warn(`[shella-server] Warning: ${webDir} not found. Static serving disabled.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start the server
|
|
70
|
+
*/
|
|
71
|
+
async function start() {
|
|
72
|
+
// Ensure config directory exists in prod
|
|
73
|
+
if (isProd) {
|
|
74
|
+
ensureConfigDir();
|
|
75
|
+
}
|
|
76
|
+
// Initialize database before starting server
|
|
77
|
+
await getDatabase();
|
|
78
|
+
// Clear/create log file on server start
|
|
79
|
+
const logDir = path.dirname(LOG_FILE);
|
|
80
|
+
if (!fs.existsSync(logDir)) {
|
|
81
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(LOG_FILE, `--- Shella server started ${new Date().toISOString()} ---\n`);
|
|
84
|
+
app.listen(PORT, () => {
|
|
85
|
+
console.log(`[shella-server] Running on http://localhost:${PORT}`);
|
|
86
|
+
console.log(`[shella-server] Mode: ${isProd ? 'production' : 'development'}`);
|
|
87
|
+
console.log(`[shella-server] Logging to ${LOG_FILE}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
start().catch((err) => {
|
|
91
|
+
console.error('[shella-server] Failed to start:', err);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
export { CONFIG_DIR };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
2
|
+
// Allow all origins for mobile debugging over LAN
|
|
3
|
+
export const corsMiddleware = cors({
|
|
4
|
+
origin: true,
|
|
5
|
+
credentials: true,
|
|
6
|
+
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
7
|
+
allowedHeaders: ['Content-Type', 'X-Client-Id', 'Accept'],
|
|
8
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const LOG_FILE = path.resolve(process.cwd(), 'dev.log');
|
|
5
|
+
const router = Router();
|
|
6
|
+
// POST /__logs - receive browser console logs
|
|
7
|
+
router.post('/', (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const { level, args } = req.body;
|
|
10
|
+
const prefix = level === 'error' ? '🔴' : level === 'warn' ? '🟡' : '🌐';
|
|
11
|
+
const timestamp = new Date().toISOString().slice(11, 23); // HH:MM:SS.mmm
|
|
12
|
+
const message = args
|
|
13
|
+
.map((a) => (typeof a === 'object' ? JSON.stringify(a) : String(a)))
|
|
14
|
+
.join(' ');
|
|
15
|
+
const line = `${timestamp} ${prefix} ${message}`;
|
|
16
|
+
// Log to terminal
|
|
17
|
+
console.log(line);
|
|
18
|
+
// Append to file
|
|
19
|
+
fs.appendFileSync(LOG_FILE, line + '\n');
|
|
20
|
+
res.sendStatus(200);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
const fallback = `${new Date().toISOString().slice(11, 23)} 🌐 [parse error] ${JSON.stringify(req.body)}`;
|
|
24
|
+
console.log(fallback);
|
|
25
|
+
fs.appendFileSync(LOG_FILE, fallback + '\n');
|
|
26
|
+
res.sendStatus(200);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
export default router;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { STATE_DIR } from '../services/database.js';
|
|
5
|
+
const MODEL_STATE_FILE = path.join(STATE_DIR, 'model.json');
|
|
6
|
+
function ensureStateDir() {
|
|
7
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
8
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function readModelState() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(MODEL_STATE_FILE)) {
|
|
14
|
+
return { recent: [], favorite: [] };
|
|
15
|
+
}
|
|
16
|
+
const data = fs.readFileSync(MODEL_STATE_FILE, 'utf-8');
|
|
17
|
+
const parsed = JSON.parse(data);
|
|
18
|
+
return {
|
|
19
|
+
recent: Array.isArray(parsed.recent) ? parsed.recent : [],
|
|
20
|
+
favorite: Array.isArray(parsed.favorite) ? parsed.favorite : [],
|
|
21
|
+
variant: typeof parsed.variant === 'object' && parsed.variant !== null ? parsed.variant : {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error('[model-state] Failed to read model state:', err);
|
|
26
|
+
return { recent: [], favorite: [] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeModelState(state) {
|
|
30
|
+
try {
|
|
31
|
+
ensureStateDir();
|
|
32
|
+
fs.writeFileSync(MODEL_STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error('[model-state] Failed to write model state:', err);
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function toggleFavorite(model) {
|
|
40
|
+
const state = readModelState();
|
|
41
|
+
const exists = state.favorite.some((f) => f.providerID === model.providerID && f.modelID === model.modelID);
|
|
42
|
+
if (exists) {
|
|
43
|
+
// Remove from favorites
|
|
44
|
+
state.favorite = state.favorite.filter((f) => f.providerID !== model.providerID || f.modelID !== model.modelID);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Add to favorites
|
|
48
|
+
state.favorite = [model, ...state.favorite];
|
|
49
|
+
}
|
|
50
|
+
writeModelState(state);
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
const router = Router();
|
|
54
|
+
// GET /__model-state - read model state
|
|
55
|
+
router.get('/', (_req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const state = readModelState();
|
|
58
|
+
res.json({ recent: state.recent, favorite: state.favorite });
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error('[model-state] Failed to get model state:', err);
|
|
62
|
+
res.status(500).json({ error: 'Failed to read model state' });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// POST /__model-state/favorite - toggle favorite
|
|
66
|
+
router.post('/favorite', (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const model = req.body;
|
|
69
|
+
if (!model.providerID || !model.modelID) {
|
|
70
|
+
res.status(400).json({ error: 'providerID and modelID required' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const state = toggleFavorite(model);
|
|
74
|
+
res.json({ recent: state.recent, favorite: state.favorite });
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error('[model-state] Failed to toggle favorite:', err);
|
|
78
|
+
res.status(500).json({ error: 'Failed to update model state' });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
export default router;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
const OPENCODE_URL = process.env.OPENCODE_URL || 'http://localhost:4096';
|
|
3
|
+
const router = Router();
|
|
4
|
+
// Routes that should return 202 immediately without waiting for OpenCode response.
|
|
5
|
+
// These are "fire-and-forget" - the browser doesn't need the response body,
|
|
6
|
+
// and waiting would cause the UI to hang while OpenCode processes the request.
|
|
7
|
+
const FIRE_AND_FORGET_PATTERNS = [
|
|
8
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/shell$/ },
|
|
9
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/command$/ },
|
|
10
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/abort$/ },
|
|
11
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/revert$/ },
|
|
12
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/unrevert$/ },
|
|
13
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/summarize$/ },
|
|
14
|
+
// Permission endpoints (old and new SDK versions)
|
|
15
|
+
{ method: 'POST', pattern: /^\/session\/[^/]+\/permissions\/[^/]+$/ },
|
|
16
|
+
{ method: 'POST', pattern: /^\/permission\/[^/]+\/reply$/ },
|
|
17
|
+
];
|
|
18
|
+
const isFireAndForget = (method, path) => {
|
|
19
|
+
// Strip query string for matching
|
|
20
|
+
const pathWithoutQuery = path.split('?')[0];
|
|
21
|
+
return FIRE_AND_FORGET_PATTERNS.some((route) => route.method === method && route.pattern.test(pathWithoutQuery));
|
|
22
|
+
};
|
|
23
|
+
const buildTargetUrl = (req) => {
|
|
24
|
+
const url = new URL(req.url, OPENCODE_URL);
|
|
25
|
+
return url;
|
|
26
|
+
};
|
|
27
|
+
const buildHeaders = (req, includeBody) => {
|
|
28
|
+
const headers = {};
|
|
29
|
+
if (typeof req.headers['accept'] === 'string') {
|
|
30
|
+
headers['accept'] = req.headers['accept'];
|
|
31
|
+
}
|
|
32
|
+
if (includeBody) {
|
|
33
|
+
headers['content-type'] = 'application/json';
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
};
|
|
37
|
+
const hasBody = (method) => {
|
|
38
|
+
return ['POST', 'PUT', 'PATCH'].includes(method);
|
|
39
|
+
};
|
|
40
|
+
// Fire-and-forget: send request to OpenCode but return 202 immediately
|
|
41
|
+
const fireAndForget = (req, label) => {
|
|
42
|
+
const url = buildTargetUrl(req);
|
|
43
|
+
const headers = buildHeaders(req, hasBody(req.method));
|
|
44
|
+
fetch(url, {
|
|
45
|
+
method: req.method,
|
|
46
|
+
headers,
|
|
47
|
+
body: hasBody(req.method) ? JSON.stringify(req.body ?? {}) : undefined,
|
|
48
|
+
}).catch((error) => {
|
|
49
|
+
console.error(`[proxy] ${label} fire-and-forget error:`, error);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
// Stream proxy: forward request and stream response back
|
|
53
|
+
const streamProxy = async (req, res) => {
|
|
54
|
+
const url = buildTargetUrl(req);
|
|
55
|
+
const headers = buildHeaders(req, hasBody(req.method));
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
method: req.method,
|
|
59
|
+
headers,
|
|
60
|
+
body: hasBody(req.method) ? JSON.stringify(req.body ?? {}) : undefined,
|
|
61
|
+
});
|
|
62
|
+
// Forward status
|
|
63
|
+
res.status(response.status);
|
|
64
|
+
// Forward headers (except content-length which may be wrong after buffering)
|
|
65
|
+
response.headers.forEach((value, key) => {
|
|
66
|
+
if (key.toLowerCase() !== 'content-length') {
|
|
67
|
+
res.setHeader(key, value);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Handle 204 No Content
|
|
71
|
+
if (response.status === 204 || !response.body) {
|
|
72
|
+
res.end();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Check if this is SSE
|
|
76
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
77
|
+
const isSSE = contentType.includes('text/event-stream');
|
|
78
|
+
if (isSSE) {
|
|
79
|
+
// SSE: stream chunks as they arrive
|
|
80
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
81
|
+
res.setHeader('Connection', 'keep-alive');
|
|
82
|
+
const reader = response.body.getReader();
|
|
83
|
+
try {
|
|
84
|
+
while (true) {
|
|
85
|
+
const { done, value } = await reader.read();
|
|
86
|
+
if (done)
|
|
87
|
+
break;
|
|
88
|
+
res.write(value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
reader.releaseLock();
|
|
93
|
+
}
|
|
94
|
+
res.end();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Non-SSE: buffer and send
|
|
98
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
99
|
+
res.send(buffer);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(`[proxy] ${req.method} ${req.url} error:`, error);
|
|
104
|
+
res.status(502).json({ error: 'Failed to proxy request to OpenCode' });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
// Log all proxied requests
|
|
108
|
+
router.use((req, _res, next) => {
|
|
109
|
+
console.log(`[proxy] ${req.method} ${req.url}`);
|
|
110
|
+
next();
|
|
111
|
+
});
|
|
112
|
+
// Main proxy handler
|
|
113
|
+
router.use('/', async (req, res) => {
|
|
114
|
+
if (isFireAndForget(req.method, req.url)) {
|
|
115
|
+
const label = req.url.split('?')[0];
|
|
116
|
+
fireAndForget(req, label);
|
|
117
|
+
res.status(202).end();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await streamProxy(req, res);
|
|
121
|
+
});
|
|
122
|
+
export default router;
|