sliccy 6.87.3 → 6.88.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 +1 -1
- package/dist/node-server/hostfs.d.ts +58 -0
- package/dist/node-server/hostfs.js +257 -0
- package/dist/node-server/index.js +10 -0
- package/dist/node-server/runtime-flags.d.ts +22 -0
- package/dist/node-server/runtime-flags.js +78 -2
- package/dist/ui/.vite/manifest.json +503 -422
- package/dist/ui/assets/CLAUDE-4LHXSyPA.js +1 -0
- package/dist/ui/assets/CLAUDE-Bio4HPGK.js +1 -0
- package/dist/ui/assets/MEMORY-6FXnIBfJ.js +135 -0
- package/dist/ui/assets/SKILL-BKUxKc7f.js +296 -0
- package/dist/ui/assets/SKILL-BKyxMoME.js +1 -0
- package/dist/ui/assets/SKILL-BcELX5TL.js +1 -0
- package/dist/ui/assets/SKILL-Bd5ykLEh.js +1 -0
- package/dist/ui/assets/SKILL-Bt-RhG0L.js +1 -0
- package/dist/ui/assets/SKILL-CED8RXQP.js +69 -0
- package/dist/ui/assets/SKILL-CNdAIhbb.js +1 -0
- package/dist/ui/assets/SKILL-CcMg6eqt.js +1 -0
- package/dist/ui/assets/SKILL-Ch-cOl61.js +1 -0
- package/dist/ui/assets/SKILL-CkfptFag.js +132 -0
- package/dist/ui/assets/SKILL-Ckg2XbSL.js +114 -0
- package/dist/ui/assets/SKILL-D40-t9w0.js +57 -0
- package/dist/ui/assets/SKILL-DFKKFkzD.js +96 -0
- package/dist/ui/assets/SKILL-DJ815vTN.js +62 -0
- package/dist/ui/assets/SKILL-DLXzwsY4.js +1 -0
- package/dist/ui/assets/SKILL-DXIigmDy.js +35 -0
- package/dist/ui/assets/SKILL-DfSluXpV.js +385 -0
- package/dist/ui/assets/SKILL-DhKtVqb-.js +1 -0
- package/dist/ui/assets/SKILL-FnBxPcDX.js +1 -0
- package/dist/ui/assets/SKILL-HEHb9hIB.js +1 -0
- package/dist/ui/assets/SKILL-i1NMDKwT.js +1 -0
- package/dist/ui/assets/SKILL-oOIS8LAm.js +1 -0
- package/dist/ui/assets/{account-store-Cy3TJKnu.js → account-store-BLu0_ipL.js} +2 -2
- package/dist/ui/assets/{account-store-DysucxMw.js → account-store-l8BmmoRx.js} +4 -4
- package/dist/ui/assets/adobe-Caluo3Vg.js +2 -0
- package/dist/ui/assets/adobe-Cj2iYsHq.js +1 -0
- package/dist/ui/assets/{angular-html-Cm4-xod0.js → angular-html-aPuBvESs.js} +1 -1
- package/dist/ui/assets/{angular-ts-Dopuir71.js → angular-ts-ocjSXnvf.js} +1 -1
- package/dist/ui/assets/anthropic-messages-CpSojOI8.js +39 -0
- package/dist/ui/assets/anthropic-messages-DbPJ2ji9.js +39 -0
- package/dist/ui/assets/api-endpoint-C00IT5T9.js +1 -0
- package/dist/ui/assets/api-endpoint-GRCyib6X.js +1 -0
- package/dist/ui/assets/{apply-layout-TIXzvNTN.js → apply-layout-lJ2Uyc3p.js} +1 -1
- package/dist/ui/assets/{apps-e--FORGt.js → apps-BRi5S2KV.js} +1 -1
- package/dist/ui/assets/{astro-E_b8esPp.js → astro-go1PgQEf.js} +1 -1
- package/dist/ui/assets/auto-mount-table-CdyPAwVL.js +1 -0
- package/dist/ui/assets/{azure-openai-C5josrBb.js → azure-openai-DtPDwGAY.js} +2 -2
- package/dist/ui/assets/azure-openai-responses-BcoOaRCx.js +1 -0
- package/dist/ui/assets/azure-openai-responses-DUMDrZ29.js +1 -0
- package/dist/ui/assets/{azure-openai-mDyNL8OK.js → azure-openai-xFuHu_hU.js} +2 -2
- package/dist/ui/assets/backend-hostfs-Com-zF5W.js +1 -0
- package/dist/ui/assets/backend-hostfs-bQQx-S0W.js +1 -0
- package/dist/ui/assets/bedrock-camp-BcJEC3Pg.js +1 -0
- package/dist/ui/assets/bedrock-camp-LeceEQbU.js +1 -0
- package/dist/ui/assets/bedrock-camp-compat-CYnC9BQI.js +1 -0
- package/dist/ui/assets/bedrock-camp-compat-CgBMwna8.js +1 -0
- package/dist/ui/assets/{blade-mj2TcBTp.js → blade-DPPr6MF0.js} +1 -1
- package/dist/ui/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/dist/ui/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/dist/ui/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/dist/ui/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/dist/ui/assets/cerebras-BKhMaWSl.js +1 -0
- package/dist/ui/assets/cerebras-CCLa0xVH.js +1 -0
- package/dist/ui/assets/chime-CPc6qSh5.js +1 -0
- package/dist/ui/assets/claude-model-version-Csn7RPVg.js +1 -0
- package/dist/ui/assets/claude-model-version-DoVrYsKi.js +2 -0
- package/dist/ui/assets/{client-BTUV7dbh.js → client-DVC0x03n.js} +1 -1
- package/dist/ui/assets/{composer-speech-SNeD5SER.js → composer-speech-Cy4g3lh3.js} +1 -1
- package/dist/ui/assets/connect-llm-FevTa-aV.js +561 -0
- package/dist/ui/assets/{connect-surface-DqU2NC2c.js → connect-surface-T7iCJFeB.js} +1 -1
- package/dist/ui/assets/cpp-Ct9pQPqT.js +1 -0
- package/dist/ui/assets/{css-vJJohHMc.js → css-DlBQoFxe.js} +1 -1
- package/dist/ui/assets/discovery-preference-__0HPYJC.js +1 -0
- package/dist/ui/assets/dist-B4LJzMVB.js +1547 -0
- package/dist/ui/assets/{ensure-speech-assets-DvFwJX-M.js → ensure-speech-assets-CKwQ54O1.js} +1 -1
- package/dist/ui/assets/float-topology-DiK91BlA.js +1 -0
- package/dist/ui/assets/{follower-navigate-watcher-CNC7ybEv.js → follower-navigate-watcher-Dz-52eZY.js} +1 -1
- package/dist/ui/assets/fs-1btyYJVn.js +1 -0
- package/dist/ui/assets/fs-BYtJQhvh.js +2 -0
- package/dist/ui/assets/github-BHL_hKU_.js +2 -0
- package/dist/ui/assets/github-copilot-CZorsYUj.js +1 -0
- package/dist/ui/assets/github-copilot-DVmzgGYz.js +2 -0
- package/dist/ui/assets/github-fX2kuECo.js +1 -0
- package/dist/ui/assets/{github-install-CFJLPySf.js → github-install-DVzD1asQ.js} +1 -1
- package/dist/ui/assets/{github-zip-C7dhlRG8.js → github-zip-5ekKl7Z0.js} +1 -1
- package/dist/ui/assets/{go-BJwz_mda.js → go-rLFTqkRN.js} +1 -1
- package/dist/ui/assets/google-generative-ai-Dxo91M3n.js +1 -0
- package/dist/ui/assets/google-generative-ai-Rjh7hNBV.js +1 -0
- package/dist/ui/assets/{google-shared-BMJ_JErv.js → google-shared-CzJgTu-P.js} +3 -3
- package/dist/ui/assets/{google-shared-YqcyR4cm.js → google-shared-DQJPCU3d.js} +3 -3
- package/dist/ui/assets/google-vertex-61Jj0dk6.js +1 -0
- package/dist/ui/assets/google-vertex-DEwkNZJm.js +1 -0
- package/dist/ui/assets/{haml-DboNpPfQ.js → haml-Bqgi1Cau.js} +1 -1
- package/dist/ui/assets/{handlebars-Bv_jLwHk.js → handlebars-JgcNzdBj.js} +1 -1
- package/dist/ui/assets/{hear-D33CHppO.js → hear-BqNAK8-P.js} +1 -1
- package/dist/ui/assets/{horizon-bright-Br1oVSNq.js → horizon-bright-DSNQnXHK.js} +1 -1
- package/dist/ui/assets/{host-command-G_JIdBPR.js → host-command-C3cIKdbE.js} +1 -1
- package/dist/ui/assets/{html-BLkwJ0Yd.js → html-BsvMqTlf.js} +1 -1
- package/dist/ui/assets/{html-derivative-B3p5VrMH.js → html-derivative-D9FvRNj4.js} +1 -1
- package/dist/ui/assets/icon-example-LUkiEXyS.js +111 -0
- package/dist/ui/assets/{jinja-BeFOxtqS.js → jinja-CixBDh_U.js} +1 -1
- package/dist/ui/assets/{js-realm-worker-BcijiBMH.js → js-realm-worker-iDrigwwJ.js} +1 -1
- package/dist/ui/assets/jsh-runtime-extensions-DpVq60Gw.js +1 -0
- package/dist/ui/assets/julia-B5NbrcUp.js +1 -0
- package/dist/ui/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/dist/ui/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/dist/ui/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/dist/ui/assets/{kernel-worker-BiaHa-Mz.js → kernel-worker-BdKtpNzV.js} +613 -4394
- package/dist/ui/assets/legacy-api-aliases-BnRqMAef.js +1 -0
- package/dist/ui/assets/legacy-api-aliases-oJPAvhVV.js +1 -0
- package/dist/ui/assets/{lick-urls-AW0BSaf4.js → lick-urls-DcXOqXGm.js} +1 -1
- package/dist/ui/assets/{lick-ws-bridge-DiPcVRfN.js → lick-ws-bridge-CnkYvHKI.js} +1 -1
- package/dist/ui/assets/{lick-ws-bridge-CWRES2H2.js → lick-ws-bridge-DOJW_mZp.js} +1 -1
- package/dist/ui/assets/{local-llm-CQE3URQQ.js → local-llm-BoLsVU6D.js} +1 -1
- package/dist/ui/assets/{local-llm-DVCKFUl_.js → local-llm-aZ3BO7wK.js} +1 -1
- package/dist/ui/assets/{main-DQS8QHXU.js → main-CSh1z5qo.js} +3 -3
- package/dist/ui/assets/{marko-C3klgjEa.js → marko-DYWPPmCl.js} +1 -1
- package/dist/ui/assets/mdc-Cno1UUUH.js +1 -0
- package/dist/ui/assets/mistral-conversations-BtaPxrK1.js +4 -0
- package/dist/ui/assets/mistral-conversations-CDLVw-9i.js +4 -0
- package/dist/ui/assets/{transform-messages-DEFauvrq.js → models-4WTk3EBD.js} +1 -1
- package/dist/ui/assets/{transform-messages-N_eMsP2M.js → models-BSkXF4T6.js} +1 -1
- package/dist/ui/assets/monokai-B_bV9PQ9.js +1 -0
- package/dist/ui/assets/mount-BCaA8NF3.js +1 -0
- package/dist/ui/assets/mount-ICXjya6b.js +2 -0
- package/dist/ui/assets/{mount-bridge-client-4blXuJO1.js → mount-bridge-client-DZE6sHdr.js} +1 -1
- package/dist/ui/assets/mount-recovery-CtadSXiq.js +2 -0
- package/dist/ui/assets/new-session-CCcPVim0.js +2 -0
- package/dist/ui/assets/oauth-bootstrap-CJ7pyk8I.js +2 -0
- package/dist/ui/assets/oauth-service-CTntqv0R.js +1 -0
- package/dist/ui/assets/oauth-service-Uk8UIb05.js +1 -0
- package/dist/ui/assets/openai-codex-C7WXAsyq.js +1 -0
- package/dist/ui/assets/openai-codex-DJHh6O0O.js +1 -0
- package/dist/ui/assets/openai-codex-responses-CPOJeFUY.js +7 -0
- package/dist/ui/assets/openai-codex-responses-jQR19NyX.js +7 -0
- package/dist/ui/assets/openai-completions-DGOcowq8.js +5 -0
- package/dist/ui/assets/openai-completions-DGZFJo5D.js +5 -0
- package/dist/ui/assets/openai-responses-CDTdSIhv.js +1 -0
- package/dist/ui/assets/openai-responses-bnETPgx9.js +1 -0
- package/dist/ui/assets/{openai-responses-shared-DxcsuzHF.js → openai-responses-shared-ChgvRZ74.js} +5 -5
- package/dist/ui/assets/{openai-responses-shared-DhnqxYC9.js → openai-responses-shared-DqJbHrek.js} +5 -5
- package/dist/ui/assets/openrouter-CIXJV3ar.js +1 -0
- package/dist/ui/assets/openrouter-CbIgawBs.js +1 -0
- package/dist/ui/assets/{openrouter-oauth-DvOb1QMV.js → openrouter-oauth-CfwEmxEv.js} +1 -1
- package/dist/ui/assets/{openrouter-oauth-Dy4Qqt06.js → openrouter-oauth-ZehYXklO.js} +1 -1
- package/dist/ui/assets/{page-approval-service-Bbc3_KdK.js → page-approval-service-BsXoPdFJ.js} +1 -1
- package/dist/ui/assets/panel-rpc-handlers-Bg0_q2j9.js +2 -0
- package/dist/ui/assets/{panelize-shell-BRldzPxv.js → panelize-shell-DG3fFAS4.js} +1 -1
- package/dist/ui/assets/patterns-ChiGbijt.js +171 -0
- package/dist/ui/assets/php-m5Ug_San.js +1 -0
- package/dist/ui/assets/port-bridge-client-BhmNRlRv.js +2 -0
- package/dist/ui/assets/port-bridge-client-Bz2N1tvn.js +1 -0
- package/dist/ui/assets/{provider-D2RXncrR.js → provider-1dH_J0Ky.js} +2 -2
- package/dist/ui/assets/{provider-Bk24TjUg.js → provider-CCer_iKN.js} +1 -1
- package/dist/ui/assets/{provider-settings-B5B0NtAl.js → provider-settings-DKXi1uSi.js} +1 -1
- package/dist/ui/assets/provider-settings-DZxqnVAO.js +2 -0
- package/dist/ui/assets/{provider-store-access-Cl5aZRx7.js → provider-store-access-BthQZUzO.js} +2 -2
- package/dist/ui/assets/provider-store-access-NzVfMvPw.js +1 -0
- package/dist/ui/assets/providers-D8GPTe3s.js +2 -0
- package/dist/ui/assets/providers-DdfpdKUf.js +1 -0
- package/dist/ui/assets/proxied-fetch-BGeNrvQ4.js +2 -0
- package/dist/ui/assets/proxied-fetch-Co-pb0x0.js +1 -0
- package/dist/ui/assets/{pug-BPcX_o8k.js → pug-B4bk2kWf.js} +1 -1
- package/dist/ui/assets/quick-llm-DscHMm6M.js +1 -0
- package/dist/ui/assets/r-C5FwQflr.js +1 -0
- package/dist/ui/assets/{recovery-screen-MHXsY2RD.js → recovery-screen-CmxX9RlA.js} +1 -1
- package/dist/ui/assets/red-DX1BQOs8.js +1 -0
- package/dist/ui/assets/{redirect-uri-B1SxiY-b.js → redirect-uri-CSXbRzUU.js} +1 -1
- package/dist/ui/assets/rose-pine-aMsaMD1D.js +1 -0
- package/dist/ui/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/dist/ui/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/dist/ui/assets/{scss-0nEf0IT-.js → scss-BaZL6t2Y.js} +1 -1
- package/dist/ui/assets/secret-env-CjwX7z7b.js +1 -0
- package/dist/ui/assets/secret-topology-0AWfFTh5.js +1 -0
- package/dist/ui/assets/{secrets-bridge-client-cWDAnFqs.js → secrets-bridge-client-DCQ44RyR.js} +1 -1
- package/dist/ui/assets/{session-freezer-CnFBCb6j.js → session-freezer-cQJKe1j_.js} +3 -3
- package/dist/ui/assets/{setup-feature-flags-remote-Dlcwmn-_.js → setup-feature-flags-remote-BxfewGbb.js} +1 -1
- package/dist/ui/assets/setup-standalone-prelude-Bv9X4-V2.js +2 -0
- package/dist/ui/assets/setup-sudo-B3tWN6O2.js +2 -0
- package/dist/ui/assets/{skills-C90t15zW.js → skills-C8Ojzeu6.js} +1 -1
- package/dist/ui/assets/{slicc-diff-DsGtkf0J.js → slicc-diff-DhGIKKqP.js} +1 -1
- package/dist/ui/assets/slicc-version-CaunkoU5.js +1 -0
- package/dist/ui/assets/smithy-Dgaz7aur.js +1 -0
- package/dist/ui/assets/solarized-dark-UdsmMDCL.js +1 -0
- package/dist/ui/assets/solarized-light-C1IUL_tW.js +1 -0
- package/dist/ui/assets/{speak-BDT9rGzg.js → speak-BSK3Souh.js} +1 -1
- package/dist/ui/assets/{sql-69VAgb2g.js → sql-DvarVTMJ.js} +1 -1
- package/dist/ui/assets/{src-DsDPrXOS.js → src-DXdqCrdV.js} +2 -2
- package/dist/ui/assets/{store-Cvg04_Rz.js → store-BPyCiwPs.js} +1 -1
- package/dist/ui/assets/{store-BmhLnay6.js → store-CGgqI527.js} +1 -1
- package/dist/ui/assets/style-guide-Bgzsk7bf.js +821 -0
- package/dist/ui/assets/{sudo-DYhM4jWf.js → sudo-1gsx4XWx.js} +2 -2
- package/dist/ui/assets/{svelte-B4PVQJ-7.js → svelte-D4FIy7cP.js} +1 -1
- package/dist/ui/assets/swift-CyEgAFGc.js +1 -0
- package/dist/ui/assets/temperature-support-B4x2xaWY.js +1 -0
- package/dist/ui/assets/temperature-support-Cpml9LfI.js +1 -0
- package/dist/ui/assets/transform-messages-DZg9zDcV.js +1 -0
- package/dist/ui/assets/transform-messages-XkZ1QnTb.js +1 -0
- package/dist/ui/assets/transformers-env-DPjdc2EQ.js +1 -0
- package/dist/ui/assets/tray-leader-CP11I609.js +1 -0
- package/dist/ui/assets/{tray-leave-runtime-BGfdX5KF.js → tray-leave-runtime-CN7V9V7O.js} +1 -1
- package/dist/ui/assets/{tray-runtime-config-B1LF8Kd4.js → tray-runtime-config-CVoPQrT2.js} +1 -1
- package/dist/ui/assets/tray-runtime-config-D7cmCcHJ.js +1 -0
- package/dist/ui/assets/ts-tags-C4Ifwy6r.js +1 -0
- package/dist/ui/assets/{upgrade-detection-Co2MZvYS.js → upgrade-detection-DHKcwE9c.js} +1 -1
- package/dist/ui/assets/vesper-DdrHHSXu.js +1 -0
- package/dist/ui/assets/{voice-reply-CnP5wEO_.js → voice-reply-BNX-jA0G.js} +1 -1
- package/dist/ui/assets/{vue-Dg0Kv3qp.js → vue-Djeyp8pE.js} +1 -1
- package/dist/ui/assets/{vue-vine-DdvC-EE2.js → vue-vine-By_xyXdL.js} +1 -1
- package/dist/ui/assets/{wc-attach-DsMfUpx8.js → wc-attach-CPoS-Z-g.js} +2 -2
- package/dist/ui/assets/{wc-extension-CL-LAE0W.js → wc-extension-vZctlNTg.js} +2 -2
- package/dist/ui/assets/wc-float-label-D7p-4xyd.js +1 -0
- package/dist/ui/assets/wc-follower-BgCWId1J.js +17 -0
- package/dist/ui/assets/wc-live-leqS7MUz.js +8 -0
- package/dist/ui/assets/wc-nav-CO_N4L3V.js +2 -0
- package/dist/ui/assets/wc-onboarding-B4o-Pdiq.js +2 -0
- package/dist/ui/assets/wc-placeholder-BTbGc9bq.js +4 -0
- package/dist/ui/assets/wc-settings-B4hsC2Um.js +51 -0
- package/dist/ui/assets/{wc-shell-DC5bovWA.js → wc-shell-B9UbR2P2.js} +3 -3
- package/dist/ui/assets/{wc-sprinkles-CDP31nXS.js → wc-sprinkles-Ds-rd0k7.js} +2 -2
- package/dist/ui/assets/wc-tray-BQls_HTm.js +2 -0
- package/dist/ui/assets/{wc-tray-scoops-B2RP8cgD.js → wc-tray-scoops-D8qwcovR.js} +1 -1
- package/dist/ui/assets/welcome-CyCMJDqG.js +621 -0
- package/dist/ui/assets/whisper-session-DpDs0sDL.js +2 -0
- package/dist/ui/assets/x_search-C-5p00-C.js +131 -0
- package/dist/ui/assets/xai-grok-DzMkuuG4.js +1 -0
- package/dist/ui/assets/xai-grok-kLSQtmUi.js +1 -0
- package/dist/ui/index.html +16 -14
- package/dist/ui/packages/webapp/index.html +16 -14
- package/dist/ui/slicc-diff.js +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/adobe-C19oqBaU.js +0 -1
- package/dist/ui/assets/adobe-Dv-gMc09.js +0 -2
- package/dist/ui/assets/anthropic-messages-BX4DpK10.js +0 -39
- package/dist/ui/assets/anthropic-messages-C-orWG7m.js +0 -39
- package/dist/ui/assets/azure-openai-responses-C2LleG-4.js +0 -1
- package/dist/ui/assets/azure-openai-responses-Df9YPxyp.js +0 -1
- package/dist/ui/assets/bedrock-camp-B0KF_4RS.js +0 -1
- package/dist/ui/assets/bedrock-camp-qpZqnqLa.js +0 -2
- package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +0 -1
- package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +0 -1
- package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +0 -1
- package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +0 -1
- package/dist/ui/assets/cerebras-CDHmZz_Y.js +0 -1
- package/dist/ui/assets/cerebras-DPV0CO1I.js +0 -1
- package/dist/ui/assets/cpp-CdhRINFH.js +0 -1
- package/dist/ui/assets/discovery-preference-fqUhjzeE.js +0 -1
- package/dist/ui/assets/dist-Dwa3Gdd4.js +0 -1547
- package/dist/ui/assets/float-topology-BgioAcOd.js +0 -1
- package/dist/ui/assets/fs-CIIVB1OK.js +0 -2
- package/dist/ui/assets/fs-QyBiZMuq.js +0 -1
- package/dist/ui/assets/github-Ctvr_Nd6.js +0 -1
- package/dist/ui/assets/github-Dq-ryVOk.js +0 -2
- package/dist/ui/assets/github-copilot-DIk_soEH.js +0 -1
- package/dist/ui/assets/github-copilot-Dm5mGSYk.js +0 -2
- package/dist/ui/assets/google-generative-ai-C9uw7Zks.js +0 -1
- package/dist/ui/assets/google-generative-ai-DHBc1oKV.js +0 -1
- package/dist/ui/assets/google-vertex-BaT3InWy.js +0 -1
- package/dist/ui/assets/google-vertex-DX8CfGr6.js +0 -1
- package/dist/ui/assets/julia-DibC-h6i.js +0 -1
- package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +0 -1
- package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +0 -1
- package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +0 -1
- package/dist/ui/assets/legacy-api-aliases-CsTVszvY.js +0 -1
- package/dist/ui/assets/legacy-api-aliases-nSD9C16V.js +0 -1
- package/dist/ui/assets/mdc-Otire2H_.js +0 -1
- package/dist/ui/assets/mistral-conversations-BXo-tb0V.js +0 -4
- package/dist/ui/assets/mistral-conversations-Ck257tvp.js +0 -4
- package/dist/ui/assets/monokai-CdkpiU2Y.js +0 -1
- package/dist/ui/assets/mount-C20u28qg.js +0 -2
- package/dist/ui/assets/mount-DSlWOyRg.js +0 -1
- package/dist/ui/assets/mount-recovery-Dp2uzDT3.js +0 -2
- package/dist/ui/assets/new-session-BmqHuPnd.js +0 -2
- package/dist/ui/assets/oauth-bootstrap-CF5Iq7xU.js +0 -2
- package/dist/ui/assets/oauth-service-Bbrrl8Mr.js +0 -1
- package/dist/ui/assets/oauth-service-Cy5ngmcU.js +0 -1
- package/dist/ui/assets/openai-codex-C2ilX51B.js +0 -1
- package/dist/ui/assets/openai-codex-bhWf9evO.js +0 -1
- package/dist/ui/assets/openai-codex-responses-BQzefg3i.js +0 -7
- package/dist/ui/assets/openai-codex-responses-JtcU5b3J.js +0 -7
- package/dist/ui/assets/openai-completions-D-HjYi8x.js +0 -5
- package/dist/ui/assets/openai-completions-QMfwfjiE.js +0 -5
- package/dist/ui/assets/openai-responses-BVHnEPuG.js +0 -1
- package/dist/ui/assets/openai-responses-Ca0oWhbv.js +0 -1
- package/dist/ui/assets/openrouter-Bo62to17.js +0 -1
- package/dist/ui/assets/openrouter-C9_8OqEu.js +0 -1
- package/dist/ui/assets/panel-rpc-handlers-Dbhx7m_G.js +0 -2
- package/dist/ui/assets/php-BidqsJNR.js +0 -1
- package/dist/ui/assets/port-bridge-client-BbVs1yhx.js +0 -2
- package/dist/ui/assets/port-bridge-client-DxYLF2-N.js +0 -1
- package/dist/ui/assets/provider-settings-BTpBo_o4.js +0 -2
- package/dist/ui/assets/provider-store-access-CrgSlufJ.js +0 -1
- package/dist/ui/assets/providers-Crdj27DN.js +0 -2
- package/dist/ui/assets/providers-DX7EJ4Fn.js +0 -1
- package/dist/ui/assets/proxied-fetch-B4g-4VbQ.js +0 -2
- package/dist/ui/assets/proxied-fetch-n2pCvcgW.js +0 -1
- package/dist/ui/assets/quick-llm-hL6bZGLe.js +0 -1
- package/dist/ui/assets/r-Dzep-lPg.js +0 -1
- package/dist/ui/assets/red-CJ3rzSJv.js +0 -1
- package/dist/ui/assets/rose-pine-BthvhNj6.js +0 -1
- package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +0 -1
- package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +0 -1
- package/dist/ui/assets/secret-env-q4hxkt7Z.js +0 -1
- package/dist/ui/assets/secret-topology-xLmXoozo.js +0 -1
- package/dist/ui/assets/setup-standalone-prelude-Btsjqjoe.js +0 -2
- package/dist/ui/assets/setup-sudo-DrmeHwU_.js +0 -2
- package/dist/ui/assets/slicc-version-tsUsi909.js +0 -1
- package/dist/ui/assets/solarized-dark-DV17i1UV.js +0 -1
- package/dist/ui/assets/solarized-light-DSh2HLQt.js +0 -1
- package/dist/ui/assets/swift-DonLKvLd.js +0 -1
- package/dist/ui/assets/transformers-env-SL0zTs7S.js +0 -1
- package/dist/ui/assets/tray-leader-DcRtwWl-.js +0 -1
- package/dist/ui/assets/tray-runtime-config-CRGwg9SK.js +0 -1
- package/dist/ui/assets/ts-tags-BUIgHz80.js +0 -1
- package/dist/ui/assets/vesper-D5bVUKB1.js +0 -1
- package/dist/ui/assets/wc-float-label-qqb_uM9y.js +0 -1
- package/dist/ui/assets/wc-follower-troAtcMA.js +0 -17
- package/dist/ui/assets/wc-live-D1Mon0cw.js +0 -8
- package/dist/ui/assets/wc-nav-BV6Jm98e.js +0 -2
- package/dist/ui/assets/wc-onboarding-Dg4E9VDR.js +0 -2
- package/dist/ui/assets/wc-placeholder-_6RUOpYH.js +0 -4
- package/dist/ui/assets/wc-settings-Dy5cPd8c.js +0 -51
- package/dist/ui/assets/wc-tray-MFZAPwt4.js +0 -2
- package/dist/ui/assets/whisper-session-Cf2gGkoR.js +0 -2
- package/dist/ui/assets/xai-grok-C2GEvF8U.js +0 -1
- package/dist/ui/assets/xai-grok-CoSPbEFx.js +0 -1
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ SLICC is for you if:
|
|
|
78
78
|
- **Boot full x86 virtual machines in the browser.** `v86 start` runs real operating systems (Alpine, FreeDOS, KolibriOS, ...) on the wasm-based [v86](https://github.com/copy/v86) emulator, entirely client-side. The agent drives the guest from the shell — type, click, screenshot, stream the screen live, save/restore state — and `-net <model>,relay=fetch` gives the guest HTTP access through SLICC's fetch proxy. Install with `ipk add v86`.
|
|
79
79
|
- **Delegate parallel work to scoops.** Split tasks into isolated sub-agents with their own sandboxes and context, then let the main agent coordinate the results.
|
|
80
80
|
- **Turn one-off wins into reusable workflows.** Package behavior as skills, build interactive sprinkles, and react to external events with webhooks and cron-driven licks.
|
|
81
|
-
- **Mount your local file system.** By default, SLICC is confined to your browser. But you can ask it to mount folders from your local file system, so it can read and write from there. Mount into an empty path such as `/mnt/myproject` so you do not hide existing skills or scripts.
|
|
81
|
+
- **Mount your local file system.** By default, SLICC is confined to your browser. But you can ask it to mount folders from your local file system, so it can read and write from there. Mount into an empty path such as `/mnt/myproject` so you do not hide existing skills or scripts. Picker mounts ask again after a page reload; for folders you always want available, map them in the launcher's mount table (`npx slicc --mount=~/Projects/foo:/mnt/foo`, or Settings → Mounts in Sliccstart.app) and they are mounted automatically on every launch, with no picker or permission prompt — see [Auto-mounted host folders](docs/mounts.md#auto-mounted-host-folders-the-mount-table).
|
|
82
82
|
- **Mount remote storage as if it were local.** Beyond local folders, `mount --source` bridges S3 buckets, S3-compatible services like Cloudflare R2 and MinIO, and Adobe authoring content — both da.live repositories and Helix 6 sites on the AEM Source Bus — into the same VFS surface. Reads use TTL+ETag caching with conditional revalidation; writes use ETag-conditional PUTs that surface concurrent-edit conflicts as `EBUSY`. Credentials live server-side (`~/.slicc/secrets.env` in CLI, `chrome.storage.local` in the extension via the **Extension options** page) and never reach the agent. After setup: `mount --source s3://my-bucket --profile r2 /mnt/r2`, `mount --source da://my-org/my-repo /mnt/da`, or `mount --source aem://my-org/my-site /mnt/aem`. A `da://` source checks the site config first and re-routes to the Source Bus when the site has been upgraded to Helix 6, rather than quietly mounting a store that no longer holds its content. See [docs/mounts.md](docs/mounts.md) for the full guide.
|
|
83
83
|
|
|
84
84
|
## Getting started
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host filesystem bridge — serves the folders named in the mount table
|
|
3
|
+
* (`--mount=<os-path>:<slicc-path>`) to the webapp over the local /api
|
|
4
|
+
* surface, so configured mounts appear in the VFS fully automatically,
|
|
5
|
+
* with no File System Access picker and no Chrome permission prompt.
|
|
6
|
+
*
|
|
7
|
+
* Only paths under a configured mount root are reachable. Every request
|
|
8
|
+
* names a mount by its SLICC target (`?mount=/mnt/project`) plus a
|
|
9
|
+
* mount-relative path (`?path=sub/file.txt`); the mapping to the OS root
|
|
10
|
+
* happens here and `resolveWithinRoot` rejects traversal (`..`) and
|
|
11
|
+
* symlink escapes, so the browser never addresses the host filesystem
|
|
12
|
+
* directly.
|
|
13
|
+
*
|
|
14
|
+
* Protected like every other /api route: same-origin / loopback callers
|
|
15
|
+
* pass, cross-origin callers need the bridge token (see
|
|
16
|
+
* `createThinBridgeCorsMiddleware` in index.ts).
|
|
17
|
+
*/
|
|
18
|
+
import type { Express } from 'express';
|
|
19
|
+
import type { HostMountMapping } from './runtime-flags.js';
|
|
20
|
+
/** Matches the webapp's hostfs body cap (backend-hostfs.ts). */
|
|
21
|
+
export declare const HOSTFS_MAX_BODY_BYTES: number;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve `relPath` against `root`, rejecting anything that leaves the root
|
|
24
|
+
* either lexically (`..`) or via symlinks. Symlinks *inside* the root that
|
|
25
|
+
* also point inside the root are followed; a link pointing outside is
|
|
26
|
+
* rejected. The check walks to the nearest existing ancestor so that writes
|
|
27
|
+
* to not-yet-existing paths are still validated.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveWithinRoot(root: string, relPath: string): Promise<string>;
|
|
30
|
+
interface HostMountRoot {
|
|
31
|
+
/** SLICC target path, e.g. /mnt/project. */
|
|
32
|
+
path: string;
|
|
33
|
+
/** realpath()'d OS root. */
|
|
34
|
+
root: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the configured mappings to realpath'd roots. Mappings whose OS
|
|
38
|
+
* path does not exist (or is not a directory) are dropped with a warning —
|
|
39
|
+
* a missing folder must not take the whole /api surface down, and the
|
|
40
|
+
* webapp will surface the absent mount as "not mounted".
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveHostMountRoots(mounts: readonly HostMountMapping[], warn?: (msg: string) => void): Promise<HostMountRoot[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Register the /api/hostfs routes for the resolved mount roots. The route
|
|
45
|
+
* surface mirrors `MountBackend` 1:1 (webapp `backend-hostfs.ts`):
|
|
46
|
+
*
|
|
47
|
+
* GET /api/hostfs/list?mount=&path= → { entries: [...] }
|
|
48
|
+
* GET /api/hostfs/stat?mount=&path= → { kind, size, mtime }
|
|
49
|
+
* GET /api/hostfs/read?mount=&path= → octet-stream body
|
|
50
|
+
* PUT /api/hostfs/write?mount=&path= ← octet-stream body
|
|
51
|
+
* POST /api/hostfs/mkdir?mount=&path=
|
|
52
|
+
* DELETE /api/hostfs/remove?mount=&path=[&recursive=1]
|
|
53
|
+
*
|
|
54
|
+
* Errors are `{ code, message }` JSON with an errno-derived status so the
|
|
55
|
+
* webapp backend can rethrow faithful `FsError`s.
|
|
56
|
+
*/
|
|
57
|
+
export declare function registerHostFsRoutes(app: Express, roots: readonly HostMountRoot[]): void;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host filesystem bridge — serves the folders named in the mount table
|
|
3
|
+
* (`--mount=<os-path>:<slicc-path>`) to the webapp over the local /api
|
|
4
|
+
* surface, so configured mounts appear in the VFS fully automatically,
|
|
5
|
+
* with no File System Access picker and no Chrome permission prompt.
|
|
6
|
+
*
|
|
7
|
+
* Only paths under a configured mount root are reachable. Every request
|
|
8
|
+
* names a mount by its SLICC target (`?mount=/mnt/project`) plus a
|
|
9
|
+
* mount-relative path (`?path=sub/file.txt`); the mapping to the OS root
|
|
10
|
+
* happens here and `resolveWithinRoot` rejects traversal (`..`) and
|
|
11
|
+
* symlink escapes, so the browser never addresses the host filesystem
|
|
12
|
+
* directly.
|
|
13
|
+
*
|
|
14
|
+
* Protected like every other /api route: same-origin / loopback callers
|
|
15
|
+
* pass, cross-origin callers need the bridge token (see
|
|
16
|
+
* `createThinBridgeCorsMiddleware` in index.ts).
|
|
17
|
+
*/
|
|
18
|
+
import express from 'express';
|
|
19
|
+
import { lstat, mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile, } from 'fs/promises';
|
|
20
|
+
import { dirname, resolve, sep } from 'path';
|
|
21
|
+
/** Matches the webapp's hostfs body cap (backend-hostfs.ts). */
|
|
22
|
+
export const HOSTFS_MAX_BODY_BYTES = 100 * 1024 * 1024;
|
|
23
|
+
function errnoCode(err) {
|
|
24
|
+
const code = err?.code;
|
|
25
|
+
return typeof code === 'string' ? code : null;
|
|
26
|
+
}
|
|
27
|
+
/** Map an fs errno (or traversal rejection) to an HTTP status + FsError code. */
|
|
28
|
+
function toFsCodeError(err) {
|
|
29
|
+
const code = errnoCode(err) ?? 'EIO';
|
|
30
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31
|
+
switch (code) {
|
|
32
|
+
case 'ENOENT':
|
|
33
|
+
return { status: 404, code, message };
|
|
34
|
+
case 'EACCES':
|
|
35
|
+
case 'EPERM':
|
|
36
|
+
return { status: 403, code: 'EACCES', message };
|
|
37
|
+
case 'EISDIR':
|
|
38
|
+
case 'ENOTDIR':
|
|
39
|
+
case 'ENOTEMPTY':
|
|
40
|
+
case 'EEXIST':
|
|
41
|
+
return { status: 409, code, message };
|
|
42
|
+
default:
|
|
43
|
+
return { status: 500, code, message };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function sendFsError(res, err) {
|
|
47
|
+
const mapped = toFsCodeError(err);
|
|
48
|
+
res.status(mapped.status).json({ code: mapped.code, message: mapped.message });
|
|
49
|
+
}
|
|
50
|
+
/** Traversal/symlink escape → EACCES, so it renders as a permission error. */
|
|
51
|
+
function escapeError(target) {
|
|
52
|
+
const err = new Error(`path escapes the mount root: ${target}`);
|
|
53
|
+
err.code = 'EACCES';
|
|
54
|
+
return err;
|
|
55
|
+
}
|
|
56
|
+
function isWithin(root, target) {
|
|
57
|
+
return target === root || target.startsWith(root + sep);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve `relPath` against `root`, rejecting anything that leaves the root
|
|
61
|
+
* either lexically (`..`) or via symlinks. Symlinks *inside* the root that
|
|
62
|
+
* also point inside the root are followed; a link pointing outside is
|
|
63
|
+
* rejected. The check walks to the nearest existing ancestor so that writes
|
|
64
|
+
* to not-yet-existing paths are still validated.
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveWithinRoot(root, relPath) {
|
|
67
|
+
const cleaned = relPath.replace(/^\/+/, '');
|
|
68
|
+
const target = resolve(root, cleaned);
|
|
69
|
+
if (!isWithin(root, target))
|
|
70
|
+
throw escapeError(target);
|
|
71
|
+
// Walk up to the nearest existing ancestor and realpath it; a symlinked
|
|
72
|
+
// ancestor must still land inside the (already realpath'd) root.
|
|
73
|
+
let probe = target;
|
|
74
|
+
for (;;) {
|
|
75
|
+
try {
|
|
76
|
+
const real = await realpath(probe);
|
|
77
|
+
if (!isWithin(root, real) && probe !== root)
|
|
78
|
+
throw escapeError(target);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (errnoCode(err) !== 'ENOENT')
|
|
83
|
+
throw err;
|
|
84
|
+
const parent = dirname(probe);
|
|
85
|
+
if (parent === probe)
|
|
86
|
+
break;
|
|
87
|
+
probe = parent;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return target;
|
|
91
|
+
}
|
|
92
|
+
function statPayload(s) {
|
|
93
|
+
return {
|
|
94
|
+
kind: s.isDirectory() ? 'directory' : 'file',
|
|
95
|
+
size: s.isDirectory() ? 0 : s.size,
|
|
96
|
+
mtime: Math.round(s.mtimeMs),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the configured mappings to realpath'd roots. Mappings whose OS
|
|
101
|
+
* path does not exist (or is not a directory) are dropped with a warning —
|
|
102
|
+
* a missing folder must not take the whole /api surface down, and the
|
|
103
|
+
* webapp will surface the absent mount as "not mounted".
|
|
104
|
+
*/
|
|
105
|
+
export async function resolveHostMountRoots(mounts, warn = (msg) => console.warn(msg)) {
|
|
106
|
+
const roots = [];
|
|
107
|
+
for (const mapping of mounts) {
|
|
108
|
+
try {
|
|
109
|
+
const root = await realpath(mapping.hostPath);
|
|
110
|
+
const s = await stat(root);
|
|
111
|
+
if (!s.isDirectory()) {
|
|
112
|
+
warn(`--mount ${mapping.hostPath}: not a directory, skipping`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
roots.push({ path: mapping.path, root });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
warn(`--mount ${mapping.hostPath}: does not exist, skipping`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return roots;
|
|
122
|
+
}
|
|
123
|
+
function queryString(req, name) {
|
|
124
|
+
const value = req.query[name];
|
|
125
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Register the /api/hostfs routes for the resolved mount roots. The route
|
|
129
|
+
* surface mirrors `MountBackend` 1:1 (webapp `backend-hostfs.ts`):
|
|
130
|
+
*
|
|
131
|
+
* GET /api/hostfs/list?mount=&path= → { entries: [...] }
|
|
132
|
+
* GET /api/hostfs/stat?mount=&path= → { kind, size, mtime }
|
|
133
|
+
* GET /api/hostfs/read?mount=&path= → octet-stream body
|
|
134
|
+
* PUT /api/hostfs/write?mount=&path= ← octet-stream body
|
|
135
|
+
* POST /api/hostfs/mkdir?mount=&path=
|
|
136
|
+
* DELETE /api/hostfs/remove?mount=&path=[&recursive=1]
|
|
137
|
+
*
|
|
138
|
+
* Errors are `{ code, message }` JSON with an errno-derived status so the
|
|
139
|
+
* webapp backend can rethrow faithful `FsError`s.
|
|
140
|
+
*/
|
|
141
|
+
export function registerHostFsRoutes(app, roots) {
|
|
142
|
+
const byPath = new Map(roots.map((r) => [r.path, r]));
|
|
143
|
+
const withTarget = (handler) => {
|
|
144
|
+
return (req, res) => {
|
|
145
|
+
void (async () => {
|
|
146
|
+
const mount = queryString(req, 'mount');
|
|
147
|
+
const rel = queryString(req, 'path') ?? '';
|
|
148
|
+
const entry = mount ? byPath.get(mount) : undefined;
|
|
149
|
+
if (!entry) {
|
|
150
|
+
res.status(404).json({ code: 'ENOENT', message: `no such mount: ${mount ?? ''}` });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const target = await resolveWithinRoot(entry.root, rel);
|
|
155
|
+
await handler(target, req, res);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
sendFsError(res, err);
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
app.get('/api/hostfs/list', withTarget(async (target, _req, res) => {
|
|
164
|
+
const dirents = await readdir(target, { withFileTypes: true });
|
|
165
|
+
const entries = await Promise.all(dirents.map(async (d) => {
|
|
166
|
+
if (d.isDirectory())
|
|
167
|
+
return { name: d.name, kind: 'directory' };
|
|
168
|
+
try {
|
|
169
|
+
// stat() follows symlinks, so a link to a directory classifies as
|
|
170
|
+
// a directory — matching what every subsequent access sees (and
|
|
171
|
+
// the swift server, whose fileExists follows links). Escaping
|
|
172
|
+
// links are still refused at access time by resolveWithinRoot.
|
|
173
|
+
const s = await stat(resolve(target, d.name));
|
|
174
|
+
if (s.isDirectory())
|
|
175
|
+
return { name: d.name, kind: 'directory' };
|
|
176
|
+
return {
|
|
177
|
+
name: d.name,
|
|
178
|
+
kind: 'file',
|
|
179
|
+
size: s.size,
|
|
180
|
+
lastModified: Math.round(s.mtimeMs),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Raced deletion / dangling link / unreadable: name only.
|
|
185
|
+
return { name: d.name, kind: 'file' };
|
|
186
|
+
}
|
|
187
|
+
}));
|
|
188
|
+
res.json({ entries });
|
|
189
|
+
}));
|
|
190
|
+
app.get('/api/hostfs/stat', withTarget(async (target, _req, res) => {
|
|
191
|
+
res.json(statPayload(await stat(target)));
|
|
192
|
+
}));
|
|
193
|
+
app.get('/api/hostfs/read', withTarget(async (target, _req, res) => {
|
|
194
|
+
// lstat first so a dangling symlink is ENOENT, a directory is EISDIR.
|
|
195
|
+
const s = await stat(target);
|
|
196
|
+
if (s.isDirectory()) {
|
|
197
|
+
sendFsError(res, Object.assign(new Error(`is a directory: ${target}`), { code: 'EISDIR' }));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (s.size > HOSTFS_MAX_BODY_BYTES) {
|
|
201
|
+
res.status(413).json({
|
|
202
|
+
code: 'EFBIG',
|
|
203
|
+
message: `file exceeds the ${HOSTFS_MAX_BODY_BYTES} byte hostfs cap`,
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const body = await readFile(target);
|
|
208
|
+
res.setHeader('Content-Type', 'application/octet-stream');
|
|
209
|
+
res.send(body);
|
|
210
|
+
}));
|
|
211
|
+
app.put('/api/hostfs/write', express.raw({ type: () => true, limit: HOSTFS_MAX_BODY_BYTES }), withTarget(async (target, req, res) => {
|
|
212
|
+
// Refuse to clobber a directory with a file (fs would throw EISDIR on
|
|
213
|
+
// open, but check explicitly so the error is deterministic).
|
|
214
|
+
try {
|
|
215
|
+
const existing = await lstat(target);
|
|
216
|
+
if (existing.isDirectory()) {
|
|
217
|
+
sendFsError(res, Object.assign(new Error(`is a directory: ${target}`), { code: 'EISDIR' }));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
/* ENOENT — fresh file */
|
|
223
|
+
}
|
|
224
|
+
await mkdir(dirname(target), { recursive: true });
|
|
225
|
+
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
|
|
226
|
+
// Write-then-rename would break hardlinks and xattrs on user files;
|
|
227
|
+
// plain writeFile matches what local tools do.
|
|
228
|
+
await writeFile(target, body);
|
|
229
|
+
res.json({ ok: true });
|
|
230
|
+
}));
|
|
231
|
+
app.post('/api/hostfs/mkdir', withTarget(async (target, _req, res) => {
|
|
232
|
+
await mkdir(target, { recursive: true });
|
|
233
|
+
res.json({ ok: true });
|
|
234
|
+
}));
|
|
235
|
+
app.post('/api/hostfs/rename', withTarget(async (target, req, res) => {
|
|
236
|
+
const mount = queryString(req, 'mount');
|
|
237
|
+
const toRel = queryString(req, 'to');
|
|
238
|
+
const entry = mount ? byPath.get(mount) : undefined;
|
|
239
|
+
if (!entry || !toRel) {
|
|
240
|
+
res.status(400).json({ code: 'EINVAL', message: 'rename requires mount and to' });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const to = await resolveWithinRoot(entry.root, toRel);
|
|
244
|
+
await rename(target, to);
|
|
245
|
+
res.json({ ok: true });
|
|
246
|
+
}));
|
|
247
|
+
app.delete('/api/hostfs/remove', withTarget(async (target, req, res) => {
|
|
248
|
+
const isRoot = roots.some((r) => r.root === target);
|
|
249
|
+
if (isRoot) {
|
|
250
|
+
res.status(403).json({ code: 'EACCES', message: 'refusing to remove a mount root' });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const recursive = queryString(req, 'recursive') === '1';
|
|
254
|
+
await rm(target, { recursive, force: false });
|
|
255
|
+
res.json({ ok: true });
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
@@ -30,6 +30,7 @@ import { getElectronAppPorts } from './electron-runtime.js';
|
|
|
30
30
|
import { ElectronTrayFollower } from './electron-tray-follower.js';
|
|
31
31
|
import { FileLogger } from './file-logger.js';
|
|
32
32
|
import { registerHostedBootstrapEndpoint } from './hosted-bootstrap.js';
|
|
33
|
+
import { registerHostFsRoutes, resolveHostMountRoots } from './hostfs.js';
|
|
33
34
|
import { runInstallCli } from './install-cli.js';
|
|
34
35
|
import { resolveCliBrowserLaunchUrl } from './launch-url.js';
|
|
35
36
|
import { createHttpCdp, registerLeaderRestartEndpoint } from './leader-restart.js';
|
|
@@ -197,6 +198,7 @@ function createServerState() {
|
|
|
197
198
|
electronFollower: null,
|
|
198
199
|
shuttingDown: false,
|
|
199
200
|
discoveredTrayJoinUrl: RUNTIME_FLAGS.joinUrl ?? null,
|
|
201
|
+
hostMountRoots: [],
|
|
200
202
|
bridgeToken: resolveServerBridgeToken(process.env, { thinBridgeMode: THIN_BRIDGE_MODE }),
|
|
201
203
|
cdpUrl: null,
|
|
202
204
|
chromeWs: null,
|
|
@@ -1106,6 +1108,10 @@ async function main() {
|
|
|
1106
1108
|
// after `server.listen()`, so this endpoint must NOT close over a
|
|
1107
1109
|
// pre-launch snapshot or it would always return null.
|
|
1108
1110
|
trayJoinUrl: state.discoveredTrayJoinUrl ?? null,
|
|
1111
|
+
// Mount table (--mount=<os-path>:<slicc-path>): served over
|
|
1112
|
+
// /api/hostfs and auto-mounted by the webapp at boot. Only mappings
|
|
1113
|
+
// whose OS folder existed at startup are advertised.
|
|
1114
|
+
autoMounts: state.hostMountRoots.map(({ path, root }) => ({ path, hostPath: root })),
|
|
1109
1115
|
});
|
|
1110
1116
|
});
|
|
1111
1117
|
// Localhost API descriptor — the discoverable surface advertised by the
|
|
@@ -1144,6 +1150,10 @@ async function main() {
|
|
|
1144
1150
|
registerHostedBootstrapEndpoint(app, { secretStore });
|
|
1145
1151
|
registerSecretsReloadEndpoint(app, { secretProxy, secretStore, oauthStore });
|
|
1146
1152
|
}
|
|
1153
|
+
// Host-FS bridge for the mount table (--mount=<os>:<vfs>), every mode.
|
|
1154
|
+
// Roots are resolved once at startup; a folder created later needs a restart.
|
|
1155
|
+
state.hostMountRoots = await resolveHostMountRoots(RUNTIME_FLAGS.mounts);
|
|
1156
|
+
registerHostFsRoutes(app, state.hostMountRoots);
|
|
1147
1157
|
// Sudo approval endpoint — raises a native OS dialog / TTY prompt from this
|
|
1148
1158
|
// trusted process so the in-browser agent can request, but never fabricate,
|
|
1149
1159
|
// an approval. Loopback-only; selects a backend by environment at call time.
|
|
@@ -24,7 +24,29 @@ export interface CliRuntimeFlags {
|
|
|
24
24
|
installCli: boolean;
|
|
25
25
|
/** Target directory for --install-cli (default: OS-idiomatic, see install-cli.ts) */
|
|
26
26
|
installDir: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Mount table: OS-folder → SLICC-target mappings served over the local
|
|
29
|
+
* host-FS bridge (`/api/hostfs`) and auto-mounted by the webapp at boot,
|
|
30
|
+
* with no picker and no Chrome permission prompt. Filled by repeatable
|
|
31
|
+
* `--mount=<os-path>:<slicc-path>` / `--mount <os-path>:<slicc-path>`
|
|
32
|
+
* flags. Picker-initiated (`mount <path>`) FS-Access mounts are
|
|
33
|
+
* unaffected and keep asking for permission.
|
|
34
|
+
*/
|
|
35
|
+
mounts: HostMountMapping[];
|
|
36
|
+
}
|
|
37
|
+
/** One `--mount` entry: an absolute OS folder mapped to a VFS target. */
|
|
38
|
+
export interface HostMountMapping {
|
|
39
|
+
hostPath: string;
|
|
40
|
+
path: string;
|
|
27
41
|
}
|
|
28
42
|
export declare const DEFAULT_CLI_CDP_PORT = 9222;
|
|
29
43
|
export declare const DEFAULT_ELECTRON_ATTACH_CDP_PORT = 9223;
|
|
44
|
+
/**
|
|
45
|
+
* Parse a `--mount` value of the form `<os-path>:<slicc-path>`. The split is
|
|
46
|
+
* on the LAST `:` whose remainder is an absolute path, so OS paths containing
|
|
47
|
+
* `:` still parse. A leading `~/` on the OS side expands against `homeDir`.
|
|
48
|
+
* Returns null (never a partial mapping) for anything else, so a stray
|
|
49
|
+
* `--mount=` or a one-sided value never lands in the table.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseMountTableMapping(value: string, homeDir?: string): HostMountMapping | null;
|
|
30
52
|
export declare function parseCliRuntimeFlags(argv: string[]): CliRuntimeFlags;
|
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
export const DEFAULT_CLI_CDP_PORT = 9222;
|
|
2
2
|
export const DEFAULT_ELECTRON_ATTACH_CDP_PORT = 9223;
|
|
3
|
+
/** `.`/`..` segments or empty (`//`) segments make raw != canonical. */
|
|
4
|
+
function hasDotOrEmptySegments(path) {
|
|
5
|
+
return path
|
|
6
|
+
.split(/[/\\]/)
|
|
7
|
+
.slice(1)
|
|
8
|
+
.some((segment) => segment === '' || segment === '.' || segment === '..');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalize an absolute POSIX path. Dot/empty segments are rejected rather
|
|
12
|
+
* than resolved so the accepted value IS its canonical form — the webapp,
|
|
13
|
+
* the /api/hostfs mount key, and VirtualFS.normalizePath all agree on it.
|
|
14
|
+
*/
|
|
15
|
+
function normalizeAbsolutePath(value) {
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!trimmed.startsWith('/'))
|
|
18
|
+
return null;
|
|
19
|
+
const stripped = trimmed.length > 1 ? trimmed.replace(/\/+$/, '') : trimmed;
|
|
20
|
+
if (!stripped || (stripped.length > 1 && hasDotOrEmptySegments(stripped)))
|
|
21
|
+
return null;
|
|
22
|
+
return stripped;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Normalize the OS side of a mapping: POSIX absolute, or a Windows drive
|
|
26
|
+
* path (`C:\…` / `C:/…`) — node-server ships on Windows too.
|
|
27
|
+
*/
|
|
28
|
+
function normalizeHostPath(value) {
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
if (/^[A-Za-z]:[/\\]/.test(trimmed)) {
|
|
31
|
+
const stripped = trimmed.replace(/[/\\]+$/, '');
|
|
32
|
+
// Keep the root slash on a bare drive (`C:\`).
|
|
33
|
+
if (/^[A-Za-z]:$/.test(stripped))
|
|
34
|
+
return trimmed.slice(0, 3);
|
|
35
|
+
return hasDotOrEmptySegments(stripped.slice(2)) ? null : stripped;
|
|
36
|
+
}
|
|
37
|
+
return normalizeAbsolutePath(trimmed);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a `--mount` value of the form `<os-path>:<slicc-path>`. The split is
|
|
41
|
+
* on the LAST `:` whose remainder is an absolute path, so OS paths containing
|
|
42
|
+
* `:` still parse. A leading `~/` on the OS side expands against `homeDir`.
|
|
43
|
+
* Returns null (never a partial mapping) for anything else, so a stray
|
|
44
|
+
* `--mount=` or a one-sided value never lands in the table.
|
|
45
|
+
*/
|
|
46
|
+
export function parseMountTableMapping(value, homeDir = process.env['HOME'] ?? '') {
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
const sep = trimmed.lastIndexOf(':');
|
|
49
|
+
if (sep <= 0)
|
|
50
|
+
return null;
|
|
51
|
+
let hostRaw = trimmed.slice(0, sep).trim();
|
|
52
|
+
const targetRaw = trimmed.slice(sep + 1).trim();
|
|
53
|
+
if (hostRaw === '~' || hostRaw.startsWith('~/')) {
|
|
54
|
+
if (!homeDir)
|
|
55
|
+
return null;
|
|
56
|
+
hostRaw = homeDir + hostRaw.slice(1);
|
|
57
|
+
}
|
|
58
|
+
const hostPath = normalizeHostPath(hostRaw);
|
|
59
|
+
const path = normalizeAbsolutePath(targetRaw);
|
|
60
|
+
if (!hostPath || !path || path === '/')
|
|
61
|
+
return null;
|
|
62
|
+
return { hostPath, path };
|
|
63
|
+
}
|
|
64
|
+
function addMountTablePath(flags, value) {
|
|
65
|
+
const mapping = parseMountTableMapping(value);
|
|
66
|
+
if (mapping && !flags.mounts.some((m) => m.path === mapping.path)) {
|
|
67
|
+
flags.mounts.push(mapping);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
3
70
|
function looksLikeUrl(value) {
|
|
4
71
|
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
|
|
5
72
|
}
|
|
@@ -25,6 +92,7 @@ function createDefaultFlags() {
|
|
|
25
92
|
hosted: false,
|
|
26
93
|
installCli: false,
|
|
27
94
|
installDir: null,
|
|
95
|
+
mounts: [],
|
|
28
96
|
};
|
|
29
97
|
}
|
|
30
98
|
/** Next argv entry, but only when it exists and is not itself a `--` flag. */
|
|
@@ -103,6 +171,10 @@ function applyEqualsFlag(flags, arg) {
|
|
|
103
171
|
flags.joinUrl = arg.slice('--join='.length).trim() || null;
|
|
104
172
|
return true;
|
|
105
173
|
}
|
|
174
|
+
if (arg.startsWith('--mount=')) {
|
|
175
|
+
addMountTablePath(flags, arg.slice('--mount='.length));
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
106
178
|
if (arg.startsWith('--electron-app=')) {
|
|
107
179
|
flags.electron = true;
|
|
108
180
|
flags.electronApp = arg.slice('--electron-app='.length).trim() || null;
|
|
@@ -110,7 +182,7 @@ function applyEqualsFlag(flags, arg) {
|
|
|
110
182
|
}
|
|
111
183
|
return false;
|
|
112
184
|
}
|
|
113
|
-
/** `--prompt`/`--env-file`/`--profile`/`--install-dir` followed by a value token. Returns tokens consumed. */
|
|
185
|
+
/** `--prompt`/`--env-file`/`--profile`/`--install-dir`/`--mount` followed by a value token. Returns tokens consumed. */
|
|
114
186
|
function applyPlainValueFlag(flags, argv, index, arg) {
|
|
115
187
|
const next = nextValueArg(argv, index);
|
|
116
188
|
if (next === null) {
|
|
@@ -125,6 +197,9 @@ function applyPlainValueFlag(flags, argv, index, arg) {
|
|
|
125
197
|
else if (arg === '--install-dir') {
|
|
126
198
|
flags.installDir = next.trim() || null;
|
|
127
199
|
}
|
|
200
|
+
else if (arg === '--mount') {
|
|
201
|
+
addMountTablePath(flags, next);
|
|
202
|
+
}
|
|
128
203
|
else {
|
|
129
204
|
flags.profile = next.trim() || null;
|
|
130
205
|
}
|
|
@@ -176,7 +251,8 @@ function applyValueFlag(flags, argv, index, arg) {
|
|
|
176
251
|
if (arg === '--prompt' ||
|
|
177
252
|
arg === '--env-file' ||
|
|
178
253
|
arg === '--profile' ||
|
|
179
|
-
arg === '--install-dir'
|
|
254
|
+
arg === '--install-dir' ||
|
|
255
|
+
arg === '--mount') {
|
|
180
256
|
return applyPlainValueFlag(flags, argv, index, arg);
|
|
181
257
|
}
|
|
182
258
|
if (arg === '--lead' || arg === '--join') {
|