pisper 0.5.30-linux-x64.0 → 0.5.30
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.en.md +61 -0
- package/README.md +51 -51
- package/bin/pisper.mjs +48 -0
- package/lib/install.mjs +392 -0
- package/lib/npm-platform.mjs +41 -0
- package/lib/npm-update.mjs +165 -0
- package/lib/platform.mjs +43 -0
- package/lib/postinstall.mjs +3 -0
- package/package.json +36 -14
- package/updater.pubkey +1 -0
- package/web/.vite/manifest.json +2308 -0
- package/web/assets/AnimatedContent-CYfe_lLU.js +1 -0
- package/web/assets/AnimatedList-_pbK4tS3.js +1 -0
- package/web/assets/AppOverlays-Bk75rUEj.js +1 -0
- package/web/assets/AppSelect-D5GWd61z.js +1 -0
- package/web/assets/AssetsPage-cSaY1vkh.js +1 -0
- package/web/assets/ChannelsPage-QtFzIFTP.js +1 -0
- package/web/assets/ChatDockView-B-sVCFs7.js +2 -0
- package/web/assets/ChatHistoryPage-CC7UAWFt.js +1 -0
- package/web/assets/ChatPage-ChEydSmV.js +9 -0
- package/web/assets/ClickSpark-BhxL8WOQ.js +1 -0
- package/web/assets/ConfigPage-C-scO5qN.js +1 -0
- package/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/assets/MarkdownMessage-BAEy4q6h.js +1 -0
- package/web/assets/MemoryPage-Day4QRAh.js +1 -0
- package/web/assets/MobileViewportStabilizer-B6_7wZP9.js +1 -0
- package/web/assets/PageHeader-Cpy_ynWl.js +1 -0
- package/web/assets/PluginsPage-BTgJ83-b.js +1 -0
- package/web/assets/PreviewPages-D6_JdoLf.js +1 -0
- package/web/assets/SchedulesPage-DfS1AF6X.js +1 -0
- package/web/assets/ShinyText-fuaKxKqJ.js +1 -0
- package/web/assets/SkillsPage-nTR7i95v.js +1 -0
- package/web/assets/StarOrbit-BlI6MhVr.js +1 -0
- package/web/assets/TerminalPanel-B155bQc8.js +18 -0
- package/web/assets/TerminalPanel-BrP-ENHg.css +1 -0
- package/web/assets/Threads-DskDSsK6.js +1 -0
- package/web/assets/WelcomeEffects-B5URmqkZ.js +1 -0
- package/web/assets/WorkflowsPage-CkDLh_63.js +1 -0
- package/web/assets/WorkspacePicker-D_TgesEL.js +1 -0
- package/web/assets/alert-B7D8joCd.js +1 -0
- package/web/assets/andromeeda-vGVdxbeo.js +1 -0
- package/web/assets/angular-html-17rlCfBS.js +1 -0
- package/web/assets/angular-ts-it3MaXWH.js +1 -0
- package/web/assets/arrow-right-DYEIYgH8.js +1 -0
- package/web/assets/astro-BEVDzCov.js +1 -0
- package/web/assets/aurora-x-CDeNXAV0.js +1 -0
- package/web/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/web/assets/ayu-light-C3h-C4tm.js +1 -0
- package/web/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/web/assets/blade-B3yUZIOx.js +1 -0
- package/web/assets/brand-DIEc3fK8.js +1 -0
- package/web/assets/button-DelYZAwn.js +1 -0
- package/web/assets/c-aGSZMVdM.js +1 -0
- package/web/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/web/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/web/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/web/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/web/assets/check-B6-8Fi7y.js +1 -0
- package/web/assets/chevron-down-DPTVkWS3.js +1 -0
- package/web/assets/circle-dot-BYvCj3Kc.js +1 -0
- package/web/assets/code-xml-BfSL1Sr7.js +1 -0
- package/web/assets/coffee-ouOQ86EC.js +1 -0
- package/web/assets/collapsible-BMhFA7cK.js +1 -0
- package/web/assets/copy-Cg3k2NIa.js +1 -0
- package/web/assets/cpp-Bm3vm43L.js +1 -0
- package/web/assets/css-CWcS1xXm.js +1 -0
- package/web/assets/csv-Dx-8-gkx.js +1 -0
- package/web/assets/dark-plus-Cs2F2srj.js +1 -0
- package/web/assets/dist-CtIW49fX.js +1 -0
- package/web/assets/dist-UgKRKBkV.js +1 -0
- package/web/assets/dracula-BHWKrbxM.js +1 -0
- package/web/assets/dracula-soft-5eyTD99u.js +1 -0
- package/web/assets/earth-Dgw7VJAO.js +1 -0
- package/web/assets/es2015-e8bz6pYe.js +41 -0
- package/web/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/web/assets/everforest-light-Df2xbC6M.js +1 -0
- package/web/assets/field-DBCKZVj1.js +1 -0
- package/web/assets/file-BfqrTAv7.js +1 -0
- package/web/assets/file-code-corner-CgSKhBmC.js +1 -0
- package/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/web/assets/github-dark-C-LZuMrd.js +1 -0
- package/web/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/web/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/web/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/web/assets/github-light-EUqPIrTm.js +1 -0
- package/web/assets/github-light-default-BXViO-2h.js +1 -0
- package/web/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/web/assets/glsl-TOBAB6bO.js +1 -0
- package/web/assets/graphql-BEmIBNve.js +1 -0
- package/web/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/web/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/web/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/web/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/web/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/web/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/web/assets/haml-CDWtFtU9.js +1 -0
- package/web/assets/handlebars-BNtjo3pL.js +1 -0
- package/web/assets/horizon-CE9ld1lL.js +1 -0
- package/web/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/web/assets/houston-CsvMBhTu.js +1 -0
- package/web/assets/html-BVyvp26C.js +1 -0
- package/web/assets/html-derivative-BseLpURQ.js +1 -0
- package/web/assets/http-CkCfSMV9.js +1 -0
- package/web/assets/hurl-syYMt6Rn.js +1 -0
- package/web/assets/image-50cHDvep.js +1 -0
- package/web/assets/imba-DsUTQ-LC.js +1 -0
- package/web/assets/index-DzjNXoOF.js +41 -0
- package/web/assets/index-o5MSw11Y.css +2 -0
- package/web/assets/java-duG_UTKe.js +1 -0
- package/web/assets/javascript-DWVbOe4X.js +1 -0
- package/web/assets/jinja-Boj4niAz.js +1 -0
- package/web/assets/jison-CHwnC1Pv.js +1 -0
- package/web/assets/json-qhed-kSA.js +1 -0
- package/web/assets/json5-BR5RXkoi.js +1 -0
- package/web/assets/jsonc-CYpm1nAK.js +1 -0
- package/web/assets/jsonl-CmCQp5Yx.js +1 -0
- package/web/assets/jsx-fcCEM_-L.js +1 -0
- package/web/assets/julia-VoF7UKfw.js +1 -0
- package/web/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/web/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/web/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/web/assets/laserwave-C_8bwKvT.js +1 -0
- package/web/assets/less-DVTAwKKz.js +1 -0
- package/web/assets/light-plus-DVQuIRkW.js +1 -0
- package/web/assets/link-2-CzXN6EFH.js +1 -0
- package/web/assets/loader-circle-DWen9efv.js +1 -0
- package/web/assets/markdown-BYOwaDjH.js +1 -0
- package/web/assets/marko-Cupn1xhw.js +1 -0
- package/web/assets/material-theme-Bm3Qr25_.js +1 -0
- package/web/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/web/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/web/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/web/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/web/assets/mdc-4FfK5iWR.js +1 -0
- package/web/assets/mdx-DQZ5AkYe.js +1 -0
- package/web/assets/message-circle-ChMYUHwd.js +1 -0
- package/web/assets/min-dark-BSWPekZh.js +1 -0
- package/web/assets/min-light-DDpmG2fV.js +1 -0
- package/web/assets/mobile-runtime-recovery-5kUQequ-.js +1 -0
- package/web/assets/monokai-B_bV9PQ9.js +1 -0
- package/web/assets/night-owl-DhmEMT88.js +1 -0
- package/web/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/web/assets/nord-Cb4Vim4T.js +1 -0
- package/web/assets/one-dark-pro-CLwyXe_n.js +1 -0
- package/web/assets/one-light-D7Lr4KcI.js +1 -0
- package/web/assets/panel-top-FDhtHA5b.js +1 -0
- package/web/assets/paperclip-7GUlZYL9.js +1 -0
- package/web/assets/pencil-zlF01M6O.js +1 -0
- package/web/assets/php-CUREQQ7f.js +1 -0
- package/web/assets/pick-system-directory-BnIFt85A.js +1 -0
- package/web/assets/plastic-DQwYfKfQ.js +1 -0
- package/web/assets/play-Cyd97pgZ.js +1 -0
- package/web/assets/poimandres-DRFjx7u4.js +1 -0
- package/web/assets/postcss-BXeXVLqQ.js +1 -0
- package/web/assets/progress-DQgysrvB.js +6 -0
- package/web/assets/pug-CQyGKZpv.js +1 -0
- package/web/assets/python-gzcpVVnB.js +1 -0
- package/web/assets/r-C5FwQflr.js +1 -0
- package/web/assets/react-bits-DQli9s7O.css +1 -0
- package/web/assets/red-DX1BQOs8.js +1 -0
- package/web/assets/regexp--aR2_iz7.js +1 -0
- package/web/assets/rolldown-runtime-B0Z9INg1.js +1 -0
- package/web/assets/rose-pine-aMsaMD1D.js +1 -0
- package/web/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/web/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/web/assets/sass-DXrisJhu.js +1 -0
- package/web/assets/save-DKRJzzxd.js +1 -0
- package/web/assets/scss-Davl4E3T.js +1 -0
- package/web/assets/shellscript-CLZ0U2zV.js +1 -0
- package/web/assets/shield-check-CgwoX3Uc.js +1 -0
- package/web/assets/slack-dark-DnToyrRv.js +1 -0
- package/web/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/web/assets/smithy-Dgaz7aur.js +1 -0
- package/web/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/web/assets/solarized-dark-UdsmMDCL.js +1 -0
- package/web/assets/solarized-light-C1IUL_tW.js +1 -0
- package/web/assets/sql-b92rrcVl.js +1 -0
- package/web/assets/square-BV3fMnhT.js +1 -0
- package/web/assets/square-terminal-DYVL_ZT-.js +1 -0
- package/web/assets/stylus-B6D30XZt.js +1 -0
- package/web/assets/sun-DVC8QKp0.js +1 -0
- package/web/assets/svelte-BfstjgUc.js +1 -0
- package/web/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/web/assets/tag-CvMx-fhc.js +1 -0
- package/web/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/web/assets/tool-labels-BJhLig5u.js +1 -0
- package/web/assets/trash-2-BvneYQCl.js +1 -0
- package/web/assets/ts-tags-Cckpt1jI.js +1 -0
- package/web/assets/tsx-JMDX3aQ7.js +1 -0
- package/web/assets/typescript-BAz-bF8J.js +1 -0
- package/web/assets/use-i18n-lxitBdS_.js +1 -0
- package/web/assets/usePagePrimaryAction-Bw0OFTDZ.js +1 -0
- package/web/assets/utils-C8uzG5AZ.js +1 -0
- package/web/assets/vendor-dockview-D5adAaCg.css +1 -0
- package/web/assets/vendor-dockview-yY5mBM3U.js +1 -0
- package/web/assets/vendor-markdown-CWK5AVhB.js +130 -0
- package/web/assets/vendor-markdown-plugins-DCl4I7Pz.js +263 -0
- package/web/assets/vendor-motion-XxgxfZSY.js +9 -0
- package/web/assets/vendor-react-CHuvPdJE.js +9 -0
- package/web/assets/vendor-router-Bw2aAdMs.js +4 -0
- package/web/assets/vendor-shiki-runtime-CxE5QolQ.js +153 -0
- package/web/assets/vendor-state-DE1O_s6m.js +1 -0
- package/web/assets/vendor-ui-DvrYggeS.js +1 -0
- package/web/assets/vendor-xyflow-CnjXtcyK.js +7 -0
- package/web/assets/vendor-xyflow-DLioOiRN.css +1 -0
- package/web/assets/vesper-DdrHHSXu.js +1 -0
- package/web/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/web/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/web/assets/vitesse-light-VbXTXTou.js +1 -0
- package/web/assets/vue-3H7P6ncC.js +1 -0
- package/web/assets/vue-html-DZGW9MGR.js +1 -0
- package/web/assets/vue-vine-BECt67gq.js +1 -0
- package/web/assets/wasm-BnjxR4X6.js +1 -0
- package/web/assets/wasm-ByWQv1Qj.js +1 -0
- package/web/assets/wgsl-BsKzXJz4.js +1 -0
- package/web/assets/wit-DdvCle-K.js +1 -0
- package/web/assets/wrench-BwSZObVs.js +1 -0
- package/web/assets/xml-CNfvW5JP.js +1 -0
- package/web/assets/yaml-rwi0_p6S.js +1 -0
- package/web/assets/zap-CWu2pt6r.js +1 -0
- package/web/favicon.svg +25 -0
- package/web/icons.svg +24 -0
- package/web/index.html +72 -0
- package/web/memory-galaxy-background.webp +0 -0
- package/web/mobile-startup.html +316 -0
- package/web/notification-sw.js +13 -0
- package/web/tauri-pet.css +117 -0
- package/web/tauri-pet.html +16 -0
- package/web/tauri-pet.js +149 -0
- package/components/runtime/Pisper_Runtime_Node_0.5.28_linux_x86_64.tar.gz +0 -0
- package/components/runtime/Pisper_Runtime_Node_0.5.28_linux_x86_64.tar.gz.sig +0 -1
- package/components/tui/Pisper_TUI_Component_0.5.18_linux_x86_64.tar.gz +0 -0
- package/components/tui/Pisper_TUI_Component_0.5.18_linux_x86_64.tar.gz.sig +0 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Pisper TUI Command Reference
|
|
2
|
+
|
|
3
|
+
[Project home](https://ling-kong-ran.github.io/pisper/) · [GitHub repository](https://github.com/ling-kong-ran/pisper) · [中文](./README.md)
|
|
4
|
+
|
|
5
|
+
Argument notation: `<argument>` is required; `[argument]` is optional.
|
|
6
|
+
|
|
7
|
+
## CLI Commands
|
|
8
|
+
|
|
9
|
+
| Command | Explanation |
|
|
10
|
+
| :--- | :--- |
|
|
11
|
+
| `pisper` | Start the TUI in the current directory with a new empty conversation. History is not resumed automatically. |
|
|
12
|
+
| `pisper --cwd <directory>` | Start the TUI with a new empty conversation in the specified directory. |
|
|
13
|
+
| `pisper resume` | Open the conversation picker across all workspaces. If no history exists, report it and exit. |
|
|
14
|
+
| `pisper resume --cwd <directory>` | Open the conversation picker from a specified launch directory. A resumed conversation keeps its own saved working directory. |
|
|
15
|
+
| `pisper doctor` | Diagnose Sidecar startup, Runtime authentication, capability catalogs, and the latest conversation directory. |
|
|
16
|
+
| `pisper doctor --cwd <directory>` | Run diagnostics with the specified launch workspace. |
|
|
17
|
+
| `pisper web` | Install or use the Web frontend and open its authenticated local settings page in the default browser. Keep the process running while using the page. |
|
|
18
|
+
| `pisper web --cwd <directory>` | Start the Runtime for a specified directory and open the Web settings page. |
|
|
19
|
+
| `pisper help [COMMAND]` | Show global help. `pisper help web` shows help for the Web subcommand. |
|
|
20
|
+
| `pisper -h` / `pisper --help` | Show command help without starting the Runtime. |
|
|
21
|
+
| `pisper -V` / `pisper --version` | Print the installed TUI version. |
|
|
22
|
+
| `pisper update --check` | Check an npm-installed Pisper for updates without installing them. |
|
|
23
|
+
| `pisper update` | Update an npm-installed Pisper through the active npm registry and verify the components again. |
|
|
24
|
+
|
|
25
|
+
## Environment Variables
|
|
26
|
+
|
|
27
|
+
| Variable | Description |
|
|
28
|
+
| :--- | :--- |
|
|
29
|
+
| `PISPER_TUI_MOUSE=1` | Enable mouse wheel scrolling in the transcript; note that mouse capture replaces the terminal's native text selection. |
|
|
30
|
+
| `PISPER_TUI_REDUCED_MOTION=1` | Disable the typewriter reveal and status animations (streaming text appears immediately); useful on low-end terminals and SSH sessions. |
|
|
31
|
+
|
|
32
|
+
## Slash Commands
|
|
33
|
+
|
|
34
|
+
| Command | Explanation |
|
|
35
|
+
| :--- | :--- |
|
|
36
|
+
| `/init` | Analyze the current project and create or improve `AGENTS.md` at the project root. Existing useful guidance is preserved, and no other project files are changed. |
|
|
37
|
+
| `/new` | Create an empty conversation in the TUI launch directory. Unavailable during an Agent run. |
|
|
38
|
+
| `/sessions` | Open the conversation picker across all directories. Switching is unavailable during an Agent run. |
|
|
39
|
+
| `/dir <directory>` | Change the active conversation's working directory. Relative paths resolve from its current directory. Unavailable during an Agent run. |
|
|
40
|
+
| `/changes` | Open Git or SVN changes. In that view, `R` refreshes, `C` commits, `P` pushes Git, and pressing `V` twice reverts. SVN has no Push operation. |
|
|
41
|
+
| `/changes commit <message>` | Commit the current Git or SVN changes with an explicit message. |
|
|
42
|
+
| `/chat` | Return to the Chat message stream from another view. |
|
|
43
|
+
| `/model` | Open the model picker and switch the active conversation model. Only configured Provider models are listed. Unavailable during an Agent run. |
|
|
44
|
+
| `/thinking` | Refresh and select thinking levels supported by the active model. Unavailable during an Agent run. |
|
|
45
|
+
| `/provider [id]` | Edit a Provider's protocol, effective Base URL, and masked API key. Pass `id` to open a known Provider directly. |
|
|
46
|
+
| `/apikey [id]` | Compatibility alias for `/provider`. |
|
|
47
|
+
| `/web` | Open the authenticated local settings page in the default browser using the installed Web frontend. |
|
|
48
|
+
| `/compact` | Summarize older context immediately. Available only for idle conversations with enough history. |
|
|
49
|
+
| `/attach` | Open the attachment picker rooted at the active conversation directory. |
|
|
50
|
+
| `/mode` | Show the active execution mode and accepted values. The mode can also change during an Agent run. |
|
|
51
|
+
| `/mode approval-required` | Ask before writes, Shell commands, and high-risk tools. |
|
|
52
|
+
| `/mode workspace-write` | Auto-approve workspace edits and routine commands. |
|
|
53
|
+
| `/mode full-access` | Allow local files, network access, and Shell commands as the current operating-system user. |
|
|
54
|
+
| `/quit` | Exit the TUI. |
|
|
55
|
+
|
|
56
|
+
## Tool and Skill Commands
|
|
57
|
+
|
|
58
|
+
| Form | Explanation |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| `/<tool> [request]` | Request a Runtime Tool, for example `/read README.md`, `/bash npm test`, or `/web_search Pisper`. The Agent still generates arguments and calls the Tool; the active execution mode and Tool settings remain enforced. |
|
|
61
|
+
| `/skill:<name> [request]` | Request an enabled Skill, for example `/skill:docs-search find signing requirements`. Runtime resources load on demand, and Tools invoked by the Skill follow the same permission chain. |
|
package/README.md
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
# Pisper TUI
|
|
1
|
+
# Pisper TUI 命令参考
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
[项目主页](https://ling-kong-ran.github.io/pisper/) · [GitHub 仓库](https://github.com/ling-kong-ran/pisper) · [English](./README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
参数约定:`<参数>` 必填,`[参数]` 可选。
|
|
6
6
|
|
|
7
|
-
## CLI
|
|
7
|
+
## CLI 命令
|
|
8
8
|
|
|
9
|
-
|
|
|
9
|
+
| 命令 | 解释 |
|
|
10
10
|
| :--- | :--- |
|
|
11
|
-
| `pisper` |
|
|
12
|
-
| `pisper --cwd
|
|
13
|
-
| `pisper resume` |
|
|
14
|
-
| `pisper resume --cwd
|
|
15
|
-
| `pisper doctor` |
|
|
16
|
-
| `pisper doctor --cwd
|
|
17
|
-
| `pisper web` |
|
|
18
|
-
| `pisper web --cwd
|
|
19
|
-
| `pisper help [COMMAND]` |
|
|
20
|
-
| `pisper -h` / `pisper --help` |
|
|
21
|
-
| `pisper -V` / `pisper --version` |
|
|
22
|
-
| `pisper update --check` |
|
|
23
|
-
| `pisper update` |
|
|
24
|
-
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
|
11
|
+
| `pisper` | 在当前目录启动 TUI,并创建一个空会话;不会自动恢复历史会话。 |
|
|
12
|
+
| `pisper --cwd <目录>` | 使用指定目录启动 TUI 并创建空会话。 |
|
|
13
|
+
| `pisper resume` | 打开跨工作目录的历史会话选择器;没有历史会话时提示后退出。 |
|
|
14
|
+
| `pisper resume --cwd <目录>` | 从指定启动目录进入历史会话选择器;恢复后仍使用会话自身保存的工作目录。 |
|
|
15
|
+
| `pisper doctor` | 检查 Sidecar、Runtime 鉴权、能力目录与最近会话目录,并输出诊断信息。 |
|
|
16
|
+
| `pisper doctor --cwd <目录>` | 以指定目录作为启动工作区执行诊断。 |
|
|
17
|
+
| `pisper web` | 安装或使用 Web 前端,并在默认浏览器打开本机认证配置页;命令运行期间需保持进程存活。 |
|
|
18
|
+
| `pisper web --cwd <目录>` | 以指定目录启动 Runtime 并打开 Web 配置页。 |
|
|
19
|
+
| `pisper help [COMMAND]` | 显示全局帮助;`pisper help web` 显示 Web 子命令帮助。 |
|
|
20
|
+
| `pisper -h` / `pisper --help` | 显示命令帮助,不启动 Runtime。 |
|
|
21
|
+
| `pisper -V` / `pisper --version` | 显示已安装的 TUI 版本。 |
|
|
22
|
+
| `pisper update --check` | 检查 npm 安装的 Pisper 是否有更新,不执行安装。 |
|
|
23
|
+
| `pisper update` | 使用当前 npm registry 更新 npm 安装的 Pisper,并重新校验组件。 |
|
|
24
|
+
|
|
25
|
+
## 环境变量
|
|
26
|
+
|
|
27
|
+
| 变量 | 解释 |
|
|
28
28
|
| :--- | :--- |
|
|
29
|
-
| `PISPER_TUI_MOUSE=1` |
|
|
30
|
-
| `PISPER_TUI_REDUCED_MOTION=1` |
|
|
29
|
+
| `PISPER_TUI_MOUSE=1` | 启用鼠标滚轮滚动聊天记录;注意鼠标捕获会接管终端原生文本选择。 |
|
|
30
|
+
| `PISPER_TUI_REDUCED_MOTION=1` | 关闭打字机逐字显现与状态动画(流式文本立即完整显示),适合低配终端与 SSH 会话。 |
|
|
31
31
|
|
|
32
|
-
## Slash
|
|
32
|
+
## Slash Command
|
|
33
33
|
|
|
34
|
-
|
|
|
34
|
+
| 命令 | 解释 |
|
|
35
35
|
| :--- | :--- |
|
|
36
|
-
| `/init` |
|
|
37
|
-
| `/new` |
|
|
38
|
-
| `/sessions` |
|
|
39
|
-
| `/dir
|
|
40
|
-
| `/changes` |
|
|
41
|
-
| `/changes commit <message>` |
|
|
42
|
-
| `/chat` |
|
|
43
|
-
| `/model` |
|
|
44
|
-
| `/thinking` |
|
|
45
|
-
| `/provider [id]` |
|
|
46
|
-
| `/apikey [id]` |
|
|
47
|
-
| `/web` |
|
|
48
|
-
| `/compact` |
|
|
49
|
-
| `/attach` |
|
|
50
|
-
| `/mode` |
|
|
51
|
-
| `/mode approval-required` |
|
|
52
|
-
| `/mode workspace-write` |
|
|
53
|
-
| `/mode full-access` |
|
|
54
|
-
| `/quit` |
|
|
55
|
-
|
|
56
|
-
## Tool
|
|
57
|
-
|
|
58
|
-
|
|
|
36
|
+
| `/init` | 分析当前项目并创建或完善项目根目录的 `AGENTS.md`;保留已有有效说明,不修改其他项目文件。 |
|
|
37
|
+
| `/new` | 在 TUI 启动目录创建空会话;Agent 运行期间不可执行。 |
|
|
38
|
+
| `/sessions` | 打开跨所有目录的历史会话选择器;Agent 运行期间不可切换会话。 |
|
|
39
|
+
| `/dir <目录>` | 修改当前会话的工作目录;相对路径以当前会话目录为基准,Agent 运行期间不可修改。 |
|
|
40
|
+
| `/changes` | 打开 Git 或 SVN 改动视图。视图内 `R` 刷新、`C` 提交、`P` 推送 Git、连续两次 `V` 撤销;SVN 不提供 Push。 |
|
|
41
|
+
| `/changes commit <message>` | 使用指定提交信息提交当前 Git 或 SVN 工作区改动。 |
|
|
42
|
+
| `/chat` | 从其他视图返回 Chat 消息流。 |
|
|
43
|
+
| `/model` | 打开模型选择器并切换当前会话模型;只列出已配置 Provider 的模型,Agent 运行期间不可切换。 |
|
|
44
|
+
| `/thinking` | 刷新并选择当前模型支持的思考等级;Agent 运行期间不可切换。 |
|
|
45
|
+
| `/provider [id]` | 编辑 Provider 协议、当前生效 Base URL 与掩码 API Key;传入 `id` 可直达指定 Provider。 |
|
|
46
|
+
| `/apikey [id]` | `/provider` 的兼容别名。 |
|
|
47
|
+
| `/web` | 使用已安装的 Web 前端,在默认浏览器打开本机认证配置页。 |
|
|
48
|
+
| `/compact` | 立即摘要较早上下文;仅适用于有足够历史且当前空闲的会话。 |
|
|
49
|
+
| `/attach` | 打开当前会话目录的附件选择器。 |
|
|
50
|
+
| `/mode` | 显示当前执行模式及可用值;Agent 运行期间也可调整。 |
|
|
51
|
+
| `/mode approval-required` | 写入、Shell 和高风险工具执行前请求授权。 |
|
|
52
|
+
| `/mode workspace-write` | 自动批准工作区修改和常规命令。 |
|
|
53
|
+
| `/mode full-access` | 允许以当前系统用户权限访问本机文件、网络与 Shell。 |
|
|
54
|
+
| `/quit` | 退出 TUI。 |
|
|
55
|
+
|
|
56
|
+
## Tool 与 Skill 命令
|
|
57
|
+
|
|
58
|
+
| 形式 | 解释 |
|
|
59
59
|
| :--- | :--- |
|
|
60
|
-
| `/<tool> [
|
|
61
|
-
| `/skill:<name> [
|
|
60
|
+
| `/<tool> [请求]` | 请求 Runtime Tool,例如 `/read README.md`、`/bash npm test` 或 `/web_search Pisper`。Agent 仍负责生成参数并调用 Tool,当前执行模式与 Tool 开关继续生效。 |
|
|
61
|
+
| `/skill:<name> [请求]` | 请求已启用 Skill,例如 `/skill:docs-search 查找签名要求`。Runtime 按需加载 Skill 资源,其中触发的 Tool 继续经过同一权限链。 |
|
package/bin/pisper.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { ensurePisperInstallation } from '../lib/install.mjs'
|
|
7
|
+
import { handleNpmHelp, handleNpmUpdate } from '../lib/npm-update.mjs'
|
|
8
|
+
|
|
9
|
+
const frontendRoot = fileURLToPath(new URL('../web', import.meta.url))
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const arguments_ = process.argv.slice(2)
|
|
13
|
+
if (!handleNpmHelp(arguments_) && !(await handleNpmUpdate(arguments_))) {
|
|
14
|
+
const installation = await ensurePisperInstallation()
|
|
15
|
+
const child = spawn(installation.executable, arguments_, {
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
PISPER_APP_ROOT: installation.appRoot,
|
|
19
|
+
PISPER_DISTRIBUTION: 'npm',
|
|
20
|
+
PISPER_FRONTEND_ROOT: frontendRoot,
|
|
21
|
+
PISPER_RUNTIME_NODE: process.execPath,
|
|
22
|
+
},
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
windowsHide: false,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
28
|
+
process.on(signal, () => {
|
|
29
|
+
if (!child.killed) child.kill(signal)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
child.once('error', (error) => {
|
|
34
|
+
console.error(`pisper: failed to start the terminal client: ${error.message}`)
|
|
35
|
+
process.exitCode = 1
|
|
36
|
+
})
|
|
37
|
+
child.once('exit', (code, signal) => {
|
|
38
|
+
if (signal) {
|
|
39
|
+
process.kill(process.pid, signal)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
process.exitCode = code ?? 1
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`pisper: ${error instanceof Error ? error.message : String(error)}`)
|
|
47
|
+
process.exitCode = 1
|
|
48
|
+
}
|
package/lib/install.mjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { chmod, mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
import { promisify } from 'node:util'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
import { PublicKey, Signature } from '@threema/wasm-minisign-verify'
|
|
9
|
+
import { x as extractTar } from 'tar'
|
|
10
|
+
import { npmPlatformAlias, npmPlatformTarget, npmPlatformVersion } from './npm-platform.mjs'
|
|
11
|
+
import {
|
|
12
|
+
componentsRoot,
|
|
13
|
+
executableName,
|
|
14
|
+
releaseArchitecture,
|
|
15
|
+
releasePlatform,
|
|
16
|
+
supportedTarget,
|
|
17
|
+
} from './platform.mjs'
|
|
18
|
+
|
|
19
|
+
const execFileAsync = promisify(execFile)
|
|
20
|
+
const require = createRequire(import.meta.url)
|
|
21
|
+
const packageRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
22
|
+
const packageManifest = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'))
|
|
23
|
+
const tuiVersion = packageManifest.pisper?.tuiVersion
|
|
24
|
+
const runtimeVersion = packageManifest.pisper?.runtimeVersion
|
|
25
|
+
const MAX_ARCHIVE_BYTES = 128 * 1024 * 1024
|
|
26
|
+
const INSTALL_WAIT_MS = 600_000
|
|
27
|
+
const LEGACY_LOCK_GRACE_MS = 300_000
|
|
28
|
+
|
|
29
|
+
for (const [label, value] of [
|
|
30
|
+
['TUI', tuiVersion],
|
|
31
|
+
['Runtime', runtimeVersion],
|
|
32
|
+
]) {
|
|
33
|
+
if (!/^\d+\.\d+\.\d+$/.test(value || '')) {
|
|
34
|
+
throw new Error(`pisper package metadata does not contain a valid ${label} version`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function decodeWrapped(value) {
|
|
39
|
+
const trimmed = String(value).trim()
|
|
40
|
+
if (trimmed.startsWith('untrusted comment:')) return trimmed
|
|
41
|
+
if (!/^[A-Za-z0-9+/=\r\n]+$/.test(trimmed)) return trimmed
|
|
42
|
+
const decoded = Buffer.from(trimmed.replace(/\s/g, ''), 'base64').toString('utf8').trim()
|
|
43
|
+
return decoded.startsWith('untrusted comment:') ? decoded : trimmed
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function componentAsset(component, version, platform = process.platform, arch = process.arch) {
|
|
47
|
+
const target = supportedTarget(platform, arch)
|
|
48
|
+
const label = component === 'tui' ? 'TUI_Component' : 'Runtime_Node'
|
|
49
|
+
return `Pisper_${label}_${version}_${target}.tar.gz`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function releaseAsset(
|
|
53
|
+
version = tuiVersion,
|
|
54
|
+
platform = process.platform,
|
|
55
|
+
arch = process.arch,
|
|
56
|
+
) {
|
|
57
|
+
return componentAsset('tui', version, platform, arch)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compareVersions(left, right) {
|
|
61
|
+
const a = left.split('.').map(Number)
|
|
62
|
+
const b = right.split('.').map(Number)
|
|
63
|
+
for (let index = 0; index < 3; index += 1) {
|
|
64
|
+
if (a[index] !== b[index]) return a[index] - b[index]
|
|
65
|
+
}
|
|
66
|
+
return 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function isFile(path) {
|
|
70
|
+
try {
|
|
71
|
+
return (await stat(path)).isFile()
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error?.code === 'ENOENT') return false
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function commandName(component) {
|
|
79
|
+
if (component === 'tui') return executableName()
|
|
80
|
+
return 'sidecar-runtime/runtime/sidecar.mjs'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function validateComponent(component, version, destination) {
|
|
84
|
+
const executable = join(destination, commandName(component))
|
|
85
|
+
if (!(await isFile(executable))) return ''
|
|
86
|
+
if (
|
|
87
|
+
component === 'runtime' &&
|
|
88
|
+
!(await isFile(join(destination, 'sidecar-runtime', 'package.json')))
|
|
89
|
+
) {
|
|
90
|
+
return ''
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const manifest = JSON.parse(await readFile(join(destination, 'manifest.json'), 'utf8'))
|
|
94
|
+
if (
|
|
95
|
+
manifest.version !== version ||
|
|
96
|
+
manifest.platform !== releasePlatform() ||
|
|
97
|
+
manifest.arch !== releaseArchitecture() ||
|
|
98
|
+
manifest.command !== commandName(component)
|
|
99
|
+
) {
|
|
100
|
+
return ''
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return ''
|
|
104
|
+
}
|
|
105
|
+
return executable
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function installedComponent(component, version, destination) {
|
|
109
|
+
const executable = await validateComponent(component, version, destination)
|
|
110
|
+
return executable ? { version, destination, executable } : null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function resolveCurrentComponent(root, component, minimumVersion) {
|
|
114
|
+
try {
|
|
115
|
+
const pointer = JSON.parse(await readFile(join(root, component, 'current.json'), 'utf8'))
|
|
116
|
+
if (!/^\d+\.\d+\.\d+$/.test(pointer.version || '')) return null
|
|
117
|
+
if (compareVersions(pointer.version, minimumVersion) < 0) return null
|
|
118
|
+
const destination = join(root, component, 'versions', pointer.version)
|
|
119
|
+
return installedComponent(component, pointer.version, destination)
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error?.code === 'ENOENT' || error instanceof SyntaxError) return null
|
|
122
|
+
throw error
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function resolvePlatformBundle() {
|
|
127
|
+
const target = npmPlatformTarget()
|
|
128
|
+
const alias = npmPlatformAlias()
|
|
129
|
+
let manifestPath
|
|
130
|
+
try {
|
|
131
|
+
manifestPath = require.resolve(`${alias}/package.json`)
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`the npm platform package ${alias} is missing; reinstall pisper without --omit=optional`,
|
|
135
|
+
{ cause: error },
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'))
|
|
139
|
+
const metadata = manifest.pisperBundle
|
|
140
|
+
const expectedVersion = npmPlatformVersion(packageManifest.version)
|
|
141
|
+
if (
|
|
142
|
+
manifest.name !== 'pisper' ||
|
|
143
|
+
manifest.version !== expectedVersion ||
|
|
144
|
+
metadata?.target !== target.release ||
|
|
145
|
+
metadata?.tuiVersion !== tuiVersion ||
|
|
146
|
+
metadata?.runtimeVersion !== runtimeVersion
|
|
147
|
+
) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`npm platform package ${alias} does not match pisper@${packageManifest.version}`,
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
return dirname(manifestPath)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function readBundledAsset(bundleRoot, component, version) {
|
|
156
|
+
const asset = componentAsset(component, version)
|
|
157
|
+
const archivePath = join(bundleRoot, 'components', component, asset)
|
|
158
|
+
const signaturePath = `${archivePath}.sig`
|
|
159
|
+
let size
|
|
160
|
+
try {
|
|
161
|
+
size = (await stat(archivePath)).size
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw new Error(`npm platform package is missing ${component} archive ${asset}`, {
|
|
164
|
+
cause: error,
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
if (size <= 0 || size > MAX_ARCHIVE_BYTES) {
|
|
168
|
+
throw new Error(`npm platform package contains an invalid ${component} archive size: ${size}`)
|
|
169
|
+
}
|
|
170
|
+
const [archive, signature] = await Promise.all([readFile(archivePath), readFile(signaturePath)])
|
|
171
|
+
return { archive, signature }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function verifyArchive(component, archive, signatureBytes) {
|
|
175
|
+
const publicKeyText = await readFile(join(packageRoot, 'updater.pubkey'), 'utf8')
|
|
176
|
+
const publicKey = PublicKey.decode(decodeWrapped(publicKeyText))
|
|
177
|
+
const signature = Signature.decode(decodeWrapped(signatureBytes.toString('utf8')))
|
|
178
|
+
try {
|
|
179
|
+
if (!publicKey.verify(archive, signature)) {
|
|
180
|
+
throw new Error('signature verification returned false')
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw new Error(`${component} archive signature verification failed: ${error.message}`, {
|
|
184
|
+
cause: error,
|
|
185
|
+
})
|
|
186
|
+
} finally {
|
|
187
|
+
signature.free()
|
|
188
|
+
publicKey.free()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function nativeTarCommand() {
|
|
193
|
+
if (process.platform === 'win32') {
|
|
194
|
+
return join(
|
|
195
|
+
process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows',
|
|
196
|
+
'System32',
|
|
197
|
+
'tar.exe',
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
if (process.platform === 'darwin') return '/usr/bin/tar'
|
|
201
|
+
return 'tar'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function extractWithNodeTar(archivePath, extractionRoot) {
|
|
205
|
+
await extractTar({
|
|
206
|
+
cwd: extractionRoot,
|
|
207
|
+
file: archivePath,
|
|
208
|
+
preservePaths: false,
|
|
209
|
+
strict: true,
|
|
210
|
+
strip: 1,
|
|
211
|
+
filter: (_path, entry) => entry.type === 'File' || entry.type === 'Directory',
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function extractComponentArchive(
|
|
216
|
+
archivePath,
|
|
217
|
+
extractionRoot,
|
|
218
|
+
tarCommand = nativeTarCommand(),
|
|
219
|
+
) {
|
|
220
|
+
try {
|
|
221
|
+
await execFileAsync(
|
|
222
|
+
tarCommand,
|
|
223
|
+
['-xzf', archivePath, '-C', extractionRoot, '--strip-components=1'],
|
|
224
|
+
{ windowsHide: true },
|
|
225
|
+
)
|
|
226
|
+
} catch {
|
|
227
|
+
await rm(extractionRoot, { recursive: true, force: true })
|
|
228
|
+
await mkdir(extractionRoot, { recursive: true })
|
|
229
|
+
await extractWithNodeTar(archivePath, extractionRoot)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function wait(milliseconds) {
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function installerLockIsActive(lockPath) {
|
|
238
|
+
try {
|
|
239
|
+
const [raw, details] = await Promise.all([readFile(lockPath, 'utf8'), stat(lockPath)])
|
|
240
|
+
const age = Math.max(0, Date.now() - details.mtimeMs)
|
|
241
|
+
if (age >= INSTALL_WAIT_MS) return false
|
|
242
|
+
|
|
243
|
+
let metadata
|
|
244
|
+
try {
|
|
245
|
+
metadata = JSON.parse(raw)
|
|
246
|
+
} catch {
|
|
247
|
+
// Older installers created empty locks. Give a live legacy install time to finish.
|
|
248
|
+
return age < LEGACY_LOCK_GRACE_MS
|
|
249
|
+
}
|
|
250
|
+
if (!Number.isSafeInteger(metadata?.pid) || metadata.pid <= 0) {
|
|
251
|
+
return age < LEGACY_LOCK_GRACE_MS
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
process.kill(metadata.pid, 0)
|
|
255
|
+
return true
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (error?.code === 'ESRCH') return false
|
|
258
|
+
if (error?.code === 'EPERM') return true
|
|
259
|
+
throw error
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error?.code === 'ENOENT') return false
|
|
263
|
+
throw error
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function acquireInstallLock(lockPath, validate) {
|
|
268
|
+
const deadline = Date.now() + INSTALL_WAIT_MS
|
|
269
|
+
while (true) {
|
|
270
|
+
try {
|
|
271
|
+
const lock = await open(lockPath, 'wx')
|
|
272
|
+
try {
|
|
273
|
+
await lock.writeFile(`${JSON.stringify({ pid: process.pid, createdAt: Date.now() })}\n`)
|
|
274
|
+
return lock
|
|
275
|
+
} catch (error) {
|
|
276
|
+
await lock.close()
|
|
277
|
+
await rm(lockPath, { force: true })
|
|
278
|
+
throw error
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error?.code !== 'EEXIST') throw error
|
|
282
|
+
if (await validate()) return null
|
|
283
|
+
if (!(await installerLockIsActive(lockPath))) {
|
|
284
|
+
try {
|
|
285
|
+
await rm(lockPath, { force: true })
|
|
286
|
+
continue
|
|
287
|
+
} catch (removeError) {
|
|
288
|
+
if (!['EACCES', 'EBUSY', 'EPERM'].includes(removeError?.code)) throw removeError
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (Date.now() >= deadline) {
|
|
292
|
+
throw new Error('timed out waiting for another Pisper installer')
|
|
293
|
+
}
|
|
294
|
+
await wait(250)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function installComponent({ root, bundleRoot, component, version, destination }) {
|
|
300
|
+
const lockRoot = join(root, 'npm')
|
|
301
|
+
await mkdir(lockRoot, { recursive: true })
|
|
302
|
+
const lockPath = join(lockRoot, `${component}-install.lock`)
|
|
303
|
+
const validate = () => validateComponent(component, version, destination)
|
|
304
|
+
const lock = await acquireInstallLock(lockPath, validate)
|
|
305
|
+
if (!lock) return { version, destination, executable: await validate() }
|
|
306
|
+
|
|
307
|
+
const staging = `${destination}.tmp-${process.pid}-${Date.now()}`
|
|
308
|
+
// Renaming a tree with paths over MAX_PATH fails on Windows even though tar can extract it.
|
|
309
|
+
const extractionRoot = process.platform === 'win32' ? destination : staging
|
|
310
|
+
const archivePath = `${staging}.tar.gz`
|
|
311
|
+
try {
|
|
312
|
+
const existing = await validate()
|
|
313
|
+
if (existing) return { version, destination, executable: existing }
|
|
314
|
+
|
|
315
|
+
console.log(`pisper: installing ${component} ${version} from the npm platform package`)
|
|
316
|
+
await mkdir(dirname(destination), { recursive: true })
|
|
317
|
+
await rm(extractionRoot, { recursive: true, force: true })
|
|
318
|
+
await mkdir(extractionRoot, { recursive: true })
|
|
319
|
+
const { archive, signature } = await readBundledAsset(bundleRoot, component, version)
|
|
320
|
+
console.log(`pisper: verifying ${component} ${version} signature`)
|
|
321
|
+
await verifyArchive(component, archive, signature)
|
|
322
|
+
console.log(`pisper: extracting ${component} ${version}`)
|
|
323
|
+
await writeFile(archivePath, archive, { mode: 0o600 })
|
|
324
|
+
await extractComponentArchive(archivePath, extractionRoot)
|
|
325
|
+
if (process.platform !== 'win32') {
|
|
326
|
+
await chmod(join(extractionRoot, commandName(component)), 0o755)
|
|
327
|
+
}
|
|
328
|
+
const stagedExecutable = await validateComponent(component, version, extractionRoot)
|
|
329
|
+
if (!stagedExecutable) {
|
|
330
|
+
throw new Error(`bundled ${component} archive has an invalid package layout`)
|
|
331
|
+
}
|
|
332
|
+
if (extractionRoot !== destination) {
|
|
333
|
+
await rm(destination, { recursive: true, force: true })
|
|
334
|
+
await rename(staging, destination)
|
|
335
|
+
}
|
|
336
|
+
console.log(`pisper: ${component} ${version} is ready`)
|
|
337
|
+
return { version, destination, executable: join(destination, commandName(component)) }
|
|
338
|
+
} finally {
|
|
339
|
+
await lock.close()
|
|
340
|
+
await Promise.all([
|
|
341
|
+
rm(lockPath, { force: true }),
|
|
342
|
+
rm(staging, { recursive: true, force: true }),
|
|
343
|
+
rm(archivePath, { force: true }),
|
|
344
|
+
])
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function ensurePisperInstallation() {
|
|
349
|
+
const root = componentsRoot()
|
|
350
|
+
const npmTuiDestination = join(root, 'npm', 'versions', tuiVersion)
|
|
351
|
+
const runtimeDestination = join(root, 'npm-runtime', 'versions', runtimeVersion)
|
|
352
|
+
const [tui, runtime] = await Promise.all([
|
|
353
|
+
resolveCurrentComponent(root, 'tui', tuiVersion).then(
|
|
354
|
+
(current) => current || installedComponent('tui', tuiVersion, npmTuiDestination),
|
|
355
|
+
),
|
|
356
|
+
installedComponent('runtime', runtimeVersion, runtimeDestination),
|
|
357
|
+
])
|
|
358
|
+
if (tui && runtime) {
|
|
359
|
+
return {
|
|
360
|
+
executable: tui.executable,
|
|
361
|
+
appRoot: join(runtime.destination, 'sidecar-runtime'),
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const bundleRoot = await resolvePlatformBundle()
|
|
366
|
+
const [installedTui, installedRuntime] = await Promise.all([
|
|
367
|
+
tui ||
|
|
368
|
+
installComponent({
|
|
369
|
+
root,
|
|
370
|
+
bundleRoot,
|
|
371
|
+
component: 'tui',
|
|
372
|
+
version: tuiVersion,
|
|
373
|
+
destination: npmTuiDestination,
|
|
374
|
+
}),
|
|
375
|
+
runtime ||
|
|
376
|
+
installComponent({
|
|
377
|
+
root,
|
|
378
|
+
bundleRoot,
|
|
379
|
+
component: 'runtime',
|
|
380
|
+
version: runtimeVersion,
|
|
381
|
+
destination: runtimeDestination,
|
|
382
|
+
}),
|
|
383
|
+
])
|
|
384
|
+
return {
|
|
385
|
+
executable: installedTui.executable,
|
|
386
|
+
appRoot: join(installedRuntime.destination, 'sidecar-runtime'),
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export async function ensurePisperExecutable() {
|
|
391
|
+
return (await ensurePisperInstallation()).executable
|
|
392
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
export const NPM_PLATFORM_TARGETS = Object.freeze([
|
|
4
|
+
Object.freeze({ platform: 'win32', arch: 'x64', release: 'windows_x86_64', slug: 'win32-x64' }),
|
|
5
|
+
Object.freeze({ platform: 'darwin', arch: 'x64', release: 'darwin_x86_64', slug: 'darwin-x64' }),
|
|
6
|
+
Object.freeze({
|
|
7
|
+
platform: 'darwin',
|
|
8
|
+
arch: 'arm64',
|
|
9
|
+
release: 'darwin_aarch64',
|
|
10
|
+
slug: 'darwin-arm64',
|
|
11
|
+
}),
|
|
12
|
+
Object.freeze({ platform: 'linux', arch: 'x64', release: 'linux_x86_64', slug: 'linux-x64' }),
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export function npmPlatformTarget(platform = process.platform, arch = process.arch) {
|
|
16
|
+
const target = NPM_PLATFORM_TARGETS.find(
|
|
17
|
+
(candidate) => candidate.platform === platform && candidate.arch === arch,
|
|
18
|
+
)
|
|
19
|
+
if (!target) throw new Error(`Pisper does not publish an npm package for ${platform}-${arch}`)
|
|
20
|
+
return target
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function npmPlatformAlias(platform = process.platform, arch = process.arch) {
|
|
24
|
+
return `pisper-binary-${npmPlatformTarget(platform, arch).slug}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function npmPlatformVersion(version, platform = process.platform, arch = process.arch) {
|
|
28
|
+
if (!/^\d+\.\d+\.\d+$/.test(version || '')) {
|
|
29
|
+
throw new Error(`invalid Pisper npm version: ${version}`)
|
|
30
|
+
}
|
|
31
|
+
return `${version}-${npmPlatformTarget(platform, arch).slug}.0`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function npmPlatformOptionalDependencies(version) {
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
NPM_PLATFORM_TARGETS.map(({ platform, arch }) => [
|
|
37
|
+
npmPlatformAlias(platform, arch),
|
|
38
|
+
`npm:pisper@${npmPlatformVersion(version, platform, arch)}`,
|
|
39
|
+
]),
|
|
40
|
+
)
|
|
41
|
+
}
|