@pi-chamber/web 0.1.8
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 +43 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +140 -0
- package/bin/cli-output.test.js +36 -0
- package/bin/cli.js +383 -0
- package/bin/cli.test.js +1065 -0
- package/bin/lib/DOCUMENTATION.md +31 -0
- package/bin/lib/cli-api-target.js +74 -0
- package/bin/lib/cli-args.js +846 -0
- package/bin/lib/cli-errors.js +18 -0
- package/bin/lib/cli-http.js +252 -0
- package/bin/lib/cli-lifecycle.js +421 -0
- package/bin/lib/cli-log-files.js +93 -0
- package/bin/lib/cli-network.js +186 -0
- package/bin/lib/cli-paths.js +128 -0
- package/bin/lib/cli-ports.js +51 -0
- package/bin/lib/cli-process.js +342 -0
- package/bin/lib/cli-startup.js +371 -0
- package/bin/lib/cli-tunnel-capabilities.js +9 -0
- package/bin/lib/cli-tunnel-profiles.js +366 -0
- package/bin/lib/cli-tunnel-utils.js +300 -0
- package/bin/lib/commands-connect-url.js +329 -0
- package/bin/lib/commands-lifecycle.js +396 -0
- package/bin/lib/commands-logs.js +110 -0
- package/bin/lib/commands-serve.js +419 -0
- package/bin/lib/commands-startup.js +64 -0
- package/bin/lib/commands-status.js +114 -0
- package/bin/lib/commands-tunnel.js +1761 -0
- package/bin/lib/commands-update.js +184 -0
- package/bin/lib/commands-update.test.js +98 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +13 -0
- package/dist/assets/CommitSection-DGGArAb2.js +1 -0
- package/dist/assets/DiagramEditor-ig5nYV6t.js +1 -0
- package/dist/assets/DiagramView-8Qr6pwwI.js +1 -0
- package/dist/assets/DiffView-BHS4m_K7.js +18 -0
- package/dist/assets/FilesView-DmZcdwl-.js +14 -0
- package/dist/assets/GitView-gfHxmc66.js +6 -0
- package/dist/assets/JsonTreeView-DbFqka3P.js +1 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/MarkdownRendererImpl-B07lHhBl.js +12 -0
- package/dist/assets/PatchDiff-DD9pAs47.js +1 -0
- package/dist/assets/PierreDiffViewer-Bo0_BCT7.js +136 -0
- package/dist/assets/SessionSwitcherDropdown-t7WZX24f.js +1 -0
- package/dist/assets/SettingsView-CtsS4oDI.js +3 -0
- package/dist/assets/SettingsWindow-DIRGn3eQ.js +1 -0
- package/dist/assets/TerminalView-BV3KOtdA.css +1 -0
- package/dist/assets/TerminalView-DUwO1CCY.js +46 -0
- package/dist/assets/ToolOutputDialog-iZG2ey4T.js +15 -0
- package/dist/assets/ToolPartDiffPreview-FTexlZSz.js +8 -0
- package/dist/assets/VirtualizedFileDiff-DD5IznIe.js +10 -0
- package/dist/assets/Virtualizer-BxW2CJGB.js +1 -0
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/abap-Dg923wIy.js +1 -0
- package/dist/assets/actionscript-3-8WTK4YD2.js +1 -0
- package/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/assets/ada-CTi7YQtL.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/assets/andromeeda-CT0vplkN.js +1 -0
- package/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/assets/angular-html-D2R1pe45.js +1 -0
- package/dist/assets/angular-ts-BDmYQnhm.js +1 -0
- package/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/assets/apache-PcO-DBxQ.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-CnCu5_gb.js +1 -0
- package/dist/assets/apex-D8_7TLub.js +1 -0
- package/dist/assets/apl-B4CMkyY2.js +1 -0
- package/dist/assets/apl-DkfyvC6M.js +1 -0
- package/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/assets/appThemeRegistry-BK0dHKa6.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/applescript-Ds_W2jPS.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/ara-C1--7J61.js +1 -0
- package/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/assets/asciidoc-nTfEFGxb.js +1 -0
- package/dist/assets/asm-CTFRnVOM.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/assets/astro-DtPAdMss.js +1 -0
- package/dist/assets/aurora-x-C0VKFdlO.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-CrWgQKhV.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/assets/ayu-dark-wZ6ORhBL.js +1 -0
- package/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/assets/ayu-light-xXLJ_Ch1.js +1 -0
- package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/assets/ayu-mirage-CQckA_sP.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/ballerina-a0yOSyc5.js +1 -0
- package/dist/assets/bat-B0cHNp6s.js +1 -0
- package/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/assets/beancount-dPJi_gci.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-CVKoklHx.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bibtex-DRcCXNgM.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/bicep-jWz-hQ8I.js +1 -0
- package/dist/assets/bird2-DPOp833l.js +1 -0
- package/dist/assets/bird2-DaGXIRGu.js +1 -0
- package/dist/assets/blade-D4QpJJKB.js +1 -0
- package/dist/assets/blade-QlWdzBxO.js +1 -0
- package/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/assets/bsl-jNujDQK2.js +1 -0
- package/dist/assets/c-BG-tZi7o.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/c3-Fcb8_HKm.js +1 -0
- package/dist/assets/c3-eo99z4R2.js +1 -0
- package/dist/assets/cadence-BuaeHeSu.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-BYiDgOF2.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-CfssSZMA.js +1 -0
- package/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/assets/catppuccin-latte-DoBaWTGn.js +1 -0
- package/dist/assets/catppuccin-macchiato-BkAuvdZC.js +1 -0
- package/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/assets/catppuccin-mocha-D9fcC0iT.js +1 -0
- package/dist/assets/clarity-CC_0IFDj.js +1 -0
- package/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/assets/clike-B9uivgTg.js +1 -0
- package/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/dist/assets/clojure-KnfDkB_V.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cmake-bh2MLnkv.js +1 -0
- package/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/dist/assets/cobol-dIXRVV9Y.js +1 -0
- package/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeowners-CtwIM3TN.js +1 -0
- package/dist/assets/codeql-D164juXQ.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Bk-z6oI_.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/dist/assets/collapsible-ODWWxk4f.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/common-lisp-DXc9XcIM.js +1 -0
- package/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/dist/assets/computeVirtualFileMetrics-DdXkLFAD.js +3 -0
- package/dist/assets/contextFileOpenGuard-uC4OerzL.js +1 -0
- package/dist/assets/coq-BBL5-mG4.js +1 -0
- package/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/assets/cpp-C0lzDePx.js +1 -0
- package/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/assets/crystal-Bhade0k2.js +1 -0
- package/dist/assets/crystal-SjHAIU92.js +1 -0
- package/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/assets/csharp-B7nLXyi8.js +1 -0
- package/dist/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/assets/css-BnMrqG3P.js +1 -0
- package/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/assets/css-PZBBrkI0.js +1 -0
- package/dist/assets/csv-D5aBhKLr.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cue-WCin6O4e.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/dist/assets/cypher-DYQk2G82.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/d-DVc-yF-l.js +1 -0
- package/dist/assets/d-pRatUO7H.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dark-plus-DLU5INA3.js +1 -0
- package/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/assets/dart-YLd5yyRs.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/dax-oldcCaJg.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/desktop-DZfyBMgF.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/diff-DbItnlRl.js +1 -0
- package/dist/assets/diff-jW5kmiru.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/docker-DG9neUKd.js +1 -0
- package/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/dist/assets/document-attachments-BslE4Ns7.js +30 -0
- package/dist/assets/dotenv-Bea-FkXq.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-XcYHBZy6.js +1 -0
- package/dist/assets/dracula-soft-BPeX9gxW.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/dream-maker-DAHQwpwp.js +1 -0
- package/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/dist/assets/dylan-DwRh75JA.js +1 -0
- package/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/dist/assets/ecl-Cabwm37j.js +1 -0
- package/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/assets/edge-zt3dU95k.js +1 -0
- package/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/assets/elixir-JolcG37e.js +1 -0
- package/dist/assets/elm-BjIDgKzz.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/elm-vLlmbW-K.js +1 -0
- package/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/assets/emacs-lisp-DMBh55R4.js +1 -0
- package/dist/assets/erb-B12qg9BL.js +1 -0
- package/dist/assets/erb-rsjdL4fB.js +1 -0
- package/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/dist/assets/erlang-DNeY46SW.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-dark-WOub3wle.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/everforest-light-DCUg8VH8.js +1 -0
- package/dist/assets/factor-kuTfRLto.js +1 -0
- package/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fennel-DBMsxtrh.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fish-BxskpYPN.js +1 -0
- package/dist/assets/fluent-Bwo2XpEt.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/forth-Ffai-XNe.js +1 -0
- package/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/assets/fortran-fixed-form-DFFnVrrY.js +1 -0
- package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/assets/fortran-free-form-DB5N_DgY.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/fsharp-DRAF1jsN.js +1 -0
- package/dist/assets/gas-Bneqetm1.js +1 -0
- package/dist/assets/gdresource-BD-1uzY7.js +1 -0
- package/dist/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/assets/gdscript-C3mT4Uym.js +1 -0
- package/dist/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/gdshader-G7X6ysY3.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/genie-DSsjfZMX.js +1 -0
- package/dist/assets/gherkin-Cl2Fw-_0.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/dist/assets/git-commit-CJPsQObV.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-DeZqv4fp.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-BQV5ugrc.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-default-DLWDM7FH.js +1 -0
- package/dist/assets/github-dark-dimmed-D1FmiR_p.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-BilLoi0o.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-Rcb2q3L5.js +1 -0
- package/dist/assets/github-light-default-CQ75esMw.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/github-light-high-contrast-CRF1wyLy.js +1 -0
- package/dist/assets/gleam-BeBqoPwE.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-BuyKUjfV.js +1 -0
- package/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/assets/glimmer-ts-D9iniPFN.js +1 -0
- package/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/assets/glsl-BOG9l2nV.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gn-Dxr0sSUf.js +1 -0
- package/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/assets/gnuplot-CtW3R_Hz.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-BBr2UQCn.js +1 -0
- package/dist/assets/go-CxLEBnE3.js +1 -0
- package/dist/assets/graphql-B6XHM4uj.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/dist/assets/groovy-DFxE31Qs.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-C37G1gbc.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-C3G9m3TV.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-dark-soft-eZmcmZ9I.js +1 -0
- package/dist/assets/gruvbox-light-hard-BYj-vz_3.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-BdQlDiiF.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-BGNwjiRh.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/assets/hack-Cxa-FaZ4.js +1 -0
- package/dist/assets/haml-2e0EI6Is.js +1 -0
- package/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/assets/handlebars-CojHdNmS.js +1 -0
- package/dist/assets/haskell-BfdhR_WN.js +1 -0
- package/dist/assets/haskell-Cw1EW3IL.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/assets/haxe-DVIybFAS.js +1 -0
- package/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/dist/assets/hcl-B6OePqme.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D2jn7IRw.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/hlsl-MKeth5V9.js +1 -0
- package/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
- package/dist/assets/horizon-bright-CoQRYDvy.js +1 -0
- package/dist/assets/horizon-cslQss18.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/houston-RHIKkNfR.js +1 -0
- package/dist/assets/html-DOf0XnR2.js +1 -0
- package/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/assets/html-derivative-DfCZ6Hio.js +1 -0
- package/dist/assets/http-CG3jvXcS.js +1 -0
- package/dist/assets/http-DBlCnlav.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-CxQrh4RI.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/assets/hxml-CkS3Eie7.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/hy-DI6mCUnw.js +1 -0
- package/dist/assets/idl-BEugSyMb.js +1 -0
- package/dist/assets/imba-D-Pk5S0X.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-CRB7i1S2.js +8 -0
- package/dist/assets/index-C_Mo87uS.css +1 -0
- package/dist/assets/index-MSKFS67y.js +1 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/ini-Dix3pTmg.js +1 -0
- package/dist/assets/isDiffPlainText-CeCQwHA2.js +1 -0
- package/dist/assets/isFilePlainText-BcfhE9FH.js +1 -0
- package/dist/assets/java-BTDmaRNO.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-BO_2ioGC.js +1 -0
- package/dist/assets/javascript-WyGyM1wV.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/assets/jinja-D0mZ3q2r.js +1 -0
- package/dist/assets/jison-B9XHrcYJ.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-B8JryHcM.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/json5-Cdatmj9n.js +1 -0
- package/dist/assets/jsonc-CvUCa1o5.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-9I_GBd5L.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jsonnet-XoenQ2oc.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jssm-CWE2S0Z5.js +1 -0
- package/dist/assets/jsx-8cLv2Six.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-C1oINnwa.js +1 -0
- package/dist/assets/julia-CxzCAyBv.js +1 -0
- package/dist/assets/julia-DuME0IfC.js +1 -0
- package/dist/assets/just-Bo3fV9RY.js +1 -0
- package/dist/assets/just-Cw27pwNe.js +1 -0
- package/dist/assets/kanagawa-dragon-BOvIHJVT.js +1 -0
- package/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/assets/kanagawa-lotus-Bwcd7Bvx.js +1 -0
- package/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/assets/kanagawa-wave-0SAlpf0s.js +1 -0
- package/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/assets/kdl-CMbyIXjQ.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kimi-for-coding-eVEHug_h.svg +3 -0
- package/dist/assets/kotlin-BQy537ds.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-B_y0uMaZ.js +1 -0
- package/dist/assets/kusto-DZf3V79B.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/laserwave-si7wuSrt.js +1 -0
- package/dist/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/assets/latex-CZAhoKip.js +1 -0
- package/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/assets/lean-C4RHkN41.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/less-XNfgnZC7.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/light-plus-g_Fi9vkG.js +1 -0
- package/dist/assets/liquid-DFlqVvG0.js +1 -0
- package/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/assets/livescript-BwQOo05w.js +1 -0
- package/dist/assets/llmapi-detailed-Pup_107f.svg +28 -0
- package/dist/assets/llvm-BJRUtCev.js +1 -0
- package/dist/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/log-CcaF_xNv.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/logo-D9WQLoKQ.js +1 -0
- package/dist/assets/lua-8Av-h-ns.js +1 -0
- package/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/assets/lua-BgMRiT3U.js +1 -0
- package/dist/assets/luau-BTSFCcFb.js +1 -0
- package/dist/assets/luau-C-HG3fhB.js +1 -0
- package/dist/assets/main-CPygRDd9.js +134 -0
- package/dist/assets/main-DZg4qzOe.js +2 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/make-DXVx14cr.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/markdown-DX-TNHnc.js +1 -0
- package/dist/assets/markdown-shiki.worker-DnvH1S1x.js +12 -0
- package/dist/assets/markdownCore-BULtt6Xa.js +2 -0
- package/dist/assets/marko-CnJfTvn9.js +1 -0
- package/dist/assets/marko-DiIoNBFw.js +1 -0
- package/dist/assets/material-theme-CIJ3cGaW.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BWg3m1GB.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-lighter-DR8HWMN7.js +1 -0
- package/dist/assets/material-theme-ocean-CZf9pk28.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-BF01YeZy.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/matlab-DzreyMvm.js +1 -0
- package/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/dist/assets/mdc-BMNejdWA.js +1 -0
- package/dist/assets/mdc-D_DpLax5.js +1 -0
- package/dist/assets/mdx-C2fqO2XY.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-Cwy4Up_p.js +1 -0
- package/dist/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-dark-DhfaepVE.js +1 -0
- package/dist/assets/min-light-6xx0uMgm.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/miniChat-BFotDgpO.js +2 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mipsasm-yE_Aqo9y.js +1 -0
- package/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/dist/assets/mllike-BO34x3tv.js +1 -0
- package/dist/assets/mobile-3LLzhN5Q.js +2 -0
- package/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/dist/assets/mojo-M6pJPbMk.js +1 -0
- package/dist/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/assets/monokai-BodnsaDQ.js +1 -0
- package/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/assets/moonbit-CR8uFLGd.js +1 -0
- package/dist/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/assets/move-CyYMWVy5.js +1 -0
- package/dist/assets/move-IF9eRakj.js +1 -0
- package/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/dist/assets/mumps-BT43cFF4.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/narrat-DXUZ_ZkW.js +1 -0
- package/dist/assets/nextflow-BPolriEg.js +1 -0
- package/dist/assets/nextflow-Zz6hmt5N.js +1 -0
- package/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
- package/dist/assets/nextflow-groovy-pQRwk_Jg.js +1 -0
- package/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/assets/nginx-DSXkacNc.js +1 -0
- package/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/night-owl-EdP2Hztp.js +1 -0
- package/dist/assets/night-owl-light-Bk-lYVyk.js +1 -0
- package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/assets/nim-e2z2kCcq.js +1 -0
- package/dist/assets/nix-Ckjrpg0r.js +1 -0
- package/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/assets/nord-BAu0wM6c.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/dist/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/assets/nushell-DtHLcZf1.js +1 -0
- package/dist/assets/objective-c-BoaBTdtz.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/objective-cpp-DstR8hNy.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/ocaml-DTbn2Hv7.js +1 -0
- package/dist/assets/octave-CWZ4xMHY.js +1 -0
- package/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/assets/odin-Bxc4S6Xn.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-dark-pro-utPxbBbJ.js +1 -0
- package/dist/assets/one-light-BuCQ22aO.js +1 -0
- package/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/openscad-CXHpNBsb.js +1 -0
- package/dist/assets/oz-BzwKVEFT.js +1 -0
- package/dist/assets/pascal--L3eBynH.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/pascal-Sr-XLSE6.js +1 -0
- package/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/assets/perl-C95KruZl.js +1 -0
- package/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/dist/assets/permissionStore-BRcQ9Jgt.js +1 -0
- package/dist/assets/php-B4Kqya17.js +1 -0
- package/dist/assets/php-Dhbhpdrm.js +1 -0
- package/dist/assets/pig-CevX1Tat.js +1 -0
- package/dist/assets/pkl-CRusufhw.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plastic-CXnNNlFZ.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/plsql-Dbm3tV2N.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/po-LG0lFwH4.js +1 -0
- package/dist/assets/poimandres-BrOVMvuE.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/polar-DjOs3GWb.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/postcss-GRRkWn2F.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powerquery-DHf-dtTS.js +1 -0
- package/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/dist/assets/powershell-DXrKVo3G.js +1 -0
- package/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/assets/prisma-Dgf8IN_C.js +1 -0
- package/dist/assets/prolog-9JcfiY6I.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/properties-C78fOPTZ.js +1 -0
- package/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/assets/proto-dqx_Sj2i.js +1 -0
- package/dist/assets/protobuf-ChK-085T.js +1 -0
- package/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/assets/pug-CIGw5rlb.js +1 -0
- package/dist/assets/pug-CoOVtr4u.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/dist/assets/puppet-Di57VbSS.js +1 -0
- package/dist/assets/purescript-BgpM7rDH.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6Ihcp-E.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/python-Cso5vRIg.js +1 -0
- package/dist/assets/q-pXgVlZs6.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qml-DmGQGbca.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qmldir-DXd9s5XC.js +1 -0
- package/dist/assets/qss-B7NnhfIi.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-1ts4xG1r.js +1 -0
- package/dist/assets/r-B6wPVr8A.js +1 -0
- package/dist/assets/r-Dspwwk_N.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/racket-CeDx7KO2.js +1 -0
- package/dist/assets/raku-BMJwmrCS.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-BaMsy-R7.js +1 -0
- package/dist/assets/razor-Uh8Bk_45.js +1 -0
- package/dist/assets/red-DR6ZwibK.js +1 -0
- package/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/reg-Cvyu2Gnx.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/regexp-DK-WlHNu.js +1 -0
- package/dist/assets/registerCustomTheme-BBXUCRnG.js +1 -0
- package/dist/assets/rel-BlqNHz8k.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/renderElectronMiniChatApp-DGmmT9Wf.js +1 -0
- package/dist/assets/renderMobileApp-Dd8P9hvM.js +23 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/riscv-C5dpdlr7.js +1 -0
- package/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/assets/ron-j5NfrB3z.js +1 -0
- package/dist/assets/rose-pine-CN2F2lJl.js +1 -0
- package/dist/assets/rose-pine-dawn-0S8NEhsA.js +1 -0
- package/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/assets/rose-pine-moon-DzBDT4rc.js +1 -0
- package/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rosmsg-ZkSIzPhc.js +1 -0
- package/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/dist/assets/rst-BrH8l1NY.js +1 -0
- package/dist/assets/rst-C371KPjr.js +1 -0
- package/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/dist/assets/ruby-DusoydGj.js +1 -0
- package/dist/assets/ruby-Dw2BHqvy.js +1 -0
- package/dist/assets/runtimeSurface-Cnqjhb6Y.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/rust-CxuBE0J8.js +1 -0
- package/dist/assets/sas-B4kiWyti.js +1 -0
- package/dist/assets/sas-DtSHuIwI.js +1 -0
- package/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/sass-bfEVHPJ8.js +1 -0
- package/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/assets/scala-Dtw19ca_.js +1 -0
- package/dist/assets/scheme-C41bIUwD.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scheme-OJppFQF3.js +1 -0
- package/dist/assets/scss-DxBkwadj.js +1 -0
- package/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/assets/sdbl-CvZs3G9c.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-CEJedz68.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shellscript-DgJyDXW6.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/shellsession-DSlzxnCM.js +1 -0
- package/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-dark-CWRQmTmo.js +1 -0
- package/dist/assets/slack-ochin-DO_ok7XK.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-B9IHbYt7.js +1 -0
- package/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/dist/assets/snazzy-light-B5llO_gK.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/assets/solarized-dark-G9yBjKj_.js +1 -0
- package/dist/assets/solarized-light-DeSzljQz.js +1 -0
- package/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/assets/solidity-B3CjIuAd.js +1 -0
- package/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/assets/solr-DehyRSwq.js +1 -0
- package/dist/assets/soy-BSb8SaP0.js +1 -0
- package/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/assets/sparql-BJh4JEw-.js +1 -0
- package/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/splunk-h-bNAnbk.js +1 -0
- package/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/assets/sql-BbONfI8K.js +1 -0
- package/dist/assets/sql-D0XecflT.js +1 -0
- package/dist/assets/ssh-config-CywOjoXH.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/assets/stata-BKjsT8qe.js +1 -0
- package/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/dist/assets/stylus-B533Al4x.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/stylus-CIg617SB.js +1 -0
- package/dist/assets/surrealql-2KNEnAt7.js +1 -0
- package/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/assets/svelte-C_ipcX3V.js +1 -0
- package/dist/assets/svelte-xaxjcSSP.js +1 -0
- package/dist/assets/swift-BzpIVaGY.js +1 -0
- package/dist/assets/swift-D82vCrfD.js +1 -0
- package/dist/assets/swift-tEesUtni.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/synthwave-84-DRVv-_m_.js +1 -0
- package/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/assets/system-verilog-Qdm_1g2q.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/systemd-DKMPPZjB.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/talonscript-eRuyeYHl.js +1 -0
- package/dist/assets/tasl-B9CVvjfG.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-Bq9dbVAF.js +1 -0
- package/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-BVTOctZ7.js +1 -0
- package/dist/assets/templ-P3uqSqPl.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/terraform-CAp711iT.js +1 -0
- package/dist/assets/tex-BHxF5DMe.js +1 -0
- package/dist/assets/tex-idrVyKtj.js +1 -0
- package/dist/assets/textile-CnDTJFAw.js +1 -0
- package/dist/assets/themeResolver-BmJyTmjJ.js +1 -0
- package/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/dist/assets/tiki-DGYXhP31.js +1 -0
- package/dist/assets/tokyo-night-BvwC8l2v.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/dist/assets/toml-Ms459DBw.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/troff-wAsdV37c.js +1 -0
- package/dist/assets/ts-tags-C-gylFal.js +1 -0
- package/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/assets/tsv-BFtm7NXx.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/tsx-D6116Ycy.js +1 -0
- package/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/turtle-DpRhbQZY.js +1 -0
- package/dist/assets/twig-DNn4PbVi.js +1 -0
- package/dist/assets/twig-DuP5mDZ4.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typescript-BqvgUolh.js +1 -0
- package/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/assets/typespec-DhK7u29_.js +1 -0
- package/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/assets/typst-Dfd71gQj.js +1 -0
- package/dist/assets/useAppFontEffects-DXASDavv.js +7043 -0
- package/dist/assets/useInlineCommentController-KJFe98HG.js +2 -0
- package/dist/assets/useOpenInAppsStore-DHAeT9zx.js +1 -0
- package/dist/assets/useWindowTitle-BcwmWC0M.js +10 -0
- package/dist/assets/v-BH76v7br.js +1 -0
- package/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vala-z59IxkFd.js +1 -0
- package/dist/assets/vb-4AxSLKIU.js +1 -0
- package/dist/assets/vb-CmGdzxic.js +1 -0
- package/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/dist/assets/velocity-D8B20fx6.js +1 -0
- package/dist/assets/vendor-aparajita-capacitor-secure-storage-5IShChL0.js +1 -0
- package/dist/assets/vendor-aria-hidden-DvXkyWUv.js +1 -0
- package/dist/assets/vendor-base-ui-DwfTg5wD.js +1 -0
- package/dist/assets/vendor-beautiful-mermaid-r4GK_usS.js +119 -0
- package/dist/assets/vendor-capacitor-app-BrDJj9I_.js +1 -0
- package/dist/assets/vendor-capacitor-core-B4EWRrHz.js +1 -0
- package/dist/assets/vendor-capacitor-keyboard-CYccoJjr.js +1 -0
- package/dist/assets/vendor-capacitor-push-notifications-KNe1zSBF.js +1 -0
- package/dist/assets/vendor-capacitor-status-bar-B-N0EOVk.js +1 -0
- package/dist/assets/vendor-ccount-c2V3InAJ.js +1 -0
- package/dist/assets/vendor-character-entities-html4-D7taRCQ7.js +1 -0
- package/dist/assets/vendor-character-entities-legacy-BpoLgcEt.js +1 -0
- package/dist/assets/vendor-class-variance-authority-5VPnzWs2.js +1 -0
- package/dist/assets/vendor-clsx-B-dksMZM.js +1 -0
- package/dist/assets/vendor-cmdk-BCEUzcHE.js +1 -0
- package/dist/assets/vendor-codemirror-autocomplete-3xMyV7a2.js +1 -0
- package/dist/assets/vendor-codemirror-commands-Bzf12ODH.js +1 -0
- package/dist/assets/vendor-codemirror-lang-angular-0K6s6cvb.js +1 -0
- package/dist/assets/vendor-codemirror-lang-cpp-B0aL03z-.js +1 -0
- package/dist/assets/vendor-codemirror-lang-css-C4jEScp7.js +1 -0
- package/dist/assets/vendor-codemirror-lang-go-CgrfVBPr.js +7 -0
- package/dist/assets/vendor-codemirror-lang-html-C1Svvdey.js +1 -0
- package/dist/assets/vendor-codemirror-lang-java-DNPz7_Bx.js +1 -0
- package/dist/assets/vendor-codemirror-lang-javascript-CrbeaEAU.js +13 -0
- package/dist/assets/vendor-codemirror-lang-jinja-DL8BR_RG.js +1 -0
- package/dist/assets/vendor-codemirror-lang-json-BTvAjwkJ.js +1 -0
- package/dist/assets/vendor-codemirror-lang-less-CPABt6NZ.js +1 -0
- package/dist/assets/vendor-codemirror-lang-liquid-BLOLOBMg.js +1 -0
- package/dist/assets/vendor-codemirror-lang-markdown-BloD9GN-.js +1 -0
- package/dist/assets/vendor-codemirror-lang-php-FIZMTuWM.js +1 -0
- package/dist/assets/vendor-codemirror-lang-python-CR2g6ukK.js +3 -0
- package/dist/assets/vendor-codemirror-lang-rust-lsPqn4k4.js +1 -0
- package/dist/assets/vendor-codemirror-lang-sass-CtECMwzH.js +1 -0
- package/dist/assets/vendor-codemirror-lang-sql-BtmZrrZH.js +2 -0
- package/dist/assets/vendor-codemirror-lang-vue-Br6DrntY.js +1 -0
- package/dist/assets/vendor-codemirror-lang-wast-DT7IlCnb.js +1 -0
- package/dist/assets/vendor-codemirror-lang-xml-wNr-LZOf.js +1 -0
- package/dist/assets/vendor-codemirror-lang-yaml-BTKFwfc2.js +1 -0
- package/dist/assets/vendor-codemirror-language-BuDklWi8.js +3 -0
- package/dist/assets/vendor-codemirror-language-data-CFVH0pe8.js +2 -0
- package/dist/assets/vendor-codemirror-search-BzyfPoRb.js +2 -0
- package/dist/assets/vendor-codemirror-state-Wdz6Qsow.js +5 -0
- package/dist/assets/vendor-codemirror-view-pXtfpqbT.js +4 -0
- package/dist/assets/vendor-comma-separated-tokens-D8TqSuIK.js +1 -0
- package/dist/assets/vendor-crelt-C8TCjufn.js +1 -0
- package/dist/assets/vendor-detect-node-es-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-diff-C3JhlQdm.js +13 -0
- package/dist/assets/vendor-dijkstrajs-COg3n3zL.js +1 -0
- package/dist/assets/vendor-dnd-kit-accessibility-Cv52zwWT.js +1 -0
- package/dist/assets/vendor-dnd-kit-core-GipZW2tY.js +5 -0
- package/dist/assets/vendor-dnd-kit-sortable-Ct2QxIHb.js +1 -0
- package/dist/assets/vendor-dnd-kit-utilities-CrOcp0Bp.js +1 -0
- package/dist/assets/vendor-dompurify-r8glNMJW.js +2 -0
- package/dist/assets/vendor-elkjs-CRCLNXjf.js +24 -0
- package/dist/assets/vendor-entities-C3BA0AJP.js +1 -0
- package/dist/assets/vendor-fflate-C7GOKjkv.js +1 -0
- package/dist/assets/vendor-floating-ui-core-CXmKXOXc.js +1 -0
- package/dist/assets/vendor-floating-ui-dom-Bkw82kL9.js +1 -0
- package/dist/assets/vendor-floating-ui-react-dom-sVFnuruh.js +1 -0
- package/dist/assets/vendor-floating-ui-utils-BzXdqYpP.js +1 -0
- package/dist/assets/vendor-framer-motion-DJASDzNt.js +1 -0
- package/dist/assets/vendor-fuse.js-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-get-nonce-C-Z93AgS.js +1 -0
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +13 -0
- package/dist/assets/vendor-hast-util-to-html-BKBhbTDb.js +9 -0
- package/dist/assets/vendor-hast-util-whitespace-D4Wp6AEg.js +1 -0
- package/dist/assets/vendor-heic2any-L8hyfc24.js +66 -0
- package/dist/assets/vendor-html-to-image-BeoRn2gJ.js +2 -0
- package/dist/assets/vendor-html-void-elements-CkdplsNl.js +1 -0
- package/dist/assets/vendor-katex-CHD9BdDO.js +257 -0
- package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
- package/dist/assets/vendor-lezer-common-B3-cUCR_.js +1 -0
- package/dist/assets/vendor-lezer-cpp-BleAU9o-.js +1 -0
- package/dist/assets/vendor-lezer-css-C5xB6jxd.js +1 -0
- package/dist/assets/vendor-lezer-go-CXK1UKk8.js +1 -0
- package/dist/assets/vendor-lezer-highlight-CDMHUZ74.js +1 -0
- package/dist/assets/vendor-lezer-html-C0I1p52t.js +1 -0
- package/dist/assets/vendor-lezer-java-D_zzj-NV.js +1 -0
- package/dist/assets/vendor-lezer-javascript-D7Tj24ot.js +1 -0
- package/dist/assets/vendor-lezer-json-Iaex0UN-.js +1 -0
- package/dist/assets/vendor-lezer-lr-bhwg1HMg.js +1 -0
- package/dist/assets/vendor-lezer-markdown-C6g-d6P3.js +6 -0
- package/dist/assets/vendor-lezer-php-_DHrbsZG.js +1 -0
- package/dist/assets/vendor-lezer-python-CvuFxeZl.js +1 -0
- package/dist/assets/vendor-lezer-rust-Lqgd5SAo.js +1 -0
- package/dist/assets/vendor-lezer-sass-Dnm2lY4t.js +1 -0
- package/dist/assets/vendor-lezer-xml-hUDuAyS5.js +1 -0
- package/dist/assets/vendor-lezer-yaml-BNIl6S_O.js +1 -0
- package/dist/assets/vendor-lru_map-B-cPXHXW.js +1 -0
- package/dist/assets/vendor-marijn-find-cluster-break-DXwl3gUT.js +1 -0
- package/dist/assets/vendor-marked-i_gIkIP3.js +59 -0
- package/dist/assets/vendor-morphdom-B7dS_VPB.js +1 -0
- package/dist/assets/vendor-motion-dom-DJLgZh4Q.js +1 -0
- package/dist/assets/vendor-motion-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-motion-utils-BGh8QBgx.js +1 -0
- package/dist/assets/vendor-oniguruma-parser-C0D4_9rT.js +33 -0
- package/dist/assets/vendor-oniguruma-to-es-DLBnFDCE.js +94 -0
- package/dist/assets/vendor-pierre-theme-Bp7SdUL6.js +1 -0
- package/dist/assets/vendor-pierre-theming-C6Z4M5_u.js +1 -0
- package/dist/assets/vendor-property-information-rJCbAzvX.js +1 -0
- package/dist/assets/vendor-qrcode-COsmt6jn.js +8 -0
- package/dist/assets/vendor-radix-ui-primitive-Dc_FVRD7.js +1 -0
- package/dist/assets/vendor-radix-ui-react-compose-refs-D1GnwnP9.js +1 -0
- package/dist/assets/vendor-radix-ui-react-context-CM4yFQ2I.js +1 -0
- package/dist/assets/vendor-radix-ui-react-dialog-jX3Dld8c.js +5 -0
- package/dist/assets/vendor-radix-ui-react-dismissable-layer-M7LsH3yL.js +1 -0
- package/dist/assets/vendor-radix-ui-react-focus-guards-D5MZ0nQs.js +1 -0
- package/dist/assets/vendor-radix-ui-react-focus-scope-C2K7lNPv.js +1 -0
- package/dist/assets/vendor-radix-ui-react-id-BcxRjnbX.js +1 -0
- package/dist/assets/vendor-radix-ui-react-portal-xlJWBfDg.js +1 -0
- package/dist/assets/vendor-radix-ui-react-presence-Btn-Dp7L.js +1 -0
- package/dist/assets/vendor-radix-ui-react-primitive-CfpW6_IT.js +1 -0
- package/dist/assets/vendor-radix-ui-react-slot-BxUc3Pvz.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-callback-ref-UKEWHtoP.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-controllable-state-C_ecoh35.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-effect-event-DkJl_Rn_.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-escape-keydown-Co13lHGj.js +1 -0
- package/dist/assets/vendor-radix-ui-react-use-layout-effect-0fyGwS2_.js +1 -0
- package/dist/assets/vendor-react-DYyjh7Q0.js +9 -0
- package/dist/assets/vendor-react-drawio-Boqanm0j.js +1 -0
- package/dist/assets/vendor-react-remove-scroll-CfeMe0DL.js +4 -0
- package/dist/assets/vendor-react-remove-scroll-bar-C9YRs2nU.js +38 -0
- package/dist/assets/vendor-react-style-singleton-B1H4bWSG.js +1 -0
- package/dist/assets/vendor-regex-BBWcyTkg.js +16 -0
- package/dist/assets/vendor-regex-recursion-JU4mF-dD.js +1 -0
- package/dist/assets/vendor-regex-utilities-2BQyy0xI.js +1 -0
- package/dist/assets/vendor-remend-BWYYytN3.js +22 -0
- package/dist/assets/vendor-remixicon-react-CzPq62YO.js +1 -0
- package/dist/assets/vendor-replit-codemirror-vim-BUqe2Slv.js +39 -0
- package/dist/assets/vendor-reselect-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-scheduler-7OC5HNn7.js +1 -0
- package/dist/assets/vendor-shiki-Bbf2GEcE.js +2 -0
- package/dist/assets/vendor-shikijs-core-CGM_jqPK.js +2 -0
- package/dist/assets/vendor-shikijs-engine-javascript-Cme4830A.js +1 -0
- package/dist/assets/vendor-shikijs-engine-oniguruma-DPWuIF1G.js +1 -0
- package/dist/assets/vendor-shikijs-transformers-CZWsLLKm.js +1 -0
- package/dist/assets/vendor-shikijs-types-CPSbSeK6.js +1 -0
- package/dist/assets/vendor-shikijs-vscode-textmate-BgLrS_2D.js +3 -0
- package/dist/assets/vendor-simplewebauthn-browser-VDA66rT_.js +2 -0
- package/dist/assets/vendor-sonner-dWA7qvjr.js +1 -0
- package/dist/assets/vendor-space-separated-tokens-DD3iYX1K.js +1 -0
- package/dist/assets/vendor-stringify-entities-CsH1VEn9.js +1 -0
- package/dist/assets/vendor-style-mod-NZ5MEPvj.js +3 -0
- package/dist/assets/vendor-tailwind-merge-CPcsbTWB.js +1 -0
- package/dist/assets/vendor-tanstack-react-virtual-IrbCBGJ4.js +1 -0
- package/dist/assets/vendor-tanstack-virtual-core-Dubg10g5.js +1 -0
- package/dist/assets/vendor-tslib-De9GV7Vy.js +1 -0
- package/dist/assets/vendor-use-callback-ref-CJB0R85I.js +1 -0
- package/dist/assets/vendor-use-sidecar-C-AuTczP.js +1 -0
- package/dist/assets/vendor-use-sync-external-store-B7OScDIw.js +1 -0
- package/dist/assets/vendor-vite-runtime-DD1OZmLC.js +1 -0
- package/dist/assets/vendor-w3c-keyname-Vcq4gwWv.js +1 -0
- package/dist/assets/vendor-workbox-window-BIl4cyR9.js +2 -0
- package/dist/assets/vendor-zumer-snapdom-Btyu_Xi2.js +8 -0
- package/dist/assets/vendor-zustand-CnsBKjP5.js +6 -0
- package/dist/assets/vendor-zwitch-C2o2j-tx.js +1 -0
- package/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/dist/assets/verilog-LfQUkHDc.js +1 -0
- package/dist/assets/vesper--t9w6MWz.js +1 -0
- package/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/vhdl-DJCsFYgn.js +1 -0
- package/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/viml-pbKHW3Ia.js +1 -0
- package/dist/assets/vitesse-black-B_BYPdvp.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-dark-DOSOf2Sz.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vitesse-light-gUX51GpB.js +1 -0
- package/dist/assets/vue-D2k0BAl5.js +1 -0
- package/dist/assets/vue-DN_0RTcg.js +1 -0
- package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/assets/vue-html-zEfNEgpj.js +1 -0
- package/dist/assets/vue-vine-CQOfvN7w.js +1 -0
- package/dist/assets/vue-vine-D9eCaTqi.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/vyper-DpazWNpx.js +1 -0
- package/dist/assets/wasm-CFi-k9--.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/web-components-CNPQSzpa.js +1390 -0
- package/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wenyan-CYFFG9DK.js +1 -0
- package/dist/assets/wgsl-CxjTZs-b.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wikitext-Bx2qct8O.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wit-DDW3hD69.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/wolfram-pu21K997.js +1 -0
- package/dist/assets/worker-BBk7yuNN.js +153 -0
- package/dist/assets/workerFactory-tw7cIdmV.js +1 -0
- package/dist/assets/xml-BkAJuBCc.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xquery-DzFWVndE.js +1 -0
- package/dist/assets/xsl-C6UUWSil.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/dist/assets/yaml-Bopit2L3.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/dist/assets/zenscript-BIAKq-aR.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/assets/zig-bSFrHQ7n.js +1 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +13 -0
- package/dist/index.html +658 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.svg +13 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.svg +13 -0
- package/dist/mini-chat.html +121 -0
- package/dist/mobile.html +79 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +21 -0
- package/dist/sw.js +1 -0
- package/package.json +115 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +13 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +13 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.svg +13 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.svg +13 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +21 -0
- package/server/index.d.ts +35 -0
- package/server/index.js +206 -0
- package/server/lib/client-auth/pairing.js +308 -0
- package/server/lib/client-auth/pairing.test.js +142 -0
- package/server/lib/client-auth/remote-clients.js +280 -0
- package/server/lib/client-auth/remote-clients.test.js +158 -0
- package/server/lib/cloudflare-tunnel.js +606 -0
- package/server/lib/fs/DOCUMENTATION.md +39 -0
- package/server/lib/fs/pick-directory.js +236 -0
- package/server/lib/fs/pick-directory.test.js +134 -0
- package/server/lib/fs/routes.js +1543 -0
- package/server/lib/fs/routes.test.js +920 -0
- package/server/lib/fs/search.js +238 -0
- package/server/lib/git/DOCUMENTATION.md +180 -0
- package/server/lib/git/credentials.js +41 -0
- package/server/lib/git/identity-storage.js +113 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/issue-2746-longpaths.test.js +206 -0
- package/server/lib/git/routes.js +1089 -0
- package/server/lib/git/routes.test.js +210 -0
- package/server/lib/git/service.js +4927 -0
- package/server/lib/git/service.test.js +1216 -0
- package/server/lib/inherited-env.js +74 -0
- package/server/lib/inherited-env.test.js +65 -0
- package/server/lib/ngrok-tunnel.js +343 -0
- package/server/lib/ngrok-tunnel.test.js +50 -0
- package/server/lib/package-manager.js +850 -0
- package/server/lib/package-manager.test.js +370 -0
- package/server/lib/path-realpath-cache.js +85 -0
- package/server/lib/path-realpath-cache.test.js +75 -0
- package/server/lib/pi/archive-store.js +52 -0
- package/server/lib/pi/archive-store.test.js +32 -0
- package/server/lib/pi/attachment-store.js +98 -0
- package/server/lib/pi/attachment-store.test.js +40 -0
- package/server/lib/pi/custom-themes.js +33 -0
- package/server/lib/pi/custom-themes.test.js +21 -0
- package/server/lib/pi/legacy-ui-model-defaults.js +57 -0
- package/server/lib/pi/legacy-ui-model-defaults.test.js +65 -0
- package/server/lib/pi/model-config-store.js +173 -0
- package/server/lib/pi/model-config-store.test.js +42 -0
- package/server/lib/pi/routes.js +956 -0
- package/server/lib/pi/routes.test.js +733 -0
- package/server/lib/pi/session-daemon/DOCUMENTATION.md +41 -0
- package/server/lib/pi/session-daemon/daemon-process.js +113 -0
- package/server/lib/pi/session-daemon/ipc-client.js +144 -0
- package/server/lib/pi/session-daemon/runtime-registry.js +149 -0
- package/server/lib/pi/session-daemon/runtime-registry.test.js +109 -0
- package/server/lib/pi/session-daemon/session-daemon.js +1903 -0
- package/server/lib/pi/session-daemon/session-daemon.test.js +1306 -0
- package/server/lib/pi/session-daemon/session-jsonl.js +94 -0
- package/server/lib/pi/session-daemon/session-jsonl.test.js +73 -0
- package/server/lib/pi/session-daemon/supervisor.js +458 -0
- package/server/lib/pi/session-daemon/supervisor.test.js +212 -0
- package/server/lib/pi/session-folders-store.js +74 -0
- package/server/lib/pi/session-folders-store.test.js +45 -0
- package/server/lib/pi/session-lifecycle.integration.test.js +197 -0
- package/server/lib/pi/settings-store.js +133 -0
- package/server/lib/pi/settings-store.test.js +113 -0
- package/server/lib/pi/thinking-levels.js +39 -0
- package/server/lib/pi/thinking-levels.test.js +34 -0
- package/server/lib/pi/ui-settings-store.js +68 -0
- package/server/lib/pi/ui-settings-store.test.js +40 -0
- package/server/lib/pichamber-data-dir.js +78 -0
- package/server/lib/pichamber-data-dir.test.js +105 -0
- package/server/lib/pichamber-data-root-authority.test.js +81 -0
- package/server/lib/preview/proxy-runtime.js +1604 -0
- package/server/lib/preview/proxy-runtime.test.js +528 -0
- package/server/lib/pwa-keys-contract.test.ts +62 -0
- package/server/lib/relay/DOCUMENTATION.md +106 -0
- package/server/lib/relay/cross-compat.test.js +135 -0
- package/server/lib/relay/e2ee.js +341 -0
- package/server/lib/relay/e2ee.test.js +124 -0
- package/server/lib/relay/host-client.js +389 -0
- package/server/lib/relay/host-client.test.js +292 -0
- package/server/lib/relay/identity.js +93 -0
- package/server/lib/relay/identity.test.js +78 -0
- package/server/lib/relay/service.js +9 -0
- package/server/lib/relay/signing-key.js +73 -0
- package/server/lib/relay/tunnel-codec.js +373 -0
- package/server/lib/relay/tunnel-codec.test.js +58 -0
- package/server/lib/relay/tunnel-host.js +465 -0
- package/server/lib/security/bind-host.js +40 -0
- package/server/lib/security/bind-host.test.js +36 -0
- package/server/lib/security/packaged-client-origins.js +11 -0
- package/server/lib/security/request-security.js +122 -0
- package/server/lib/security/request-security.test.js +57 -0
- package/server/lib/server/cli-entry-runtime.js +44 -0
- package/server/lib/server/cli-options.js +132 -0
- package/server/lib/server/core-routes.js +1041 -0
- package/server/lib/server/cors.js +51 -0
- package/server/lib/server/cors.test.js +37 -0
- package/server/lib/server/lan-addresses.js +41 -0
- package/server/lib/server/lan-addresses.test.js +42 -0
- package/server/lib/server/path-utils.js +27 -0
- package/server/lib/server/tunnel-auth.js +586 -0
- package/server/lib/terminal/DOCUMENTATION.md +48 -0
- package/server/lib/terminal/history.js +83 -0
- package/server/lib/terminal/history.test.js +19 -0
- package/server/lib/terminal/runtime.js +391 -0
- package/server/lib/terminal/runtime.test.js +566 -0
- package/server/lib/terminal/shells.js +100 -0
- package/server/lib/terminal/shells.test.js +73 -0
- package/server/lib/terminal/terminal-ws-protocol.js +68 -0
- package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
- package/server/lib/terminal/theme-response.js +98 -0
- package/server/lib/terminal/theme-response.test.js +70 -0
- package/server/lib/tunnels/DOCUMENTATION.md +33 -0
- package/server/lib/tunnels/executable-search.js +140 -0
- package/server/lib/tunnels/executable-search.test.js +96 -0
- package/server/lib/tunnels/install-help.js +56 -0
- package/server/lib/tunnels/install-help.test.js +31 -0
- package/server/lib/tunnels/providers/cloudflare.js +54 -0
- package/server/lib/tunnels/providers/ngrok.js +32 -0
- package/server/lib/tunnels/types.js +16 -0
- package/server/lib/ui-auth/DOCUMENTATION.md +44 -0
- package/server/lib/ui-auth/ui-auth.js +972 -0
- package/server/lib/ui-auth/ui-auth.test.js +250 -0
- package/server/lib/ui-auth/ui-passkeys.js +543 -0
- package/server/lib/workspace/host.js +91 -0
|
@@ -0,0 +1,1761 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { EXIT_CODE, TunnelCliError } from './cli-errors.js';
|
|
6
|
+
import { DEFAULT_PORT, findClosestMatch, generateCompletionScript, showTunnelHelp } from './cli-args.js';
|
|
7
|
+
import { requestJson, fetchSystemInfoFromPort, waitForServerHealth } from './cli-http.js';
|
|
8
|
+
import {
|
|
9
|
+
discoverRunningInstances,
|
|
10
|
+
getLatestInstance,
|
|
11
|
+
inspectTunnelAttachability,
|
|
12
|
+
isDesktopRuntimeForPort,
|
|
13
|
+
resolveDoctorPortStatuses,
|
|
14
|
+
resolveTunnelProviders,
|
|
15
|
+
} from './cli-lifecycle.js';
|
|
16
|
+
import {
|
|
17
|
+
normalizeProfileProvider,
|
|
18
|
+
normalizeProfileMode,
|
|
19
|
+
normalizeProfileName,
|
|
20
|
+
normalizeProfileHostname,
|
|
21
|
+
normalizeProfileToken,
|
|
22
|
+
suggestProfileNameFromHostname,
|
|
23
|
+
resolveToken,
|
|
24
|
+
redactProfileForOutput,
|
|
25
|
+
redactProfilesForOutput,
|
|
26
|
+
formatProfileTokenStatus,
|
|
27
|
+
writeTunnelProfilesToDisk,
|
|
28
|
+
writeManagedRemotePairsToDiskFromProfiles,
|
|
29
|
+
resolveProfileByName,
|
|
30
|
+
} from './cli-tunnel-profiles.js';
|
|
31
|
+
import {
|
|
32
|
+
buildTunnelProfileAddCommand,
|
|
33
|
+
buildTunnelStartReplayCommand,
|
|
34
|
+
resolveTunnelTtlOverrides,
|
|
35
|
+
} from './cli-tunnel-utils.js';
|
|
36
|
+
import { DEFAULT_TUNNEL_PROVIDER_CAPABILITIES } from './cli-tunnel-capabilities.js';
|
|
37
|
+
import { assertSafeBrowserPort, buildLocalUrl, isUnsafeBrowserPort } from './cli-network.js';
|
|
38
|
+
import {
|
|
39
|
+
readLastManagedLocalConfigPath,
|
|
40
|
+
writeLastManagedLocalConfigPath,
|
|
41
|
+
} from './cli-paths.js';
|
|
42
|
+
import {
|
|
43
|
+
intro as clackIntro,
|
|
44
|
+
outro as clackOutro,
|
|
45
|
+
log as clackLog,
|
|
46
|
+
box as clackBox,
|
|
47
|
+
confirm as clackConfirm,
|
|
48
|
+
select as clackSelect,
|
|
49
|
+
text as clackText,
|
|
50
|
+
password as clackPassword,
|
|
51
|
+
cancel as clackCancel,
|
|
52
|
+
isCancel as clackIsCancel,
|
|
53
|
+
isJsonMode,
|
|
54
|
+
isQuietMode,
|
|
55
|
+
shouldRenderHumanOutput,
|
|
56
|
+
canPrompt,
|
|
57
|
+
createSpinner,
|
|
58
|
+
createProgress,
|
|
59
|
+
printJson,
|
|
60
|
+
logStatus,
|
|
61
|
+
formatProviderWithIcon as clackFormatProviderWithIcon,
|
|
62
|
+
} from '../cli-output.js';
|
|
63
|
+
|
|
64
|
+
const TUNNEL_PROFILES_VERSION = 1;
|
|
65
|
+
|
|
66
|
+
function getDefaultCloudflaredConfigPath() {
|
|
67
|
+
return path.join(os.homedir(), '.cloudflared', 'config.yml');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isReadableRegularFile(filePath) {
|
|
71
|
+
if (typeof filePath !== 'string' || filePath.trim().length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const stat = fs.statSync(filePath);
|
|
76
|
+
if (!stat.isFile()) return false;
|
|
77
|
+
fs.accessSync(filePath, fs.constants.R_OK);
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function displayTunnelQrCode(url) {
|
|
85
|
+
try {
|
|
86
|
+
const qrcode = await import('qrcode-terminal');
|
|
87
|
+
console.log('\n📱 Scan this QR code to access the tunnel:\n');
|
|
88
|
+
qrcode.default.generate(url, { small: true });
|
|
89
|
+
console.log('');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.warn(`Warning: Could not generate QR code: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isTruthyEnv(value) {
|
|
96
|
+
if (typeof value !== 'string') return false;
|
|
97
|
+
const normalized = value.trim().toLowerCase();
|
|
98
|
+
if (!normalized) return false;
|
|
99
|
+
return normalized !== '0' && normalized !== 'false' && normalized !== 'no';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function shouldDisplayTunnelQr(options) {
|
|
103
|
+
if (options?.json) return false;
|
|
104
|
+
if (options?.quiet) return false;
|
|
105
|
+
if (options?.explicitQr === true) return options.qr === true;
|
|
106
|
+
if (!process.stdout?.isTTY) return false;
|
|
107
|
+
return !isTruthyEnv(process.env.CI);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
function isValidTunnelDoctorResponse(body) {
|
|
112
|
+
if (!body || typeof body !== 'object') {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (body.ok !== true) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (!Array.isArray(body.providerChecks)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!Array.isArray(body.modes)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return body.modes.every((entry) => {
|
|
125
|
+
if (!entry || typeof entry.mode !== 'string') return false;
|
|
126
|
+
// Accept new shape: { ready: boolean, blockers: [] }
|
|
127
|
+
if (typeof entry.ready === 'boolean' && Array.isArray(entry.blockers)) return true;
|
|
128
|
+
// Accept server shape: { checks: [], summary: { ready: boolean } }
|
|
129
|
+
if (Array.isArray(entry.checks) && entry.summary && typeof entry.summary.ready === 'boolean') return true;
|
|
130
|
+
return false;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function resolveTargetInstance({
|
|
135
|
+
options,
|
|
136
|
+
serveCommand,
|
|
137
|
+
allowAutoStart,
|
|
138
|
+
requireAll = false,
|
|
139
|
+
rejectDesktopRuntime = false,
|
|
140
|
+
}) {
|
|
141
|
+
let running = await discoverRunningInstances();
|
|
142
|
+
|
|
143
|
+
if (options.all && requireAll) {
|
|
144
|
+
if (running.length === 0) {
|
|
145
|
+
throw new Error('No running PiChamber instance found. Start one with `pichamber serve`.');
|
|
146
|
+
}
|
|
147
|
+
return running;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (options.explicitPort) {
|
|
151
|
+
const found = running.find((entry) => entry.port === options.port);
|
|
152
|
+
if (found) {
|
|
153
|
+
if (rejectDesktopRuntime) {
|
|
154
|
+
const attachability = await inspectTunnelAttachability(found.port, { requireHealthy: true });
|
|
155
|
+
if (!attachability.attachable) {
|
|
156
|
+
if (attachability.reason === 'desktop') {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Port ${options.port} is used by PiChamber Desktop app. Tunnel attach requires a CLI instance from \`pichamber serve\`.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Port ${options.port} is not an attachable PiChamber tunnel instance. Ensure it is healthy and running PiChamber CLI runtime.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return found;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (rejectDesktopRuntime) {
|
|
170
|
+
const systemInfo = await fetchSystemInfoFromPort(options.port, globalThis.fetch, options.host);
|
|
171
|
+
if (isDesktopRuntimeForPort(systemInfo, options.port)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Port ${options.port} is used by PiChamber Desktop app. Tunnel attach requires a CLI instance from \`pichamber serve\`.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (allowAutoStart) {
|
|
179
|
+
await serveCommand({
|
|
180
|
+
port: options.port,
|
|
181
|
+
explicitPort: true,
|
|
182
|
+
host: options.host,
|
|
183
|
+
uiPassword: options.uiPassword,
|
|
184
|
+
apiOnly: options.apiOnly,
|
|
185
|
+
suppressUnsafePortWarning: true,
|
|
186
|
+
suppressUiPasswordWarning: true,
|
|
187
|
+
suppressStartupSummary: true,
|
|
188
|
+
});
|
|
189
|
+
running = await discoverRunningInstances();
|
|
190
|
+
const started = running.find((entry) => entry.port === options.port);
|
|
191
|
+
if (started) return { ...started, autoStarted: true };
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`No running PiChamber instance found on port ${options.port}.`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (rejectDesktopRuntime) {
|
|
197
|
+
const attachableEntries = [];
|
|
198
|
+
let sawDesktop = false;
|
|
199
|
+
for (const entry of running) {
|
|
200
|
+
const attachability = await inspectTunnelAttachability(entry.port, { requireHealthy: true });
|
|
201
|
+
if (attachability.reason === 'desktop') {
|
|
202
|
+
sawDesktop = true;
|
|
203
|
+
}
|
|
204
|
+
if (attachability.attachable) {
|
|
205
|
+
attachableEntries.push(entry);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (attachableEntries.length === 1) {
|
|
210
|
+
return attachableEntries[0];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (attachableEntries.length > 1) {
|
|
214
|
+
const ports = attachableEntries.map((entry) => entry.port).join(', ');
|
|
215
|
+
throw new Error(`Multiple attachable PiChamber instances found: ${ports}. Use --port <port> or --all.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (allowAutoStart) {
|
|
219
|
+
const startedPort = await serveCommand({
|
|
220
|
+
...options,
|
|
221
|
+
explicitPort: false,
|
|
222
|
+
suppressUnsafePortWarning: true,
|
|
223
|
+
suppressUiPasswordWarning: true,
|
|
224
|
+
suppressStartupSummary: true,
|
|
225
|
+
});
|
|
226
|
+
running = await discoverRunningInstances();
|
|
227
|
+
const started = running.find((entry) => entry.port === startedPort) || getLatestInstance(running);
|
|
228
|
+
if (started) return { ...started, autoStarted: true };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (sawDesktop) {
|
|
232
|
+
throw new Error('Only PiChamber Desktop instance(s) detected. Tunnel attach requires a CLI instance from `pichamber serve`.');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error('No attachable PiChamber instance found. Start one with `pichamber serve`.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (running.length === 1) {
|
|
239
|
+
return running[0];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (running.length === 0) {
|
|
243
|
+
if (allowAutoStart) {
|
|
244
|
+
const startedPort = await serveCommand({
|
|
245
|
+
...options,
|
|
246
|
+
explicitPort: false,
|
|
247
|
+
suppressUnsafePortWarning: true,
|
|
248
|
+
suppressUiPasswordWarning: true,
|
|
249
|
+
});
|
|
250
|
+
running = await discoverRunningInstances();
|
|
251
|
+
const started = running.find((entry) => entry.port === startedPort) || getLatestInstance(running);
|
|
252
|
+
if (started) return { ...started, autoStarted: true };
|
|
253
|
+
}
|
|
254
|
+
throw new Error('No running PiChamber instance found. Start one with `pichamber serve`.');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const ports = running.map((entry) => entry.port).join(', ');
|
|
258
|
+
throw new Error(`Multiple PiChamber instances found: ${ports}. Use --port <port> or --all.`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function resolveTunnelReadEntries(options) {
|
|
262
|
+
const running = await discoverRunningInstances();
|
|
263
|
+
|
|
264
|
+
if (options.explicitPort) {
|
|
265
|
+
const found = running.find((entry) => entry.port === options.port);
|
|
266
|
+
if (!found) {
|
|
267
|
+
throw new Error(`No running PiChamber instance found on port ${options.port}.`);
|
|
268
|
+
}
|
|
269
|
+
return [found];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (running.length === 0) {
|
|
273
|
+
throw new Error('No running PiChamber instance found. Start one with `pichamber serve`.');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return running;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function formatTunnelStatusLine(statusBody, port) {
|
|
280
|
+
const active = Boolean(statusBody?.active);
|
|
281
|
+
const provider = statusBody?.provider || 'unknown';
|
|
282
|
+
const mode = statusBody?.mode || 'unknown';
|
|
283
|
+
const url = statusBody?.url || 'n/a';
|
|
284
|
+
return {
|
|
285
|
+
status: active ? 'success' : 'neutral',
|
|
286
|
+
line: `port ${port} ${active ? 'active' : 'inactive'} (${clackFormatProviderWithIcon(provider)}/${mode})`,
|
|
287
|
+
detail: url,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function formatModeRequirements(mode) {
|
|
292
|
+
const requires = Array.isArray(mode?.requires) ? mode.requires.filter(Boolean) : [];
|
|
293
|
+
if ((mode?.key || '') === 'managed-local') {
|
|
294
|
+
return 'config-path (or default cloudflared config)';
|
|
295
|
+
}
|
|
296
|
+
if (requires.length === 0) {
|
|
297
|
+
return 'none';
|
|
298
|
+
}
|
|
299
|
+
return requires.join(', ');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function annotateTunnelProvidersForOutput(providers) {
|
|
303
|
+
if (!Array.isArray(providers)) return providers;
|
|
304
|
+
return providers.map((provider) => {
|
|
305
|
+
const modes = Array.isArray(provider?.modes) ? provider.modes : [];
|
|
306
|
+
return {
|
|
307
|
+
...provider,
|
|
308
|
+
modes: modes.map((mode) => ({
|
|
309
|
+
...mode,
|
|
310
|
+
displayRequires: formatModeRequirements(mode),
|
|
311
|
+
})),
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function handleTunnelProfileSubcommand(options, action, { boldText, ensureTunnelProfilesMigrated }) {
|
|
317
|
+
const sub = typeof action === 'string' ? action.trim().toLowerCase() : '';
|
|
318
|
+
const store = ensureTunnelProfilesMigrated();
|
|
319
|
+
|
|
320
|
+
if (!sub) {
|
|
321
|
+
if (isJsonMode(options)) {
|
|
322
|
+
printJson({
|
|
323
|
+
command: 'tunnel profile',
|
|
324
|
+
subcommands: ['list', 'show', 'add', 'remove'],
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!isQuietMode(options)) {
|
|
330
|
+
clackIntro('Tunnel Profile');
|
|
331
|
+
logStatus('info', 'Available subcommands', 'list, show, add, remove');
|
|
332
|
+
clackLog.step('List profiles: `pichamber tunnel profile list`');
|
|
333
|
+
clackLog.step('Show one profile: `pichamber tunnel profile show --name <name>`');
|
|
334
|
+
clackLog.step('Add profile: `pichamber tunnel profile add --provider cloudflare --mode managed-remote --name <name> --hostname <host> --token <token>`');
|
|
335
|
+
clackLog.step('Remove profile: `pichamber tunnel profile remove --name <name>`');
|
|
336
|
+
clackOutro('Choose a subcommand');
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (sub === 'list') {
|
|
342
|
+
const providerFilter = normalizeProfileProvider(options.provider);
|
|
343
|
+
const profiles = providerFilter
|
|
344
|
+
? store.profiles.filter((entry) => entry.provider === providerFilter)
|
|
345
|
+
: store.profiles;
|
|
346
|
+
if (isJsonMode(options)) {
|
|
347
|
+
printJson({ profiles: redactProfilesForOutput(profiles, options.showSecrets) });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (isQuietMode(options)) {
|
|
352
|
+
for (const profile of profiles) {
|
|
353
|
+
process.stdout.write(`${profile.name} ${profile.provider}/${profile.mode} ${profile.hostname}\n`);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
clackIntro('Tunnel Profiles');
|
|
359
|
+
for (const profile of profiles) {
|
|
360
|
+
logStatus('success', `${profile.name} (${profile.provider}/${profile.mode})`, `${profile.hostname} ${formatProfileTokenStatus(profile, options.showSecrets)}`);
|
|
361
|
+
}
|
|
362
|
+
clackOutro(`${profiles.length} profile(s)`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (sub === 'show') {
|
|
367
|
+
const name = normalizeProfileName(options.name);
|
|
368
|
+
if (!name) {
|
|
369
|
+
throw new Error('`tunnel profile show` requires --name <name>.');
|
|
370
|
+
}
|
|
371
|
+
const { profile, error } = resolveProfileByName(store.profiles, name, options.provider);
|
|
372
|
+
if (!profile) {
|
|
373
|
+
throw new Error(error);
|
|
374
|
+
}
|
|
375
|
+
if (isJsonMode(options)) {
|
|
376
|
+
printJson({ profile: redactProfileForOutput(profile, options.showSecrets) });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (isQuietMode(options)) {
|
|
380
|
+
process.stdout.write(`${profile.name} ${profile.provider}/${profile.mode} ${profile.hostname} ${formatProfileTokenStatus(profile, options.showSecrets)}\n`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
clackIntro('Tunnel Profile');
|
|
384
|
+
logStatus('success', `${profile.name} (${profile.provider}/${profile.mode})`, `${profile.hostname} ${formatProfileTokenStatus(profile, options.showSecrets)}`);
|
|
385
|
+
clackOutro('show complete');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (sub === 'add') {
|
|
390
|
+
let provider = normalizeProfileProvider(options.provider);
|
|
391
|
+
let mode = normalizeProfileMode(options.mode);
|
|
392
|
+
let name = normalizeProfileName(options.name);
|
|
393
|
+
let hostname = normalizeProfileHostname(options.hostname);
|
|
394
|
+
const resolvedTokenValue = resolveToken(options);
|
|
395
|
+
let token = normalizeProfileToken(resolvedTokenValue);
|
|
396
|
+
|
|
397
|
+
if (canPrompt(options)) {
|
|
398
|
+
if (!provider) {
|
|
399
|
+
const providerResult = await resolveTunnelProviders(options, {
|
|
400
|
+
readPorts: async () => (await discoverRunningInstances()).map((entry) => entry.port),
|
|
401
|
+
});
|
|
402
|
+
const providerOptions = (Array.isArray(providerResult.providers) ? providerResult.providers : [])
|
|
403
|
+
.map((entry) => normalizeProfileProvider(entry?.provider))
|
|
404
|
+
.filter(Boolean)
|
|
405
|
+
.map((providerId) => ({ value: providerId, label: clackFormatProviderWithIcon(providerId) }));
|
|
406
|
+
|
|
407
|
+
if (providerOptions.length === 1) {
|
|
408
|
+
provider = providerOptions[0].value;
|
|
409
|
+
} else if (providerOptions.length > 1) {
|
|
410
|
+
const selectedProvider = await clackSelect({
|
|
411
|
+
message: 'Select tunnel provider',
|
|
412
|
+
options: providerOptions,
|
|
413
|
+
});
|
|
414
|
+
if (clackIsCancel(selectedProvider)) {
|
|
415
|
+
clackCancel('Profile add cancelled.');
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
provider = normalizeProfileProvider(selectedProvider);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!mode) {
|
|
423
|
+
mode = 'managed-remote';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!name) {
|
|
427
|
+
const enteredName = await clackText({
|
|
428
|
+
message: 'Profile name (Enter to accept/edit)',
|
|
429
|
+
placeholder: 'prod-main',
|
|
430
|
+
initialValue: 'prod-main',
|
|
431
|
+
validate(value) {
|
|
432
|
+
return normalizeProfileName(value).length > 0 ? undefined : 'Profile name is required.';
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
if (clackIsCancel(enteredName)) {
|
|
436
|
+
clackCancel('Profile add cancelled.');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
name = normalizeProfileName(enteredName);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const existingProfile = provider && name
|
|
443
|
+
? store.profiles.find((entry) => entry.provider === provider && entry.name.toLowerCase() === name.toLowerCase())
|
|
444
|
+
: null;
|
|
445
|
+
|
|
446
|
+
if (!hostname) {
|
|
447
|
+
const enteredHostname = await clackText({
|
|
448
|
+
message: 'Tunnel hostname (Enter to accept/edit)',
|
|
449
|
+
placeholder: existingProfile?.hostname || 'app.example.com',
|
|
450
|
+
initialValue: existingProfile?.hostname || 'app.example.com',
|
|
451
|
+
validate(value) {
|
|
452
|
+
return normalizeProfileHostname(value).length > 0 ? undefined : 'Hostname is required.';
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
if (clackIsCancel(enteredHostname)) {
|
|
456
|
+
clackCancel('Profile add cancelled.');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
hostname = normalizeProfileHostname(enteredHostname);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!token && existingProfile?.token) {
|
|
463
|
+
const useExistingToken = await clackConfirm({
|
|
464
|
+
message: `Reuse saved token for profile '${existingProfile.name}'?`,
|
|
465
|
+
initialValue: true,
|
|
466
|
+
});
|
|
467
|
+
if (clackIsCancel(useExistingToken)) {
|
|
468
|
+
clackCancel('Profile add cancelled.');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (useExistingToken) {
|
|
472
|
+
token = existingProfile.token;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!provider || !mode || !name || !hostname) {
|
|
478
|
+
throw new Error('`tunnel profile add` requires --provider, --mode managed-remote, --name, and --hostname.');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!token) {
|
|
482
|
+
if (canPrompt(options)) {
|
|
483
|
+
const entered = await clackPassword({
|
|
484
|
+
message: `Enter tunnel token for profile '${name}'`,
|
|
485
|
+
});
|
|
486
|
+
if (clackIsCancel(entered) || !entered || !entered.trim()) {
|
|
487
|
+
clackCancel('Profile add cancelled.');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
token = normalizeProfileToken(entered.trim());
|
|
491
|
+
}
|
|
492
|
+
if (!token) {
|
|
493
|
+
throw new Error('`tunnel profile add` requires a token (--token, --token-file, or --token-stdin).');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (mode !== 'managed-remote') {
|
|
497
|
+
throw new Error('`tunnel profile add` currently supports only --mode managed-remote.');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const existingIndex = store.profiles.findIndex(
|
|
501
|
+
(entry) => entry.provider === provider && entry.name.toLowerCase() === name.toLowerCase()
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (existingIndex >= 0 && !options.force && !options.dryRun) {
|
|
505
|
+
if (canPrompt(options)) {
|
|
506
|
+
const shouldOverwrite = await clackConfirm({
|
|
507
|
+
message: `Profile '${name}' already exists for provider '${provider}'. Overwrite?`,
|
|
508
|
+
});
|
|
509
|
+
if (clackIsCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
510
|
+
clackCancel('Profile add cancelled.');
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
throw new Error(`Profile '${name}' already exists for provider '${provider}'. Use --force to overwrite.`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (options.dryRun) {
|
|
519
|
+
const dryRunResult = {
|
|
520
|
+
ok: true,
|
|
521
|
+
dryRun: true,
|
|
522
|
+
action: existingIndex >= 0 ? 'overwrite' : 'create',
|
|
523
|
+
profile: redactProfileForOutput({ name, provider, mode, hostname, token }, options.showSecrets),
|
|
524
|
+
};
|
|
525
|
+
if (isJsonMode(options)) {
|
|
526
|
+
printJson(dryRunResult);
|
|
527
|
+
} else if (!isQuietMode(options)) {
|
|
528
|
+
clackIntro('Tunnel Profile Add (dry-run)');
|
|
529
|
+
logStatus('info', `Would ${existingIndex >= 0 ? 'overwrite' : 'create'}: ${name} (${provider}/${mode})`, `${hostname} ${formatProfileTokenStatus({ token }, options.showSecrets)}`);
|
|
530
|
+
clackOutro('dry-run complete (no changes applied)');
|
|
531
|
+
}
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const next = [...store.profiles];
|
|
536
|
+
const now = Date.now();
|
|
537
|
+
if (existingIndex >= 0) {
|
|
538
|
+
const current = next[existingIndex];
|
|
539
|
+
next[existingIndex] = {
|
|
540
|
+
...current,
|
|
541
|
+
mode,
|
|
542
|
+
hostname,
|
|
543
|
+
token,
|
|
544
|
+
updatedAt: now,
|
|
545
|
+
};
|
|
546
|
+
} else {
|
|
547
|
+
next.push({
|
|
548
|
+
id: crypto.randomUUID(),
|
|
549
|
+
name,
|
|
550
|
+
provider,
|
|
551
|
+
mode,
|
|
552
|
+
hostname,
|
|
553
|
+
token,
|
|
554
|
+
createdAt: now,
|
|
555
|
+
updatedAt: now,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const persisted = { version: TUNNEL_PROFILES_VERSION, profiles: next };
|
|
560
|
+
writeTunnelProfilesToDisk(persisted);
|
|
561
|
+
writeManagedRemotePairsToDiskFromProfiles(persisted);
|
|
562
|
+
const added = persisted.profiles.find((entry) => entry.provider === provider && entry.name.toLowerCase() === name.toLowerCase());
|
|
563
|
+
|
|
564
|
+
if (isJsonMode(options)) {
|
|
565
|
+
printJson({ ok: true, profile: redactProfileForOutput(added, options.showSecrets) });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (isQuietMode(options)) {
|
|
570
|
+
process.stdout.write(`saved ${added.name} ${added.provider}/${added.mode} ${added.hostname}\n`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
console.log('');
|
|
575
|
+
clackIntro(boldText('Tunnel Profile Saved'));
|
|
576
|
+
logStatus('success', `${added.name} (${added.provider}/${added.mode})`, `${added.hostname} ${formatProfileTokenStatus(added, options.showSecrets)}`);
|
|
577
|
+
clackOutro('save complete');
|
|
578
|
+
logStatus('info', '[START_PROFILE]', `pichamber tunnel start --profile ${added.name}`);
|
|
579
|
+
clackOutro('');
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (sub === 'remove') {
|
|
584
|
+
const name = normalizeProfileName(options.name);
|
|
585
|
+
if (!name) {
|
|
586
|
+
throw new Error('`tunnel profile remove` requires --name <name>.');
|
|
587
|
+
}
|
|
588
|
+
const { profile, error } = resolveProfileByName(store.profiles, name, options.provider);
|
|
589
|
+
if (!profile) {
|
|
590
|
+
throw new Error(error);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const next = store.profiles.filter((entry) => entry.id !== profile.id);
|
|
594
|
+
const persisted = { version: TUNNEL_PROFILES_VERSION, profiles: next };
|
|
595
|
+
writeTunnelProfilesToDisk(persisted);
|
|
596
|
+
writeManagedRemotePairsToDiskFromProfiles(persisted);
|
|
597
|
+
|
|
598
|
+
if (isJsonMode(options)) {
|
|
599
|
+
printJson({ ok: true, removed: redactProfileForOutput(profile, options.showSecrets) });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (isQuietMode(options)) {
|
|
604
|
+
process.stdout.write(`removed ${profile.name} ${profile.provider}/${profile.mode} ${profile.hostname}\n`);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
clackIntro('Tunnel Profile Removed');
|
|
609
|
+
logStatus('success', `${profile.name} (${profile.provider}/${profile.mode})`, profile.hostname);
|
|
610
|
+
clackOutro('remove complete');
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const knownProfileActions = ['list', 'show', 'add', 'remove'];
|
|
615
|
+
const suggestion = findClosestMatch(sub, knownProfileActions);
|
|
616
|
+
const hint = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
617
|
+
throw new TunnelCliError(
|
|
618
|
+
`Unknown tunnel profile subcommand '${sub}'.${hint} Use 'pichamber tunnel help'.`,
|
|
619
|
+
EXIT_CODE.USAGE_ERROR
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
function createTunnelCommand(deps) {
|
|
625
|
+
return (options, subcommand, action) => tunnelCommand(options, subcommand, action, deps);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function tunnelCommand(options, subcommand, action, deps) {
|
|
629
|
+
const { serveCommand, stopCommand, setCancelCleanup, boldText, ensureTunnelProfilesMigrated } = deps;
|
|
630
|
+
switch (subcommand) {
|
|
631
|
+
case 'help':
|
|
632
|
+
showTunnelHelp();
|
|
633
|
+
return;
|
|
634
|
+
case 'profile':
|
|
635
|
+
await handleTunnelProfileSubcommand(options, action, { boldText, ensureTunnelProfilesMigrated });
|
|
636
|
+
return;
|
|
637
|
+
case 'providers': {
|
|
638
|
+
const result = await resolveTunnelProviders(options, {
|
|
639
|
+
readPorts: async () => (await discoverRunningInstances()).map((entry) => entry.port),
|
|
640
|
+
});
|
|
641
|
+
if (isJsonMode(options)) {
|
|
642
|
+
printJson({ providers: annotateTunnelProvidersForOutput(result.providers), source: result.source });
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (isQuietMode(options)) {
|
|
646
|
+
for (const provider of result.providers) {
|
|
647
|
+
const modes = Array.isArray(provider?.modes) ? provider.modes : [];
|
|
648
|
+
const providerId = provider?.provider || 'unknown';
|
|
649
|
+
process.stdout.write(`provider ${providerId} modes ${modes.length}\n`);
|
|
650
|
+
for (const mode of modes) {
|
|
651
|
+
const requires = formatModeRequirements(mode).replace(/,\s+/g, ',');
|
|
652
|
+
process.stdout.write(`mode ${mode?.key || 'unknown'} requires ${requires}\n`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
clackIntro('Tunnel Providers');
|
|
658
|
+
for (const provider of result.providers) {
|
|
659
|
+
const modes = Array.isArray(provider?.modes) ? provider.modes : [];
|
|
660
|
+
clackLog.success(`${clackFormatProviderWithIcon(provider.provider)} — ${modes.length} mode(s)`);
|
|
661
|
+
for (const mode of modes) {
|
|
662
|
+
const label = mode.label || mode.key;
|
|
663
|
+
const requires = formatModeRequirements(mode);
|
|
664
|
+
clackLog.step(`${mode.key} — ${label}\n requires: ${requires}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
clackOutro(`${result.providers.length} provider(s)`);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
case 'ready': {
|
|
671
|
+
const entries = await resolveTunnelReadEntries(options);
|
|
672
|
+
const provider = typeof options.provider === 'string' && options.provider.trim().length > 0
|
|
673
|
+
? options.provider.trim().toLowerCase()
|
|
674
|
+
: 'cloudflare';
|
|
675
|
+
|
|
676
|
+
const results = [];
|
|
677
|
+
for (const entry of entries) {
|
|
678
|
+
try {
|
|
679
|
+
const { response, body } = await requestJson(entry.port, `/api/pichamber/tunnel/check?provider=${encodeURIComponent(provider)}`);
|
|
680
|
+
if (!response.ok) {
|
|
681
|
+
results.push({ port: entry.port, error: body?.error || `check ${response.status}` });
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
results.push({ port: entry.port, result: body });
|
|
685
|
+
} catch (error) {
|
|
686
|
+
results.push({ port: entry.port, error: error instanceof Error ? error.message : String(error) });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (isJsonMode(options)) {
|
|
691
|
+
printJson({ instances: results });
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (isQuietMode(options)) {
|
|
696
|
+
for (const result of results) {
|
|
697
|
+
if (result.error) {
|
|
698
|
+
process.stderr.write(`port ${result.port} failed: ${result.error}\n`);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
const providerId = result.result?.provider || provider;
|
|
702
|
+
if (result.result?.available) {
|
|
703
|
+
process.stdout.write(`port ${result.port} ready ${providerId} ${result.result?.version || 'unknown'}\n`);
|
|
704
|
+
} else {
|
|
705
|
+
process.stdout.write(`port ${result.port} not-ready ${providerId} ${result.result?.message || 'not ready'}\n`);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
clackIntro('Tunnel Ready');
|
|
712
|
+
for (const result of results) {
|
|
713
|
+
if (result.error) {
|
|
714
|
+
logStatus('error', `port ${result.port} failed`, result.error);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
logStatus(
|
|
719
|
+
result.result?.available ? 'success' : 'warning',
|
|
720
|
+
`port ${result.port} provider ${clackFormatProviderWithIcon(result.result?.provider || provider)}`,
|
|
721
|
+
result.result?.available
|
|
722
|
+
? `ready (${result.result?.version || 'unknown version'})`
|
|
723
|
+
: (result.result?.message || 'not ready'),
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
clackOutro(`${results.length} instance(s)`);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
case 'status': {
|
|
730
|
+
const entries = await resolveTunnelReadEntries(options);
|
|
731
|
+
|
|
732
|
+
const results = [];
|
|
733
|
+
for (const entry of entries) {
|
|
734
|
+
try {
|
|
735
|
+
const { response, body } = await requestJson(entry.port, '/api/pichamber/tunnel/status');
|
|
736
|
+
if (!response.ok) {
|
|
737
|
+
results.push({ port: entry.port, error: body?.error || `status ${response.status}` });
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
results.push({ port: entry.port, status: body });
|
|
741
|
+
} catch (error) {
|
|
742
|
+
results.push({ port: entry.port, error: error instanceof Error ? error.message : String(error) });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (isJsonMode(options)) {
|
|
747
|
+
printJson({ instances: results });
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (isQuietMode(options)) {
|
|
752
|
+
for (const result of results) {
|
|
753
|
+
if (result.error) {
|
|
754
|
+
process.stderr.write(`port ${result.port} failed: ${result.error}\n`);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const active = Boolean(result.status?.active);
|
|
758
|
+
const provider = result.status?.provider || 'unknown';
|
|
759
|
+
const mode = result.status?.mode || 'unknown';
|
|
760
|
+
const url = result.status?.url || 'n/a';
|
|
761
|
+
process.stdout.write(`port ${result.port} ${active ? 'active' : 'inactive'} ${provider}/${mode} ${url}\n`);
|
|
762
|
+
}
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
clackIntro('Tunnel Status');
|
|
767
|
+
for (const result of results) {
|
|
768
|
+
if (result.error) {
|
|
769
|
+
logStatus('error', `port ${result.port} failed`, result.error);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const sl = formatTunnelStatusLine(result.status, result.port);
|
|
773
|
+
logStatus(sl.status, sl.line, sl.detail);
|
|
774
|
+
}
|
|
775
|
+
clackOutro(`${results.length} instance(s)`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
case 'doctor': {
|
|
779
|
+
const doctorSpin = createSpinner(options);
|
|
780
|
+
doctorSpin?.start('Running tunnel diagnostics...');
|
|
781
|
+
|
|
782
|
+
// Phase 1: Port discovery
|
|
783
|
+
const { statuses: portStatuses, availableEntries } = await resolveDoctorPortStatuses(options);
|
|
784
|
+
|
|
785
|
+
// Phase 2: Provider diagnostics via the doctor endpoint
|
|
786
|
+
doctorSpin?.message('Checking provider...');
|
|
787
|
+
let providerOption = typeof options.provider === 'string' && options.provider.trim().length > 0
|
|
788
|
+
? options.provider.trim().toLowerCase()
|
|
789
|
+
: '';
|
|
790
|
+
|
|
791
|
+
let doctorProfile = null;
|
|
792
|
+
let doctorHostnameOverride = typeof options.hostname === 'string' ? options.hostname.trim() : '';
|
|
793
|
+
const explicitHostnameProvided = doctorHostnameOverride.length > 0;
|
|
794
|
+
const explicitTokenProvided = Boolean(options.tokenStdin)
|
|
795
|
+
|| (typeof options.token === 'string' && options.token.trim().length > 0)
|
|
796
|
+
|| (typeof options.tokenFile === 'string' && options.tokenFile.trim().length > 0);
|
|
797
|
+
let doctorTokenValue = resolveToken(options);
|
|
798
|
+
let hasSavedManagedRemoteProfile = false;
|
|
799
|
+
const normalizedMode = typeof options.mode === 'string' ? options.mode.trim().toLowerCase() : '';
|
|
800
|
+
|
|
801
|
+
if (typeof options.profile === 'string' && options.profile.trim().length > 0) {
|
|
802
|
+
const store = ensureTunnelProfilesMigrated();
|
|
803
|
+
const resolved = resolveProfileByName(store.profiles, options.profile, providerOption || options.provider);
|
|
804
|
+
if (!resolved.profile) {
|
|
805
|
+
throw new Error(resolved.error);
|
|
806
|
+
}
|
|
807
|
+
doctorProfile = resolved.profile;
|
|
808
|
+
} else if (!doctorHostnameOverride && !explicitTokenProvided && (!normalizedMode || normalizedMode === 'managed-remote')) {
|
|
809
|
+
const store = ensureTunnelProfilesMigrated();
|
|
810
|
+
const remoteProfiles = store.profiles.filter((entry) => {
|
|
811
|
+
if (entry.mode !== 'managed-remote') return false;
|
|
812
|
+
if (!providerOption) return true;
|
|
813
|
+
return entry.provider === providerOption;
|
|
814
|
+
});
|
|
815
|
+
hasSavedManagedRemoteProfile = remoteProfiles.some((entry) => {
|
|
816
|
+
const savedHostname = normalizeProfileHostname(entry.hostname);
|
|
817
|
+
const savedToken = normalizeProfileToken(entry.token);
|
|
818
|
+
return Boolean(savedHostname && savedToken);
|
|
819
|
+
});
|
|
820
|
+
if (remoteProfiles.length === 1) {
|
|
821
|
+
doctorProfile = remoteProfiles[0];
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (doctorProfile) {
|
|
826
|
+
providerOption = providerOption || doctorProfile.provider;
|
|
827
|
+
if (!doctorHostnameOverride && typeof doctorProfile.hostname === 'string') {
|
|
828
|
+
doctorHostnameOverride = doctorProfile.hostname.trim();
|
|
829
|
+
}
|
|
830
|
+
if ((!doctorTokenValue || doctorTokenValue.trim().length === 0) && typeof doctorProfile.token === 'string') {
|
|
831
|
+
doctorTokenValue = doctorProfile.token.trim();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let doctorResult = null;
|
|
836
|
+
let doctorError = null;
|
|
837
|
+
const diagnosticsEntries = [...availableEntries].sort((a, b) => b.mtime - a.mtime);
|
|
838
|
+
if (diagnosticsEntries.length > 0) {
|
|
839
|
+
const query = new URLSearchParams();
|
|
840
|
+
if (providerOption) query.set('provider', providerOption);
|
|
841
|
+
if (typeof options.mode === 'string' && options.mode.trim().length > 0) {
|
|
842
|
+
query.set('mode', options.mode.trim().toLowerCase());
|
|
843
|
+
}
|
|
844
|
+
if (typeof options.configPath === 'string') query.set('configPath', options.configPath);
|
|
845
|
+
if (doctorHostnameOverride.length > 0) {
|
|
846
|
+
query.set('managedRemoteTunnelHostname', doctorHostnameOverride);
|
|
847
|
+
}
|
|
848
|
+
if (hasSavedManagedRemoteProfile) {
|
|
849
|
+
query.set('hasSavedManagedRemoteProfile', '1');
|
|
850
|
+
}
|
|
851
|
+
const doctorBody = {};
|
|
852
|
+
doctorBody.managedRemoteTunnelTokenProvided = explicitTokenProvided;
|
|
853
|
+
doctorBody.managedRemoteTunnelHostnameProvided = explicitHostnameProvided;
|
|
854
|
+
if (typeof doctorTokenValue === 'string' && doctorTokenValue.trim().length > 0) {
|
|
855
|
+
doctorBody.managedRemoteTunnelToken = doctorTokenValue;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const failedPorts = [];
|
|
859
|
+
for (const diagnosticsEntry of diagnosticsEntries) {
|
|
860
|
+
try {
|
|
861
|
+
doctorSpin?.message(`Diagnosing provider on port ${diagnosticsEntry.port}...`);
|
|
862
|
+
const doctorFetchOptions = { timeoutMs: 10000 };
|
|
863
|
+
if (Object.keys(doctorBody).length > 0) {
|
|
864
|
+
doctorFetchOptions.method = 'POST';
|
|
865
|
+
doctorFetchOptions.body = JSON.stringify(doctorBody);
|
|
866
|
+
}
|
|
867
|
+
const { response, body } = await requestJson(
|
|
868
|
+
diagnosticsEntry.port,
|
|
869
|
+
`/api/pichamber/tunnel/doctor?${query.toString()}`,
|
|
870
|
+
doctorFetchOptions,
|
|
871
|
+
);
|
|
872
|
+
if (response.ok && body?.ok && isValidTunnelDoctorResponse(body)) {
|
|
873
|
+
doctorResult = body;
|
|
874
|
+
doctorError = null;
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const looksIncompatible = response.ok && (!body || typeof body !== 'object' || !body.ok);
|
|
879
|
+
const fallbackError = looksIncompatible
|
|
880
|
+
? `port ${diagnosticsEntry.port}: doctor endpoint unavailable or incompatible (restart this CLI instance)`
|
|
881
|
+
: `port ${diagnosticsEntry.port}: ${body?.error || `doctor ${response.status}`}`;
|
|
882
|
+
failedPorts.push(fallbackError);
|
|
883
|
+
} catch (error) {
|
|
884
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
885
|
+
failedPorts.push(`port ${diagnosticsEntry.port}: ${message}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (!doctorResult) {
|
|
890
|
+
doctorError = failedPorts.length > 0
|
|
891
|
+
? failedPorts[0]
|
|
892
|
+
: 'No compatible CLI instance found for tunnel doctor.';
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
doctorSpin?.clear();
|
|
897
|
+
|
|
898
|
+
// JSON output
|
|
899
|
+
if (isJsonMode(options)) {
|
|
900
|
+
const cliPorts = portStatuses
|
|
901
|
+
.filter((s) => s.available)
|
|
902
|
+
.map((s) => ({ port: s.port, type: 'cli', available: true }));
|
|
903
|
+
const desktopPorts = portStatuses
|
|
904
|
+
.filter((s) => !s.available)
|
|
905
|
+
.map((s) => ({ port: s.port, type: 'desktop', available: false }));
|
|
906
|
+
printJson({
|
|
907
|
+
ports: [...cliPorts, ...desktopPorts],
|
|
908
|
+
provider: doctorResult ? {
|
|
909
|
+
id: doctorResult.provider,
|
|
910
|
+
checks: doctorResult.providerChecks || [],
|
|
911
|
+
} : null,
|
|
912
|
+
modes: doctorResult?.modes || [],
|
|
913
|
+
error: doctorError || undefined,
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (isQuietMode(options)) {
|
|
919
|
+
const cliPorts = portStatuses.filter((s) => s.available).map((s) => s.port);
|
|
920
|
+
process.stdout.write(`cli-ports ${cliPorts.join(',') || 'none'}\n`);
|
|
921
|
+
if (doctorError) {
|
|
922
|
+
process.stderr.write(`doctor-error ${doctorError}\n`);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (!doctorResult) {
|
|
926
|
+
process.stdout.write('doctor unavailable\n');
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const providerLabel = doctorResult.provider || providerOption || 'unknown';
|
|
931
|
+
process.stdout.write(`provider ${providerLabel}\n`);
|
|
932
|
+
const modes = Array.isArray(doctorResult.modes) ? doctorResult.modes : [];
|
|
933
|
+
for (const modeEntry of modes) {
|
|
934
|
+
const ready = modeEntry.ready === true || modeEntry.summary?.ready === true;
|
|
935
|
+
if (ready) {
|
|
936
|
+
process.stdout.write(`mode ${modeEntry.mode} ready\n`);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const blockers = Array.isArray(modeEntry.blockers)
|
|
941
|
+
? modeEntry.blockers
|
|
942
|
+
: (Array.isArray(modeEntry.checks)
|
|
943
|
+
? modeEntry.checks
|
|
944
|
+
.filter((c) => c?.status === 'fail' && c?.id !== 'startup_readiness')
|
|
945
|
+
.map((c) => c.detail || c.label || c.id)
|
|
946
|
+
: []);
|
|
947
|
+
process.stdout.write(`mode ${modeEntry.mode} not-ready ${blockers.length || 0}\n`);
|
|
948
|
+
for (const blocker of blockers) {
|
|
949
|
+
process.stdout.write(`blocker ${modeEntry.mode} ${String(blocker)}\n`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ── Section 1: Ports ──────────────────────────────────────
|
|
956
|
+
const cliPorts = portStatuses.filter((s) => s.available);
|
|
957
|
+
const unavailablePorts = portStatuses.filter((s) => !s.available);
|
|
958
|
+
|
|
959
|
+
clackIntro(boldText('Ports'));
|
|
960
|
+
for (const entry of cliPorts) {
|
|
961
|
+
logStatus('success', `port ${entry.port} — CLI (available)`);
|
|
962
|
+
}
|
|
963
|
+
const desktopUnavailablePorts = [];
|
|
964
|
+
for (const entry of unavailablePorts) {
|
|
965
|
+
const isDesktop = typeof entry?.line === 'string' && entry.line.includes('desktop runtime');
|
|
966
|
+
if (isDesktop) {
|
|
967
|
+
desktopUnavailablePorts.push(entry.port);
|
|
968
|
+
logStatus('error', `port ${entry.port} — Desktop (tunneling not supported)`);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
logStatus('error', `port ${entry.port} — No running instance`);
|
|
972
|
+
}
|
|
973
|
+
if (desktopUnavailablePorts.length > 0) {
|
|
974
|
+
clackLog.message('Only CLI instances (pichamber serve) support tunneling.');
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (cliPorts.length === 0 && unavailablePorts.length === 0) {
|
|
978
|
+
logStatus('warning', 'No running instances found', 'Start one with `pichamber serve`.');
|
|
979
|
+
clackOutro('No ports available');
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if (cliPorts.length === 0) {
|
|
983
|
+
logStatus('warning', 'No CLI instances available for tunneling', 'Start one with `pichamber serve`.');
|
|
984
|
+
clackOutro('No CLI ports available');
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
clackOutro(`${cliPorts.length} CLI ${cliPorts.length === 1 ? 'port' : 'ports'} available`);
|
|
988
|
+
console.log('');
|
|
989
|
+
|
|
990
|
+
if (doctorProfile) {
|
|
991
|
+
logStatus('info', 'Using saved profile for managed-remote checks', `${doctorProfile.name} (${doctorProfile.provider}/${doctorProfile.mode})`);
|
|
992
|
+
console.log('');
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// ── Section 2: Provider ─────────────────────────────────
|
|
996
|
+
if (doctorError) {
|
|
997
|
+
clackIntro(boldText('Provider'));
|
|
998
|
+
logStatus('error', 'Provider diagnostics failed', doctorError);
|
|
999
|
+
clackOutro('Failed');
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (!doctorResult) {
|
|
1003
|
+
clackIntro(boldText('Provider'));
|
|
1004
|
+
logStatus('warning', 'Could not reach a running instance for diagnostics');
|
|
1005
|
+
clackOutro('Unavailable');
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const providerLabel = clackFormatProviderWithIcon(doctorResult.provider || 'unknown');
|
|
1010
|
+
clackIntro(boldText(`Provider: ${providerLabel}`));
|
|
1011
|
+
|
|
1012
|
+
let providerPassCount = 0;
|
|
1013
|
+
for (const check of (doctorResult.providerChecks || [])) {
|
|
1014
|
+
const passed = check.status === 'pass';
|
|
1015
|
+
if (passed) {
|
|
1016
|
+
providerPassCount++;
|
|
1017
|
+
logStatus('success', `${check.label}${check.detail ? ` — ${check.detail}` : ''}`);
|
|
1018
|
+
} else {
|
|
1019
|
+
logStatus('error', check.label, check.detail || undefined);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const depCheck = (doctorResult.providerChecks || []).find(
|
|
1024
|
+
(c) => c.id === 'dependency' || c.id === 'provider_dependency',
|
|
1025
|
+
);
|
|
1026
|
+
if (depCheck && depCheck.status !== 'pass') {
|
|
1027
|
+
clackOutro('1 blocker — resolve before checking modes');
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
clackOutro(`${providerPassCount} ${providerPassCount === 1 ? 'check' : 'checks'} passed`);
|
|
1031
|
+
console.log('');
|
|
1032
|
+
|
|
1033
|
+
// ── Section 3: Modes ────────────────────────────────────
|
|
1034
|
+
const DOCTOR_NOISE_CHECK_IDS = new Set(['startup_readiness', 'quick_mode_prerequisites']);
|
|
1035
|
+
const modes = doctorResult.modes || [];
|
|
1036
|
+
if (modes.length === 0) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
clackIntro(boldText('Modes'));
|
|
1041
|
+
let totalBlockers = 0;
|
|
1042
|
+
const troubleshootingHints = [];
|
|
1043
|
+
for (const modeEntry of modes) {
|
|
1044
|
+
const isReady = modeEntry.ready === true || modeEntry.summary?.ready === true;
|
|
1045
|
+
if (isReady) {
|
|
1046
|
+
const passDetail = Array.isArray(modeEntry.checks)
|
|
1047
|
+
? modeEntry.checks.find((c) => c?.status === 'pass' && !DOCTOR_NOISE_CHECK_IDS.has(c?.id))?.detail
|
|
1048
|
+
: null;
|
|
1049
|
+
logStatus('success', `${modeEntry.mode} — Ready${passDetail ? ` (${passDetail})` : ''}`);
|
|
1050
|
+
} else {
|
|
1051
|
+
const blockers = Array.isArray(modeEntry.blockers)
|
|
1052
|
+
? modeEntry.blockers
|
|
1053
|
+
: (Array.isArray(modeEntry.checks)
|
|
1054
|
+
? modeEntry.checks
|
|
1055
|
+
.filter((c) => c?.status === 'fail' && c?.id !== 'startup_readiness')
|
|
1056
|
+
.map((c) => c.detail || c.label || c.id)
|
|
1057
|
+
: []);
|
|
1058
|
+
totalBlockers += blockers.length;
|
|
1059
|
+
const blockerCount = blockers.length;
|
|
1060
|
+
const blockerWord = blockerCount === 1 ? 'blocker' : 'blockers';
|
|
1061
|
+
logStatus('error', `${modeEntry.mode} — Not ready${blockerCount > 0 ? ` (${blockerCount} ${blockerWord})` : ''}`);
|
|
1062
|
+
for (const blocker of blockers) {
|
|
1063
|
+
clackLog.message(` ${blocker}`);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const normalizedBlockers = blockers.map((blocker) => String(blocker).toLowerCase());
|
|
1067
|
+
const isManagedRemote = modeEntry.mode === 'managed-remote';
|
|
1068
|
+
const hasTokenIssue = normalizedBlockers.some((line) => line.includes('token')
|
|
1069
|
+
|| line.includes('unauthorized')
|
|
1070
|
+
|| line.includes('forbidden')
|
|
1071
|
+
|| line.includes('authentication')
|
|
1072
|
+
|| line.includes('auth'));
|
|
1073
|
+
const hasPortOrOriginIssue = normalizedBlockers.some((line) => line.includes('port')
|
|
1074
|
+
|| line.includes('localhost')
|
|
1075
|
+
|| line.includes('127.0.0.1')
|
|
1076
|
+
|| line.includes('connection refused')
|
|
1077
|
+
|| line.includes('dial tcp'));
|
|
1078
|
+
|
|
1079
|
+
if (isManagedRemote && (hasPortOrOriginIssue || hasTokenIssue)) {
|
|
1080
|
+
troubleshootingHints.push({
|
|
1081
|
+
key: 'managed-remote-port',
|
|
1082
|
+
code: '[PORT_MISMATCH]',
|
|
1083
|
+
lines: [
|
|
1084
|
+
'Cloudflare target must match the active PiChamber CLI port.',
|
|
1085
|
+
'Example: `http://127.0.0.1:<port>`',
|
|
1086
|
+
'If CLI picked a different port, update Cloudflare or run `pichamber serve --port <port>`.',
|
|
1087
|
+
],
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (isManagedRemote && hasTokenIssue) {
|
|
1092
|
+
troubleshootingHints.push({
|
|
1093
|
+
key: 'managed-remote-token',
|
|
1094
|
+
code: '[QR_PREFETCH_TOKEN]',
|
|
1095
|
+
lines: [
|
|
1096
|
+
'Some QR readers pre-fetch scanned URLs.',
|
|
1097
|
+
'Pre-fetch can consume one-time bootstrap tokens.',
|
|
1098
|
+
'If validation fails, generate a fresh token/QR and use it immediately in one browser/device.',
|
|
1099
|
+
],
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
clackOutro(totalBlockers > 0 ? `Done (${totalBlockers} ${totalBlockers === 1 ? 'issue' : 'issues'})` : 'All modes ready');
|
|
1105
|
+
|
|
1106
|
+
const dedupedHints = [];
|
|
1107
|
+
const seenHintKeys = new Set();
|
|
1108
|
+
for (const hint of troubleshootingHints) {
|
|
1109
|
+
if (!seenHintKeys.has(hint.key)) {
|
|
1110
|
+
seenHintKeys.add(hint.key);
|
|
1111
|
+
dedupedHints.push(hint);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (dedupedHints.length > 0) {
|
|
1116
|
+
console.log('');
|
|
1117
|
+
clackIntro(boldText('Suggestion notes'));
|
|
1118
|
+
for (const hint of dedupedHints) {
|
|
1119
|
+
const lines = Array.isArray(hint.lines) ? hint.lines : [];
|
|
1120
|
+
const detail = lines.length > 0 ? lines.map((line) => ` ${line}`).join('\n') : undefined;
|
|
1121
|
+
logStatus('info', hint.code || '[NOTE]', detail);
|
|
1122
|
+
}
|
|
1123
|
+
clackOutro(`${dedupedHints.length} ${dedupedHints.length === 1 ? 'suggestion' : 'suggestions'}`);
|
|
1124
|
+
}
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
case 'start': {
|
|
1128
|
+
let provider = typeof options.provider === 'string' && options.provider.trim().length > 0
|
|
1129
|
+
? options.provider.trim().toLowerCase()
|
|
1130
|
+
: '';
|
|
1131
|
+
let mode = typeof options.mode === 'string' && options.mode.trim().length > 0
|
|
1132
|
+
? options.mode.trim().toLowerCase()
|
|
1133
|
+
: '';
|
|
1134
|
+
let resolvedTokenValue = resolveToken(options);
|
|
1135
|
+
let token = typeof resolvedTokenValue === 'string' ? resolvedTokenValue : undefined;
|
|
1136
|
+
let hostname = typeof options.hostname === 'string' ? options.hostname : undefined;
|
|
1137
|
+
let selectedProfile = null;
|
|
1138
|
+
|
|
1139
|
+
if (options.explicitPort) {
|
|
1140
|
+
assertSafeBrowserPort(options.port, { context: 'Tunnel start' });
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (typeof options.profile === 'string' && options.profile.trim().length > 0) {
|
|
1144
|
+
const store = ensureTunnelProfilesMigrated();
|
|
1145
|
+
const resolved = resolveProfileByName(store.profiles, options.profile, provider || options.provider);
|
|
1146
|
+
if (!resolved.profile) {
|
|
1147
|
+
throw new Error(resolved.error);
|
|
1148
|
+
}
|
|
1149
|
+
selectedProfile = resolved.profile;
|
|
1150
|
+
provider = provider || selectedProfile.provider;
|
|
1151
|
+
mode = mode || selectedProfile.mode;
|
|
1152
|
+
token = (typeof token === 'string' && token.trim().length > 0) ? token : selectedProfile.token;
|
|
1153
|
+
hostname = typeof options.hostname === 'string' && options.hostname.trim().length > 0 ? options.hostname : selectedProfile.hostname;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Interactive profile selection when no profile/mode specified in TTY
|
|
1157
|
+
if (!selectedProfile && !mode && canPrompt(options)) {
|
|
1158
|
+
const store = ensureTunnelProfilesMigrated();
|
|
1159
|
+
if (store.profiles.length > 0) {
|
|
1160
|
+
const profileChoice = await clackSelect({
|
|
1161
|
+
message: 'Start from a saved profile or choose a mode?',
|
|
1162
|
+
options: [
|
|
1163
|
+
{ value: '__mode__', label: 'Choose a mode manually' },
|
|
1164
|
+
...store.profiles.map((p) => ({
|
|
1165
|
+
value: p.id,
|
|
1166
|
+
label: `${p.name} (${p.provider}/${p.mode})`,
|
|
1167
|
+
hint: p.hostname,
|
|
1168
|
+
})),
|
|
1169
|
+
],
|
|
1170
|
+
});
|
|
1171
|
+
if (clackIsCancel(profileChoice)) {
|
|
1172
|
+
clackCancel('Tunnel start cancelled.');
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (profileChoice !== '__mode__') {
|
|
1176
|
+
selectedProfile = store.profiles.find((p) => p.id === profileChoice);
|
|
1177
|
+
if (selectedProfile) {
|
|
1178
|
+
provider = provider || selectedProfile.provider;
|
|
1179
|
+
mode = mode || selectedProfile.mode;
|
|
1180
|
+
token = (typeof token === 'string' && token.trim().length > 0) ? token : selectedProfile.token;
|
|
1181
|
+
hostname = typeof options.hostname === 'string' && options.hostname.trim().length > 0 ? options.hostname : selectedProfile.hostname;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
provider = provider || 'cloudflare';
|
|
1188
|
+
|
|
1189
|
+
// Interactive mode selection when mode not yet resolved in TTY
|
|
1190
|
+
if (!mode && canPrompt(options)) {
|
|
1191
|
+
const providerCaps = DEFAULT_TUNNEL_PROVIDER_CAPABILITIES.find(
|
|
1192
|
+
(cap) => cap.provider === provider
|
|
1193
|
+
);
|
|
1194
|
+
const modes = providerCaps?.modes || [];
|
|
1195
|
+
if (modes.length > 1) {
|
|
1196
|
+
const modeChoice = await clackSelect({
|
|
1197
|
+
message: `Select tunnel mode for ${clackFormatProviderWithIcon(provider)}`,
|
|
1198
|
+
options: modes.map((m) => ({
|
|
1199
|
+
value: m.key,
|
|
1200
|
+
label: `${m.key} — ${m.label}`,
|
|
1201
|
+
hint: formatModeRequirements(m) !== 'none' ? `requires: ${formatModeRequirements(m)}` : undefined,
|
|
1202
|
+
})),
|
|
1203
|
+
});
|
|
1204
|
+
if (clackIsCancel(modeChoice)) {
|
|
1205
|
+
clackCancel('Tunnel start cancelled.');
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
mode = modeChoice;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
mode = mode || 'quick';
|
|
1213
|
+
if (mode === 'managed-remote') {
|
|
1214
|
+
if (!(typeof hostname === 'string' && hostname.trim().length > 0)) {
|
|
1215
|
+
if (canPrompt(options)) {
|
|
1216
|
+
const profilesStore = ensureTunnelProfilesMigrated();
|
|
1217
|
+
const lastManagedRemoteProfile = [...profilesStore.profiles]
|
|
1218
|
+
.filter((entry) => entry.provider === provider && entry.mode === 'managed-remote')
|
|
1219
|
+
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))[0];
|
|
1220
|
+
const suggestedHostname = normalizeProfileHostname(lastManagedRemoteProfile?.hostname) || 'app.example.com';
|
|
1221
|
+
const enteredHostname = await clackText({
|
|
1222
|
+
message: 'Enter managed-remote tunnel hostname',
|
|
1223
|
+
placeholder: suggestedHostname,
|
|
1224
|
+
initialValue: suggestedHostname,
|
|
1225
|
+
validate(value) {
|
|
1226
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
1227
|
+
return 'Hostname is required.';
|
|
1228
|
+
}
|
|
1229
|
+
return undefined;
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
if (clackIsCancel(enteredHostname)) {
|
|
1233
|
+
clackCancel('Tunnel start cancelled.');
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
hostname = enteredHostname.trim();
|
|
1237
|
+
} else {
|
|
1238
|
+
throw new Error('Managed-remote mode requires --hostname <hostname>.');
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (!(typeof token === 'string' && token.trim().length > 0)) {
|
|
1243
|
+
if (canPrompt(options)) {
|
|
1244
|
+
const entered = await clackPassword({
|
|
1245
|
+
message: 'Enter managed-remote tunnel token',
|
|
1246
|
+
});
|
|
1247
|
+
if (clackIsCancel(entered) || !entered || !entered.trim()) {
|
|
1248
|
+
clackCancel('Tunnel start cancelled.');
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
token = entered.trim();
|
|
1252
|
+
} else {
|
|
1253
|
+
throw new Error('Managed-remote mode requires a token (--token, --token-file, or --token-stdin).');
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (!selectedProfile && canPrompt(options)) {
|
|
1258
|
+
const runChoice = await clackSelect({
|
|
1259
|
+
message: 'Run once, or save profile and run?',
|
|
1260
|
+
options: [
|
|
1261
|
+
{ value: 'run', label: 'Run once', hint: 'Do not save profile' },
|
|
1262
|
+
{ value: 'save-run', label: 'Save profile and run', hint: 'Reuse with --profile later' },
|
|
1263
|
+
],
|
|
1264
|
+
});
|
|
1265
|
+
if (clackIsCancel(runChoice)) {
|
|
1266
|
+
clackCancel('Tunnel start cancelled.');
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (runChoice === 'save-run') {
|
|
1271
|
+
const suggestedName = suggestProfileNameFromHostname(hostname);
|
|
1272
|
+
const enteredProfileName = await clackText({
|
|
1273
|
+
message: 'Profile name',
|
|
1274
|
+
placeholder: suggestedName,
|
|
1275
|
+
initialValue: suggestedName,
|
|
1276
|
+
validate(value) {
|
|
1277
|
+
return normalizeProfileName(value).length > 0 ? undefined : 'Profile name is required.';
|
|
1278
|
+
},
|
|
1279
|
+
});
|
|
1280
|
+
if (clackIsCancel(enteredProfileName)) {
|
|
1281
|
+
clackCancel('Tunnel start cancelled.');
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const desiredName = normalizeProfileName(enteredProfileName);
|
|
1286
|
+
const store = ensureTunnelProfilesMigrated();
|
|
1287
|
+
const existingIndex = store.profiles.findIndex(
|
|
1288
|
+
(entry) => entry.provider === provider && entry.name.toLowerCase() === desiredName.toLowerCase()
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
if (existingIndex >= 0) {
|
|
1292
|
+
const shouldOverwrite = await clackConfirm({
|
|
1293
|
+
message: `Profile '${desiredName}' already exists. Overwrite and run?`,
|
|
1294
|
+
initialValue: true,
|
|
1295
|
+
});
|
|
1296
|
+
if (clackIsCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
1297
|
+
clackCancel('Tunnel start cancelled.');
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const now = Date.now();
|
|
1303
|
+
const nextProfiles = [...store.profiles];
|
|
1304
|
+
if (existingIndex >= 0) {
|
|
1305
|
+
const current = nextProfiles[existingIndex];
|
|
1306
|
+
nextProfiles[existingIndex] = {
|
|
1307
|
+
...current,
|
|
1308
|
+
mode: 'managed-remote',
|
|
1309
|
+
hostname,
|
|
1310
|
+
token,
|
|
1311
|
+
updatedAt: now,
|
|
1312
|
+
};
|
|
1313
|
+
} else {
|
|
1314
|
+
nextProfiles.push({
|
|
1315
|
+
id: crypto.randomUUID(),
|
|
1316
|
+
name: desiredName,
|
|
1317
|
+
provider,
|
|
1318
|
+
mode: 'managed-remote',
|
|
1319
|
+
hostname,
|
|
1320
|
+
token,
|
|
1321
|
+
createdAt: now,
|
|
1322
|
+
updatedAt: now,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const persisted = { version: TUNNEL_PROFILES_VERSION, profiles: nextProfiles };
|
|
1327
|
+
writeTunnelProfilesToDisk(persisted);
|
|
1328
|
+
writeManagedRemotePairsToDiskFromProfiles(persisted);
|
|
1329
|
+
|
|
1330
|
+
selectedProfile = persisted.profiles.find(
|
|
1331
|
+
(entry) => entry.provider === provider && entry.name.toLowerCase() === desiredName.toLowerCase()
|
|
1332
|
+
) || null;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (typeof options.token === 'string' && !options.tokenFile && !options.tokenStdin && canPrompt(options)) {
|
|
1337
|
+
clackBox(
|
|
1338
|
+
'Token passed via --token is visible in your shell history and process list.\n' +
|
|
1339
|
+
'Consider using --token-file or --token-stdin for better security.',
|
|
1340
|
+
'Security Warning',
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (mode === 'managed-local') {
|
|
1346
|
+
const hasConfigPath = typeof options.configPath === 'string' && options.configPath.trim().length > 0;
|
|
1347
|
+
if (!hasConfigPath && canPrompt(options)) {
|
|
1348
|
+
const lastConfigPath = readLastManagedLocalConfigPath();
|
|
1349
|
+
const defaultConfigPath = getDefaultCloudflaredConfigPath();
|
|
1350
|
+
const suggestedConfigPath = lastConfigPath || defaultConfigPath;
|
|
1351
|
+
const suggestedConfigFound = isReadableRegularFile(suggestedConfigPath);
|
|
1352
|
+
const defaultConfigFound = isReadableRegularFile(defaultConfigPath);
|
|
1353
|
+
|
|
1354
|
+
if (suggestedConfigFound || defaultConfigFound) {
|
|
1355
|
+
const foundPath = suggestedConfigFound ? suggestedConfigPath : defaultConfigPath;
|
|
1356
|
+
const configChoice = await clackSelect({
|
|
1357
|
+
message: 'Managed-local config',
|
|
1358
|
+
options: [
|
|
1359
|
+
{
|
|
1360
|
+
value: 'default',
|
|
1361
|
+
label: 'Use found config',
|
|
1362
|
+
hint: foundPath,
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
value: 'custom',
|
|
1366
|
+
label: 'Enter config path',
|
|
1367
|
+
},
|
|
1368
|
+
],
|
|
1369
|
+
});
|
|
1370
|
+
if (clackIsCancel(configChoice)) {
|
|
1371
|
+
clackCancel('Tunnel start cancelled.');
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (configChoice === 'default') {
|
|
1375
|
+
options.configPath = foundPath;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (!(typeof options.configPath === 'string' && options.configPath.trim().length > 0)) {
|
|
1380
|
+
const enteredPath = await clackText({
|
|
1381
|
+
message: 'Enter managed-local config path',
|
|
1382
|
+
placeholder: suggestedConfigPath,
|
|
1383
|
+
initialValue: suggestedConfigPath,
|
|
1384
|
+
validate(value) {
|
|
1385
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
1386
|
+
return 'Config path is required.';
|
|
1387
|
+
}
|
|
1388
|
+
return undefined;
|
|
1389
|
+
},
|
|
1390
|
+
});
|
|
1391
|
+
if (clackIsCancel(enteredPath)) {
|
|
1392
|
+
clackCancel('Tunnel start cancelled.');
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
options.configPath = enteredPath.trim();
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (typeof options.configPath === 'string' && options.configPath.trim().length > 0) {
|
|
1399
|
+
writeLastManagedLocalConfigPath(options.configPath);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const ttlOverrides = await resolveTunnelTtlOverrides(options);
|
|
1405
|
+
if (ttlOverrides === null) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
const { connectTtlMs, sessionTtlMs } = ttlOverrides;
|
|
1409
|
+
|
|
1410
|
+
if (options.dryRun) {
|
|
1411
|
+
const dryRunResult = {
|
|
1412
|
+
ok: true,
|
|
1413
|
+
dryRun: true,
|
|
1414
|
+
provider,
|
|
1415
|
+
mode,
|
|
1416
|
+
hostname: hostname || null,
|
|
1417
|
+
hasToken: typeof token === 'string' && token.trim().length > 0,
|
|
1418
|
+
profile: selectedProfile ? selectedProfile.name : null,
|
|
1419
|
+
configPath: options.configPath || null,
|
|
1420
|
+
connectTtlMs: connectTtlMs ?? null,
|
|
1421
|
+
sessionTtlMs: sessionTtlMs ?? null,
|
|
1422
|
+
};
|
|
1423
|
+
if (isJsonMode(options)) {
|
|
1424
|
+
printJson(dryRunResult);
|
|
1425
|
+
} else if (!isQuietMode(options)) {
|
|
1426
|
+
clackIntro('Tunnel Start (dry-run)');
|
|
1427
|
+
logStatus('info', `Would start ${clackFormatProviderWithIcon(provider)}/${mode}`, hostname || '(ephemeral URL)');
|
|
1428
|
+
clackOutro('dry-run complete (no changes applied)');
|
|
1429
|
+
}
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (!options.explicitPort && canPrompt(options)) {
|
|
1434
|
+
const runningInstances = await discoverRunningInstances();
|
|
1435
|
+
if (runningInstances.length > 1) {
|
|
1436
|
+
const safeInstances = runningInstances.filter((entry) => !isUnsafeBrowserPort(entry.port));
|
|
1437
|
+
if (safeInstances.length === 0) {
|
|
1438
|
+
throw new TunnelCliError(
|
|
1439
|
+
'All discovered PiChamber instance ports are browser-unsafe. Start or target a safe port (3000, 5173, 8080, or high ephemeral).',
|
|
1440
|
+
EXIT_CODE.USAGE_ERROR,
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const attachabilityResults = await Promise.all(
|
|
1445
|
+
safeInstances.map(async (entry) => ({
|
|
1446
|
+
entry,
|
|
1447
|
+
attachability: await inspectTunnelAttachability(entry.port, { requireHealthy: true }),
|
|
1448
|
+
}))
|
|
1449
|
+
);
|
|
1450
|
+
const attachableSafeInstances = attachabilityResults
|
|
1451
|
+
.filter((item) => item.attachability.attachable)
|
|
1452
|
+
.map((item) => item.entry);
|
|
1453
|
+
|
|
1454
|
+
if (attachableSafeInstances.length === 0) {
|
|
1455
|
+
throw new TunnelCliError(
|
|
1456
|
+
'No attachable PiChamber CLI instances found on safe ports. Start one with `pichamber serve --port 3000`.',
|
|
1457
|
+
EXIT_CODE.USAGE_ERROR,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const selectedPort = await clackSelect({
|
|
1462
|
+
message: 'Select PiChamber instance port',
|
|
1463
|
+
options: attachableSafeInstances.map((entry) => ({
|
|
1464
|
+
value: entry.port,
|
|
1465
|
+
label: `port ${entry.port}`,
|
|
1466
|
+
})),
|
|
1467
|
+
});
|
|
1468
|
+
if (clackIsCancel(selectedPort)) {
|
|
1469
|
+
clackCancel('Tunnel start cancelled.');
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
options.port = Number(selectedPort);
|
|
1473
|
+
options.explicitPort = true;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const instance = await resolveTargetInstance({ options, serveCommand, allowAutoStart: true, rejectDesktopRuntime: true });
|
|
1478
|
+
if (instance?.autoStarted && shouldRenderHumanOutput(options)) {
|
|
1479
|
+
logStatus(
|
|
1480
|
+
'info',
|
|
1481
|
+
`Using auto-started instance on port ${instance.port}`,
|
|
1482
|
+
`logs: pichamber logs -p ${instance.port}`,
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (instance?.autoStarted) {
|
|
1487
|
+
setCancelCleanup(async () => {
|
|
1488
|
+
try {
|
|
1489
|
+
await stopCommand({ explicitPort: true, port: instance.port });
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (instance?.autoStarted) {
|
|
1496
|
+
const healthProgress = await createProgress(options, { max: 60 });
|
|
1497
|
+
healthProgress?.start(`Waiting for PiChamber on port ${instance.port} to become healthy (up to 60s)...`);
|
|
1498
|
+
let progressedSeconds = 0;
|
|
1499
|
+
const healthy = await waitForServerHealth(instance.port, {
|
|
1500
|
+
timeoutMs: 60000,
|
|
1501
|
+
intervalMs: 250,
|
|
1502
|
+
onTick({ elapsedMs, complete }) {
|
|
1503
|
+
if (!healthProgress) return;
|
|
1504
|
+
const elapsedSeconds = Math.min(60, Math.floor(elapsedMs / 1000));
|
|
1505
|
+
const delta = elapsedSeconds - progressedSeconds;
|
|
1506
|
+
if (delta > 0) {
|
|
1507
|
+
healthProgress.advance(delta);
|
|
1508
|
+
progressedSeconds = elapsedSeconds;
|
|
1509
|
+
healthProgress.message(`Waiting for PiChamber health (${progressedSeconds}s / 60s)...`);
|
|
1510
|
+
}
|
|
1511
|
+
if (complete && progressedSeconds < 60) {
|
|
1512
|
+
const remaining = 60 - progressedSeconds;
|
|
1513
|
+
if (remaining > 0) {
|
|
1514
|
+
healthProgress.advance(remaining);
|
|
1515
|
+
progressedSeconds = 60;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1520
|
+
if (!healthy) {
|
|
1521
|
+
healthProgress?.stop('PiChamber is still starting');
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
`PiChamber on port ${instance.port} is still starting after 60s. Startup time can vary by machine performance. ` +
|
|
1524
|
+
`Wait another minute, then check health with \`curl -fsS ${buildLocalUrl(instance.port, '/health')}\`. ` +
|
|
1525
|
+
`If health is OK, retry tunnel start with \`pichamber tunnel start --port ${instance.port}\`. ` +
|
|
1526
|
+
`For diagnostics run \`pichamber logs -p ${instance.port}\`.`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
healthProgress?.stop(`Instance ${instance.port} is healthy`);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if (selectedProfile && mode === 'managed-remote') {
|
|
1533
|
+
const tokenSyncPayload = {
|
|
1534
|
+
presetId: selectedProfile.id,
|
|
1535
|
+
presetName: selectedProfile.name,
|
|
1536
|
+
managedRemoteTunnelHostname: hostname,
|
|
1537
|
+
managedRemoteTunnelToken: token,
|
|
1538
|
+
};
|
|
1539
|
+
const { response: presetResponse, body: presetBody } = await requestJson(instance.port, '/api/pichamber/tunnel/managed-remote-token', {
|
|
1540
|
+
method: 'PUT',
|
|
1541
|
+
body: JSON.stringify(tokenSyncPayload),
|
|
1542
|
+
});
|
|
1543
|
+
if (!presetResponse.ok || !presetBody?.ok) {
|
|
1544
|
+
throw new Error(presetBody?.error || `Failed to sync tunnel profile token (${presetResponse.status})`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
const payload = {
|
|
1549
|
+
provider,
|
|
1550
|
+
mode,
|
|
1551
|
+
...(typeof connectTtlMs === 'number' ? { connectTtlMs } : {}),
|
|
1552
|
+
...(typeof sessionTtlMs === 'number' ? { sessionTtlMs } : {}),
|
|
1553
|
+
...(options.configPath === null ? { configPath: null } : {}),
|
|
1554
|
+
...(typeof options.configPath === 'string' ? { configPath: options.configPath } : {}),
|
|
1555
|
+
...(typeof token === 'string' ? { token } : {}),
|
|
1556
|
+
...(typeof hostname === 'string' ? { hostname } : {}),
|
|
1557
|
+
...(selectedProfile ? {
|
|
1558
|
+
managedRemoteTunnelPresetId: selectedProfile.id,
|
|
1559
|
+
managedRemoteTunnelPresetName: selectedProfile.name,
|
|
1560
|
+
} : {}),
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
const spin = createSpinner(options);
|
|
1564
|
+
spin?.start(`Starting ${clackFormatProviderWithIcon(provider)}/${mode} tunnel...`);
|
|
1565
|
+
|
|
1566
|
+
let response;
|
|
1567
|
+
let body;
|
|
1568
|
+
try {
|
|
1569
|
+
({ response, body } = await requestJson(instance.port, '/api/pichamber/tunnel/start', {
|
|
1570
|
+
method: 'POST',
|
|
1571
|
+
body: JSON.stringify(payload),
|
|
1572
|
+
timeoutMs: 60000,
|
|
1573
|
+
}));
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
if (error instanceof Error && /\/api\/pichamber\/tunnel\/start/.test(error.message) && /timed out/.test(error.message)) {
|
|
1576
|
+
spin?.error('Tunnel start timed out');
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
`Tunnel start timed out after 60s. cloudflared may still be starting; check with \`pichamber tunnel status --port ${instance.port}\`. Run \`pichamber logs -p ${instance.port}\` for details.`
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
spin?.error('Tunnel start failed');
|
|
1582
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1583
|
+
throw new Error(`${message} Run \`pichamber logs -p ${instance.port}\` for details.`);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
if (!response.ok || !body?.ok) {
|
|
1587
|
+
spin?.error('Tunnel start failed');
|
|
1588
|
+
const baseError = body?.error || `Tunnel start failed (${response.status})`;
|
|
1589
|
+
const isCloudflareTimeout = /context deadline exceeded|Client\.Timeout exceeded while awaiting headers|failed to request quick Tunnel/i.test(baseError);
|
|
1590
|
+
const userError = isCloudflareTimeout
|
|
1591
|
+
? `Cloudflare quick tunnel request timed out. ${baseError}`
|
|
1592
|
+
: baseError;
|
|
1593
|
+
throw new Error(`${userError} Run \`pichamber logs -p ${instance.port}\` for details.`);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// Avoid duplicate "Tunnel started" lines: spinner completion is implied by
|
|
1597
|
+
// the subsequent structured success section.
|
|
1598
|
+
spin?.clear();
|
|
1599
|
+
|
|
1600
|
+
const replayCommand = buildTunnelStartReplayCommand({
|
|
1601
|
+
port: instance.port,
|
|
1602
|
+
provider,
|
|
1603
|
+
mode,
|
|
1604
|
+
profileName: selectedProfile?.name,
|
|
1605
|
+
configPath: options.configPath,
|
|
1606
|
+
hostname,
|
|
1607
|
+
connectTtlMs,
|
|
1608
|
+
sessionTtlMs,
|
|
1609
|
+
qr: options.qr === true,
|
|
1610
|
+
noQr: options.noQr === true,
|
|
1611
|
+
includeTokenPlaceholder: !selectedProfile && mode === 'managed-remote' && typeof token === 'string' && token.trim().length > 0,
|
|
1612
|
+
tokenViaStdin: options.tokenStdin === true,
|
|
1613
|
+
tokenFileProvided: typeof options.tokenFile === 'string' && options.tokenFile.trim().length > 0,
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
if (isJsonMode(options)) {
|
|
1617
|
+
printJson({ port: instance.port, replayCommand, ...body });
|
|
1618
|
+
} else if (isQuietMode(options)) {
|
|
1619
|
+
const quietUrl = body.connectUrl || body.url || 'n/a';
|
|
1620
|
+
process.stdout.write(`port ${instance.port} ${quietUrl}\n`);
|
|
1621
|
+
} else {
|
|
1622
|
+
console.log('');
|
|
1623
|
+
clackIntro(boldText('Tunnel Started'));
|
|
1624
|
+
logStatus('success', `port ${instance.port} ${clackFormatProviderWithIcon(body.provider)}/${body.mode}`);
|
|
1625
|
+
logStatus('success', body.connectUrl || body.url || 'n/a');
|
|
1626
|
+
if (body.replacedTunnel) {
|
|
1627
|
+
const revokedBootstrapCount = Number.isFinite(body.revokedBootstrapCount) ? body.revokedBootstrapCount : 0;
|
|
1628
|
+
const invalidatedSessionCount = Number.isFinite(body.invalidatedSessionCount) ? body.invalidatedSessionCount : 0;
|
|
1629
|
+
const previousMode = typeof body?.replaced?.mode === 'string' ? body.replaced.mode : 'unknown';
|
|
1630
|
+
logStatus(
|
|
1631
|
+
'warning',
|
|
1632
|
+
`replaced previous ${previousMode} tunnel`,
|
|
1633
|
+
`revoked ${revokedBootstrapCount}, invalidated ${invalidatedSessionCount}`,
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
clackOutro('');
|
|
1637
|
+
|
|
1638
|
+
const optionalTips = [
|
|
1639
|
+
{ line: 'Check status', detail: 'pichamber tunnel status' },
|
|
1640
|
+
{ line: 'Stop tunnel', detail: 'pichamber tunnel stop' },
|
|
1641
|
+
{ line: 'If needed, repeat with same settings', detail: replayCommand },
|
|
1642
|
+
];
|
|
1643
|
+
|
|
1644
|
+
if (!selectedProfile && mode === 'managed-remote' && typeof hostname === 'string' && hostname.trim().length > 0) {
|
|
1645
|
+
const profileSaveCommand = buildTunnelProfileAddCommand({ provider, hostname });
|
|
1646
|
+
optionalTips.push({ line: 'Optional: save reusable profile (stores hostname + token locally)', detail: profileSaveCommand });
|
|
1647
|
+
optionalTips.push({ line: 'Start from saved profile', detail: 'pichamber tunnel start --profile <name>' });
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
console.log('');
|
|
1651
|
+
clackIntro('Optional Tips');
|
|
1652
|
+
for (const tip of optionalTips) {
|
|
1653
|
+
logStatus('info', tip.line, tip.detail);
|
|
1654
|
+
}
|
|
1655
|
+
clackOutro('');
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
setCancelCleanup(null);
|
|
1659
|
+
|
|
1660
|
+
if (shouldDisplayTunnelQr(options)) {
|
|
1661
|
+
const url = body.connectUrl || body.url;
|
|
1662
|
+
if (typeof url === 'string' && url.length > 0) {
|
|
1663
|
+
await displayTunnelQrCode(url);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
case 'stop': {
|
|
1669
|
+
let entries;
|
|
1670
|
+
if (options.all) {
|
|
1671
|
+
entries = await resolveTargetInstance({ options, serveCommand, allowAutoStart: false, requireAll: true });
|
|
1672
|
+
if (entries.length > 1 && !options.force && canPrompt(options)) {
|
|
1673
|
+
const shouldStop = await clackConfirm({
|
|
1674
|
+
message: `Stop tunnels on all ${entries.length} instances?`,
|
|
1675
|
+
});
|
|
1676
|
+
if (clackIsCancel(shouldStop) || !shouldStop) {
|
|
1677
|
+
clackCancel('Tunnel stop cancelled.');
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
} else {
|
|
1682
|
+
entries = [await resolveTargetInstance({ options, serveCommand, allowAutoStart: false })];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const results = [];
|
|
1686
|
+
for (const entry of entries) {
|
|
1687
|
+
const tunnelStopSpin = shouldRenderHumanOutput(options) ? createSpinner(options) : null;
|
|
1688
|
+
tunnelStopSpin?.start(`Stopping tunnel on port ${entry.port}...`);
|
|
1689
|
+
try {
|
|
1690
|
+
const { response, body } = await requestJson(entry.port, '/api/pichamber/tunnel/stop', {
|
|
1691
|
+
method: 'POST',
|
|
1692
|
+
});
|
|
1693
|
+
if (!response.ok) {
|
|
1694
|
+
tunnelStopSpin?.error(`Failed to stop tunnel on port ${entry.port}`);
|
|
1695
|
+
results.push({ port: entry.port, error: body?.error || `stop ${response.status}` });
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
tunnelStopSpin?.stop(`Stopped tunnel on port ${entry.port}`);
|
|
1699
|
+
results.push({ port: entry.port, result: body });
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
tunnelStopSpin?.error(`Failed to stop tunnel on port ${entry.port}`);
|
|
1702
|
+
results.push({ port: entry.port, error: error instanceof Error ? error.message : String(error) });
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
if (isJsonMode(options)) {
|
|
1707
|
+
printJson({ instances: results });
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
if (isQuietMode(options)) {
|
|
1712
|
+
for (const result of results) {
|
|
1713
|
+
if (result.error) {
|
|
1714
|
+
process.stderr.write(`port ${result.port} failed: ${result.error}\n`);
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
process.stdout.write(`port ${result.port} stopped\n`);
|
|
1718
|
+
}
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
clackIntro('Tunnel Stop');
|
|
1723
|
+
for (const result of results) {
|
|
1724
|
+
if (result.error) {
|
|
1725
|
+
logStatus('error', `port ${result.port} failed`, result.error);
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
logStatus('success', `port ${result.port} stopped`, `revoked ${result.result?.revokedBootstrapCount || 0}, invalidated ${result.result?.invalidatedSessionCount || 0}`);
|
|
1729
|
+
}
|
|
1730
|
+
clackOutro(`${results.length} instance(s)`);
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
case 'completion': {
|
|
1734
|
+
const shell = action || 'bash';
|
|
1735
|
+
const completionScript = generateCompletionScript(shell);
|
|
1736
|
+
if (!completionScript) {
|
|
1737
|
+
throw new TunnelCliError(
|
|
1738
|
+
`Unsupported shell '${shell}'. Supported: bash, zsh, fish.`,
|
|
1739
|
+
EXIT_CODE.USAGE_ERROR
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
process.stdout.write(completionScript);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
default: {
|
|
1746
|
+
const knownTunnelSubcommands = ['help', 'providers', 'ready', 'doctor', 'status', 'start', 'stop', 'profile', 'completion'];
|
|
1747
|
+
const suggestion = findClosestMatch(subcommand, knownTunnelSubcommands);
|
|
1748
|
+
const hint = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
1749
|
+
throw new TunnelCliError(
|
|
1750
|
+
`Unknown tunnel subcommand '${subcommand}'.${hint} Use 'pichamber tunnel help'.`,
|
|
1751
|
+
EXIT_CODE.USAGE_ERROR
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
export {
|
|
1758
|
+
createTunnelCommand,
|
|
1759
|
+
isValidTunnelDoctorResponse,
|
|
1760
|
+
shouldDisplayTunnelQr,
|
|
1761
|
+
};
|