@opencue/cli 0.0.0-stage → 1.0.40
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/LICENSE.md +12 -0
- package/README.md +625 -2
- package/bin/dscode.exe +12 -0
- package/bin/stt/requirements.txt +32 -0
- package/bin/stt/stt_server.py +619 -0
- package/bin/tts/requirements.txt +21 -0
- package/bin/tts/tts_server.py +404 -0
- package/cli-wrapper.cjs +109 -0
- package/install.cjs +177 -0
- package/llms.txt +88 -0
- package/package.json +56 -4
- package/scripts/figure/compose.py +363 -0
- package/scripts/figure/replay-shots.mjs +205 -0
- package/web/dist/assets/AssistantsView-BiUiWjhF.js +1 -0
- package/web/dist/assets/AutomationView-Dcf_yNaI.js +1 -0
- package/web/dist/assets/ChannelConfigView-BLnxsMGm.js +8 -0
- package/web/dist/assets/ConnectorsView-CX3E6X6c.js +1 -0
- package/web/dist/assets/DesignSystemView--x-P-lpD.js +1 -0
- package/web/dist/assets/Drawer-CjAgqH3B.js +1 -0
- package/web/dist/assets/FileTreeView-Chb_fzTK.js +11 -0
- package/web/dist/assets/HookConfigView-EafYlWdN.js +1 -0
- package/web/dist/assets/InputArea-BbHslX7b.js +50 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/ModelsView-CGN5Nljr.js +1 -0
- package/web/dist/assets/PluginsView-CDiJgvI8.js +2 -0
- package/web/dist/assets/ProjectsView-C5Usmwqy.js +1 -0
- package/web/dist/assets/SessionGrid-BGpz0-op.css +1 -0
- package/web/dist/assets/SessionGrid-Drxg0r_u.js +4 -0
- package/web/dist/assets/SettingsModal-Cmts6aBG.js +10 -0
- package/web/dist/assets/Skeleton-Dsk6B0U3.js +1 -0
- package/web/dist/assets/SkillsView-TEDR_3XX.js +1 -0
- package/web/dist/assets/TemplatesTab-DtN5Kdgh.js +1 -0
- package/web/dist/assets/ToolsView-VukZnIt1.js +1 -0
- package/web/dist/assets/VizCardsSkeleton-C_GaEfXd.js +1 -0
- package/web/dist/assets/abap-BdImnpbu.js +1 -0
- package/web/dist/assets/abnfDiagram-YUKMZFIV-BIFw6B_N.js +1 -0
- package/web/dist/assets/actionscript-3-B3316cI-.js +1 -0
- package/web/dist/assets/ada-bCR0ucgS.js +1 -0
- package/web/dist/assets/ahk-CsyLZFj1.js +1 -0
- package/web/dist/assets/ahk2-8Zs4aa1G.js +1 -0
- package/web/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/web/dist/assets/angular-html-DA-rfuFy.js +1 -0
- package/web/dist/assets/angular-ts-BrjP3tb8.js +1 -0
- package/web/dist/assets/apache-Pmp26Uib.js +1 -0
- package/web/dist/assets/apex-DhZFqWV2.js +1 -0
- package/web/dist/assets/apl-CORt7UWP.js +1 -0
- package/web/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/web/dist/assets/ara-BRHolxvo.js +1 -0
- package/web/dist/assets/arc-BZQ951h9.js +1 -0
- package/web/dist/assets/architectureDiagram-47P4ROYG-BP5qiXzf.js +36 -0
- package/web/dist/assets/asciidoc-CSVQ5wI8.js +1 -0
- package/web/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/web/dist/assets/astro-HNnZUWAn.js +1 -0
- package/web/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/web/dist/assets/awk-DMzUqQB5.js +1 -0
- package/web/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/web/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/web/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/web/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/web/dist/assets/bat-CickPsom.js +1 -0
- package/web/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/web/dist/assets/berry-uYugtg8r.js +1 -0
- package/web/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/web/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/web/dist/assets/bird2-Bx8U0n9b.js +1 -0
- package/web/dist/assets/blade-2xfisSek.js +1 -0
- package/web/dist/assets/blockDiagram-E7TT5TSB-DtRpcE7F.js +140 -0
- package/web/dist/assets/bsl-DlhNcFeZ.js +1 -0
- package/web/dist/assets/c-BIGW1oBm.js +1 -0
- package/web/dist/assets/c3-Dp5svz6Z.js +1 -0
- package/web/dist/assets/c4Diagram-CRA5TL53-Gdzn8UBh.js +38 -0
- package/web/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/web/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/web/dist/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
- package/web/dist/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
- package/web/dist/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
- package/web/dist/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
- package/web/dist/assets/channel-BND6_KSH.js +1 -0
- package/web/dist/assets/chapel-DTp_pixX.js +1 -0
- package/web/dist/assets/chunk-2Q5K7J3B-3U6HMlsm.js +1 -0
- package/web/dist/assets/chunk-5VM5RSS4-CYl9mwa-.js +15 -0
- package/web/dist/assets/chunk-7TKQ45FW-Dn6ktcY5.js +1 -0
- package/web/dist/assets/chunk-AW2ZBBNX-De0QX6p1.js +88 -0
- package/web/dist/assets/chunk-FVRAUYC3-CpXDfvz4.js +2 -0
- package/web/dist/assets/chunk-HTAEGDNF-D1KDvcDd.js +1 -0
- package/web/dist/assets/chunk-JWPE2WC7-umm4A-fq.js +1 -0
- package/web/dist/assets/chunk-KQW6MTUR-JYH94hpE.js +1 -0
- package/web/dist/assets/chunk-XXDRQBXY-CSlCFByc.js +1 -0
- package/web/dist/assets/clarity-Dn5IMItf.js +1 -0
- package/web/dist/assets/classDiagram-v2-K4WV4PDN-DnFwS0qN.js +217 -0
- package/web/dist/assets/clojure-P80f7IUj.js +1 -0
- package/web/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/web/dist/assets/cobol-nBiQ_Alo.js +1 -0
- package/web/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/web/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/web/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/web/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/web/dist/assets/coq-C7JzOVbR.js +1 -0
- package/web/dist/assets/cose-bilkent-JH36ORCC-BxUTUyep.js +1 -0
- package/web/dist/assets/cpp-BMRokrvK.js +1 -0
- package/web/dist/assets/crystal-DGywbUpC.js +1 -0
- package/web/dist/assets/csharp-DSvCPggb.js +1 -0
- package/web/dist/assets/css-CLj8gQPS.js +1 -0
- package/web/dist/assets/csv-fuZLfV_i.js +1 -0
- package/web/dist/assets/cue-D82EKSYY.js +1 -0
- package/web/dist/assets/cynefin-EF2NZ3EQ-CiMvulJr.js +141 -0
- package/web/dist/assets/cynefinDiagram-3GCD6N5R-DuXFORdW.js +62 -0
- package/web/dist/assets/cypher-COkxafJQ.js +1 -0
- package/web/dist/assets/cytoscape.esm-Ix0LnXOy.js +331 -0
- package/web/dist/assets/d-85-TOEBH.js +1 -0
- package/web/dist/assets/dagre-W4DXFKR2-DzMZHXci.js +4 -0
- package/web/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/web/dist/assets/dart-bE4Kk8sk.js +1 -0
- package/web/dist/assets/dax-CEL-wOlO.js +1 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/web/dist/assets/diagram-2UJZ2QOL-B6tO5G1v.js +200 -0
- package/web/dist/assets/diagram-OVF4WLC6-DRZt8tGc.js +24 -0
- package/web/dist/assets/diagram-PFPMY2P6-DGh6IecR.js +30 -0
- package/web/dist/assets/diagram-UMYRVEAY-BLbnt0yT.js +24 -0
- package/web/dist/assets/diagram-YEKJPTXX-C-S5DVhj.js +41 -0
- package/web/dist/assets/diagram-ZIFT7M5P-JrGHTWbo.js +3 -0
- package/web/dist/assets/diff-D97Zzqfu.js +1 -0
- package/web/dist/assets/docker-BcOcwvcX.js +1 -0
- package/web/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/web/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/web/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/web/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/web/dist/assets/ebnfDiagram-VR2GEFS7-DPXA-YOi.js +1 -0
- package/web/dist/assets/edge-FbVlp4U3.js +1 -0
- package/web/dist/assets/elixir-CkH2-t6x.js +1 -0
- package/web/dist/assets/elk-IJKZMXRS-CINeWt2W.js +27 -0
- package/web/dist/assets/elm-DbKCFpqz.js +1 -0
- package/web/dist/assets/emacs-lisp-C_m_b--Z.js +1 -0
- package/web/dist/assets/erDiagram-O2IAPWRE-PPaHmqA1.js +99 -0
- package/web/dist/assets/erb-DXfck5VN.js +1 -0
- package/web/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/web/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/web/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/web/dist/assets/fennel-BYunw83y.js +1 -0
- package/web/dist/assets/fish-BvzEVeQv.js +1 -0
- package/web/dist/assets/flowDiagram-OXPTDLAJ-t-u0QXEA.js +213 -0
- package/web/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/web/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/web/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/web/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/web/dist/assets/ganttDiagram-R7TSEDQI-Bw49ZD_b.js +292 -0
- package/web/dist/assets/gdresource-TyuKm33G.js +1 -0
- package/web/dist/assets/gdscript-DqcFQ5yU.js +1 -0
- package/web/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/web/dist/assets/genie-D0YGMca9.js +1 -0
- package/web/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/web/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/web/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/web/dist/assets/gitGraphDiagram-XJZIOB7I-BAfOqeeg.js +106 -0
- package/web/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/web/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/web/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/web/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/web/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/web/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/web/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/web/dist/assets/gleam-BspZqrRM.js +1 -0
- package/web/dist/assets/glimmer-js-ByusRIyA.js +1 -0
- package/web/dist/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/web/dist/assets/glsl-DplSGwfg.js +1 -0
- package/web/dist/assets/gn-n2N0HUVH.js +1 -0
- package/web/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/web/dist/assets/go-C27-OAKa.js +1 -0
- package/web/dist/assets/graph-Bn3Yngep.js +1 -0
- package/web/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/web/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/web/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/web/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/web/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/web/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/web/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/web/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/web/dist/assets/hack-BWmVpMyf.js +1 -0
- package/web/dist/assets/haml-D5jkg6IW.js +1 -0
- package/web/dist/assets/handlebars-BpdQsYii.js +1 -0
- package/web/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/web/dist/assets/haxe-CfZj7gIn.js +1 -0
- package/web/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/web/dist/assets/hjson-D5-asLiD.js +1 -0
- package/web/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/web/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/web/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/web/dist/assets/houston-DnULxvSX.js +1 -0
- package/web/dist/assets/html-derivative-DlHx6ybY.js +1 -0
- package/web/dist/assets/html-pp8916En.js +1 -0
- package/web/dist/assets/http-jrhK8wxY.js +1 -0
- package/web/dist/assets/hurl-irOxFIW8.js +1 -0
- package/web/dist/assets/hxml-2-FPmUDs.js +1 -0
- package/web/dist/assets/hy-DFXneXwc.js +1 -0
- package/web/dist/assets/icons-Bm_ZzSAy.js +964 -0
- package/web/dist/assets/imba-DGztddWO.js +1 -0
- package/web/dist/assets/index-CeeLdENM.js +677 -0
- package/web/dist/assets/index-Ceez9lYk.css +1 -0
- package/web/dist/assets/infoDiagram-5W2HQ5XZ-Bv4RBxYW.js +2 -0
- package/web/dist/assets/ini-BEwlwnbL.js +1 -0
- package/web/dist/assets/init-Gi6I4Gst.js +1 -0
- package/web/dist/assets/ishikawaDiagram-K3B6WC7H-DEpj0bRp.js +70 -0
- package/web/dist/assets/java-CylS5w8V.js +1 -0
- package/web/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/web/dist/assets/jinja-f2NsQr07.js +1 -0
- package/web/dist/assets/jison-wvAkD_A8.js +1 -0
- package/web/dist/assets/journeyDiagram-COXZFDF6-MJu84Iiz.js +139 -0
- package/web/dist/assets/json-Cp-IABpG.js +1 -0
- package/web/dist/assets/json5-C9tS-k6U.js +1 -0
- package/web/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/web/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/web/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/web/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/web/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/web/dist/assets/julia-5Bft2YPA.js +1 -0
- package/web/dist/assets/just-Cwhn7H3k.js +1 -0
- package/web/dist/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
- package/web/dist/assets/kanagawa-lotus-C3DzagqV.js +1 -0
- package/web/dist/assets/kanagawa-wave-DFGoQZhC.js +1 -0
- package/web/dist/assets/kanban-definition-P3RFRI5V-mlYSRALK.js +89 -0
- package/web/dist/assets/kdl-DV7GczEv.js +1 -0
- package/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/web/dist/assets/kusto-wEQ09or8.js +1 -0
- package/web/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/web/dist/assets/latex-D5pSuvFb.js +1 -0
- package/web/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/web/dist/assets/less-B1dDrJ26.js +1 -0
- package/web/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/web/dist/assets/linear-5j9gt8MV.js +1 -0
- package/web/dist/assets/liquid-C0sCDyMI.js +1 -0
- package/web/dist/assets/llvm-BZoOZj88.js +1 -0
- package/web/dist/assets/log-2UxHyX5q.js +1 -0
- package/web/dist/assets/logo-BtOb2qkB.js +1 -0
- package/web/dist/assets/lua-BaeVxFsk.js +1 -0
- package/web/dist/assets/luau-BnpPk5vE.js +1 -0
- package/web/dist/assets/make-CHLpvVh8.js +1 -0
- package/web/dist/assets/markdown-BWXjRrSw.js +30 -0
- package/web/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/web/dist/assets/marko-DjSrsDqO.js +1 -0
- package/web/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/web/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/web/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/web/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/web/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/web/dist/assets/matlab-D7o27uSR.js +1 -0
- package/web/dist/assets/mdc-D1_yUvq7.js +1 -0
- package/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/web/dist/assets/mermaid-CQcHuHx7.js +1 -0
- package/web/dist/assets/mermaid.core-DLb6eAON.js +314 -0
- package/web/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/web/dist/assets/min-light-CTRr51gU.js +1 -0
- package/web/dist/assets/mindmap-definition-LZFPQGKD-C9v_57Ex.js +96 -0
- package/web/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/web/dist/assets/mojo-DJz3ZmWd.js +1 -0
- package/web/dist/assets/monokai-BRVnQi9A.js +1 -0
- package/web/dist/assets/moonbit-CHtswR0a.js +1 -0
- package/web/dist/assets/move-el3G9tDJ.js +1 -0
- package/web/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/web/dist/assets/nextflow-C-mBbutL.js +1 -0
- package/web/dist/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/web/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/web/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/web/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/web/dist/assets/nim-BIad80T-.js +1 -0
- package/web/dist/assets/nix-CwoSXNpI.js +1 -0
- package/web/dist/assets/nord-Ddv68eIx.js +1 -0
- package/web/dist/assets/nsis-BlV79W_Q.js +1 -0
- package/web/dist/assets/nushell-D3jzshHO.js +1 -0
- package/web/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/web/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/web/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/web/dist/assets/odin-BBf5iR-q.js +1 -0
- package/web/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/web/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/web/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/web/dist/assets/org-DM6o9KBp.js +1 -0
- package/web/dist/assets/panels-BQOvnbWH.js +1 -0
- package/web/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/web/dist/assets/pegDiagram-BGZESJAR-KQP26s3-.js +1 -0
- package/web/dist/assets/perl-B9cMNwum.js +1 -0
- package/web/dist/assets/php-Csjmro_R.js +1 -0
- package/web/dist/assets/pieDiagram-CAPJLFHJ-DN3RQ_ih.js +39 -0
- package/web/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/web/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/web/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/web/dist/assets/po-BTJTHyun.js +1 -0
- package/web/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/web/dist/assets/polar-C0HS_06l.js +1 -0
- package/web/dist/assets/postcss-CXtECtnM.js +1 -0
- package/web/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/web/dist/assets/powershell-BmBUJMz7.js +1 -0
- package/web/dist/assets/prisma-Vru482bI.js +1 -0
- package/web/dist/assets/profileChange-B7TaQ18u.js +1 -0
- package/web/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/web/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/web/dist/assets/pug-DKIMFp6K.js +1 -0
- package/web/dist/assets/puppet-BMWR74SV.js +1 -0
- package/web/dist/assets/purescript-CklMAg4u.js +1 -0
- package/web/dist/assets/python-B6aJPvgy.js +1 -0
- package/web/dist/assets/qml-3beO22l8.js +1 -0
- package/web/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/web/dist/assets/qss-IeuSbFQv.js +1 -0
- package/web/dist/assets/quadrantDiagram-GDMTTRAM-BpL_ZL76.js +7 -0
- package/web/dist/assets/r-Cf5RLm7j.js +1 -0
- package/web/dist/assets/racket-BqYA7rlc.js +1 -0
- package/web/dist/assets/railroadDiagram-SM67HX2B-DZ-fPU85.js +1 -0
- package/web/dist/assets/raku-DXvB9xmW.js +1 -0
- package/web/dist/assets/razor-BjBPvh-w.js +1 -0
- package/web/dist/assets/rbs-CpoqiR4B.js +1 -0
- package/web/dist/assets/react-vendor-C0HIWSpf.js +9 -0
- package/web/dist/assets/red-hvxz__6c.js +1 -0
- package/web/dist/assets/reg-C-SQnVFl.js +1 -0
- package/web/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/web/dist/assets/rel-C3B-1QV4.js +1 -0
- package/web/dist/assets/requirementDiagram-X7JNWC4A-mKPGj64L.js +84 -0
- package/web/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/web/dist/assets/ron-D8l8udqQ.js +1 -0
- package/web/dist/assets/rose-pine-5qJOZa0Y.js +1 -0
- package/web/dist/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
- package/web/dist/assets/rose-pine-moon-C9pEdX9L.js +1 -0
- package/web/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/web/dist/assets/rst-bs7f0vWN.js +1 -0
- package/web/dist/assets/ruby-C0TQ7zu5.js +1 -0
- package/web/dist/assets/rust-B1yitclQ.js +1 -0
- package/web/dist/assets/sankeyDiagram-UM26HJRW-Bs4xfjQP.js +40 -0
- package/web/dist/assets/sas-DEy46yEz.js +1 -0
- package/web/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/web/dist/assets/scala-CqE71os6.js +1 -0
- package/web/dist/assets/scheme-C98Dy4si.js +1 -0
- package/web/dist/assets/scss-D5BDwBP9.js +1 -0
- package/web/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/web/dist/assets/sequenceDiagram-ZO4K6R2Y-BjUmzZaO.js +169 -0
- package/web/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/web/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/web/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/web/dist/assets/sizeCapture-INFHLROL-BXBM3j_Q.js +1 -0
- package/web/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/web/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/web/dist/assets/smalltalk-BOQMe2GC.js +1 -0
- package/web/dist/assets/smithy-cds9vsN8.js +1 -0
- package/web/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/web/dist/assets/solarized-dark-CpvCGNkr.js +1 -0
- package/web/dist/assets/solarized-light-Dlz6yCKv.js +1 -0
- package/web/dist/assets/solidity-DijEV5ha.js +1 -0
- package/web/dist/assets/soy-8wufbnw4.js +1 -0
- package/web/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/web/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/web/dist/assets/sql-CRqJ_cUM.js +1 -0
- package/web/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/web/dist/assets/stata-DI20mbqo.js +1 -0
- package/web/dist/assets/state-Kfon1nTr.js +17 -0
- package/web/dist/assets/stateDiagram-v2-TBUQTH76-FGgFDGUU.js +291 -0
- package/web/dist/assets/sttLanguages-C7kVulzY.js +1 -0
- package/web/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/web/dist/assets/surrealql-Cjom0U5J.js +1 -0
- package/web/dist/assets/svelte-Cy7k_4gC.js +1 -0
- package/web/dist/assets/swift-C2oV4EkX.js +1 -0
- package/web/dist/assets/swimlanes-N4OXWK64-wkd_n4HU.js +1 -0
- package/web/dist/assets/swimlanesDiagram-K3J5GTZL-CKnwEuq_.js +8 -0
- package/web/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/web/dist/assets/system-verilog-0hqHdDBg.js +1 -0
- package/web/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/web/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/web/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/web/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/web/dist/assets/templ-DhtptRzy.js +1 -0
- package/web/dist/assets/terraform-BETggiCN.js +1 -0
- package/web/dist/assets/tex-D96PA37w.js +1 -0
- package/web/dist/assets/timeline-definition-YOQAKGHF-DyBln1jw.js +120 -0
- package/web/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/web/dist/assets/toml-vGWfd6FD.js +1 -0
- package/web/dist/assets/ts-tags-D351s5mN.js +1 -0
- package/web/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/web/dist/assets/tsx-COt5Ahok.js +1 -0
- package/web/dist/assets/turtle-BsS91CYL.js +1 -0
- package/web/dist/assets/twig-27uCiNez.js +1 -0
- package/web/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/web/dist/assets/typespec-CAFt9gP4.js +1 -0
- package/web/dist/assets/typst-BUadGCkm.js +1 -0
- package/web/dist/assets/usecaseDiagram-VIAY4XPW-Cqis3XHX.js +370 -0
- package/web/dist/assets/v-BGw2Nkan.js +1 -0
- package/web/dist/assets/vala-CsfeWuGM.js +1 -0
- package/web/dist/assets/vb-Cu-pLBUe.js +1 -0
- package/web/dist/assets/vennDiagram-BLWOH2XV-BaiFLOVS.js +34 -0
- package/web/dist/assets/verilog-nZwndyjY.js +1 -0
- package/web/dist/assets/vesper-DRje8inN.js +1 -0
- package/web/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/web/dist/assets/viml-CJc9bBzg.js +1 -0
- package/web/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/web/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/web/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/web/dist/assets/vue-BqiEGhQt.js +1 -0
- package/web/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/web/dist/assets/vue-vine-BoDAl6tE.js +1 -0
- package/web/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/web/dist/assets/wardleyDiagram-YMQ3BMBF-C4CzpJto.js +78 -0
- package/web/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/web/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/web/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/web/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/web/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/web/dist/assets/wit-5i3qLPDT.js +1 -0
- package/web/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/web/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/web/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/web/dist/assets/xychartDiagram-TAQBALBS-Dp1hfe3-.js +7 -0
- package/web/dist/assets/yaml-Buea-lGh.js +1 -0
- package/web/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/web/dist/assets/zig-VOosw3JB.js +1 -0
- package/web/dist/favicon.svg +40 -0
- package/web/dist/figma-demo/product-1.jpg +0 -0
- package/web/dist/figma-demo/product-2.jpg +0 -0
- package/web/dist/figma-demo/trail-1.jpg +0 -0
- package/web/dist/figma-demo/trail-2.jpg +0 -0
- package/web/dist/figma-demo/trail-3.jpg +0 -0
- package/web/dist/fonts/OFL-Inter.txt +93 -0
- package/web/dist/fonts/inter-latin-400-normal.woff2 +0 -0
- package/web/dist/fonts/inter-latin-600-normal.woff2 +0 -0
- package/web/dist/icons/icon-180.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-maskable-512.png +0 -0
- package/web/dist/index.html +56 -0
- package/web/dist/logo.svg +40 -0
- package/web/dist/manifest.webmanifest +16 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""TTS(语音合成)sidecar — Kokoro-82M(ONNX),零 torch。
|
|
2
|
+
|
|
3
|
+
与 `packages/stt/stt_server.py` 同构:Node 侧分配端口后用 `--port` 传入、靠 `/health`
|
|
4
|
+
判断就绪、`--parent-pid` 看门狗防孤儿进程;父进程退出后本进程自杀。
|
|
5
|
+
权重**不由本进程下载**(Node 侧 `ttsDownload.ts` 负责,含国内镜像选择),本进程只从
|
|
6
|
+
models_dir 加载。
|
|
7
|
+
|
|
8
|
+
环境变量:
|
|
9
|
+
DSC_TTS_PORT 监听端口(默认 0 = 系统分配)
|
|
10
|
+
DSC_TTS_MODELS_DIR 权重目录(默认 ~/.agent/tts-models)
|
|
11
|
+
DSC_TTS_MODEL 模型文件名(默认 model_uint8f16.onnx)
|
|
12
|
+
DSC_TTS_VOICES 音色文件名(默认 voices-v1.0.bin)
|
|
13
|
+
|
|
14
|
+
端点:
|
|
15
|
+
GET /health 进程就绪(不依赖模型加载完成)
|
|
16
|
+
GET /status 依赖与权重就绪情况(供「缺什么 + 一键装」文案使用)
|
|
17
|
+
POST /synthesize {"text","voice","speed","lang","stream"}
|
|
18
|
+
非流式 -> application/wav 整段返回
|
|
19
|
+
流式 -> audio/L16 裸 PCM(s16le 24kHz 单声道),HTTP/1.0 连接关闭定界
|
|
20
|
+
|
|
21
|
+
中文音素**必须**走 misaki(见 docs/architecture-design/PERSONAL_REMOTE_AGENT_PLAN.md
|
|
22
|
+
gotcha 11):kokoro-onnx 自带的 `phonemize(text, "cmn")` 走 espeak,产出丢声母且**完全无音调**,
|
|
23
|
+
中文因此听不懂——而且不报错、不掉字,只有人耳能发现。Kokoro 的中文音色是用 misaki 的
|
|
24
|
+
拼音 IPA(带 `↓→↗↘` 调号)训练的,所以这里自己产出音素,再以 `is_phonemes=True` 送入。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import io
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
import threading
|
|
36
|
+
import time
|
|
37
|
+
import wave
|
|
38
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
from typing import Any, Callable
|
|
41
|
+
|
|
42
|
+
import numpy as np
|
|
43
|
+
|
|
44
|
+
DEFAULT_MODELS_DIR = str(Path.home() / ".agent" / "tts-models")
|
|
45
|
+
SAMPLE_RATE = 24_000
|
|
46
|
+
DEFAULT_MODEL = "model_uint8f16.onnx"
|
|
47
|
+
DEFAULT_VOICES = "voices-v1.0.bin"
|
|
48
|
+
# 每档语言的默认音色(Kokoro v1.0 音色名;未知音色会被 resolve_voice 挡下并列出可选值)
|
|
49
|
+
DEFAULT_VOICE = {"zh": "zf_xiaoxiao", "en": "af_heart"}
|
|
50
|
+
FALLBACK_VOICE = "af_heart"
|
|
51
|
+
MAX_TEXT_CHARS = 5_000
|
|
52
|
+
# 中英混排切分:英文段(含文件名/路径里的 . _ - /)与其余段。见 gotcha 11 的切分理由。
|
|
53
|
+
_MIXED_RE = re.compile(r"[A-Za-z][A-Za-z0-9_.\-/]*|[^A-Za-z]+")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def log(message: str) -> None:
|
|
57
|
+
print(f"[tts] {message}", flush=True)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _arg_value(name: str) -> str | None:
|
|
61
|
+
"""从 sys.argv 取 `--x value` 或 `--x=value`(与 stt_server.py 同一约定)。"""
|
|
62
|
+
argv = sys.argv
|
|
63
|
+
for index, item in enumerate(argv):
|
|
64
|
+
if item == name and index + 1 < len(argv):
|
|
65
|
+
return argv[index + 1]
|
|
66
|
+
if item.startswith(f"{name}="):
|
|
67
|
+
return item.split("=", 1)[1]
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_chinese(lang: str) -> bool:
|
|
72
|
+
normalized = (lang or "").lower().replace("_", "-")
|
|
73
|
+
return normalized.startswith("zh") or normalized.startswith("cmn") or normalized.startswith("yue")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def lang_key(lang: str) -> str:
|
|
77
|
+
return "zh" if is_chinese(lang) else "en"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parent_alive(pid: int) -> bool:
|
|
81
|
+
"""父进程是否还在。
|
|
82
|
+
|
|
83
|
+
**不要**用 `os.kill(pid, 0)` 探活:Windows 上它并非「发 0 号信号」,CPython 会走
|
|
84
|
+
`OpenProcess(PROCESS_ALL_ACCESS)` + `TerminateProcess`(文档:除 CTRL_C_EVENT /
|
|
85
|
+
CTRL_BREAK_EVENT 外的 sig 会「unconditionally killed by the TerminateProcess API」),
|
|
86
|
+
而且 `PROCESS_ALL_ACCESS` 在子进程带 `CREATE_NO_WINDOW`(Node 的 `windowsHide: true`)
|
|
87
|
+
时**会失败**并抛 OSError → 看门狗误判「父进程已死」→ 服务约 5 秒后自杀,表现为
|
|
88
|
+
第一个请求连接被重置、之后一律 ConnectionRefused。实测见 .agent/tts-probe/shape_probe.py。
|
|
89
|
+
"""
|
|
90
|
+
if os.name == "nt":
|
|
91
|
+
import ctypes
|
|
92
|
+
|
|
93
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
94
|
+
# 只申请「查询」权限,不做任何写/终止操作
|
|
95
|
+
handle = kernel32.OpenProcess(0x1000, False, pid)
|
|
96
|
+
if not handle:
|
|
97
|
+
return False
|
|
98
|
+
try:
|
|
99
|
+
code = ctypes.c_ulong()
|
|
100
|
+
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(code)):
|
|
101
|
+
return False
|
|
102
|
+
return code.value == 259 # STILL_ACTIVE
|
|
103
|
+
finally:
|
|
104
|
+
kernel32.CloseHandle(handle)
|
|
105
|
+
try:
|
|
106
|
+
os.kill(pid, 0)
|
|
107
|
+
return True
|
|
108
|
+
except (OSError, ValueError):
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_phonemes(text: str, lang: str, tokenizer: Any, zh_g2p: Any) -> str:
|
|
113
|
+
"""text -> 音素串。非中文交给 kokoro-onnx 原生 espeak 路径;中文走 misaki,
|
|
114
|
+
并把中英混排自己切开——否则英文段会被原样保留、随后被 tokenizer 静默丢弃。"""
|
|
115
|
+
if not is_chinese(lang):
|
|
116
|
+
return tokenizer.phonemize(text, lang)
|
|
117
|
+
parts: list[str] = []
|
|
118
|
+
for match in _MIXED_RE.finditer(text):
|
|
119
|
+
chunk = match.group(0)
|
|
120
|
+
if not chunk.strip():
|
|
121
|
+
continue
|
|
122
|
+
if chunk[0].isascii() and chunk[0].isalpha():
|
|
123
|
+
parts.append(tokenizer.phonemize(chunk, "en-us"))
|
|
124
|
+
else:
|
|
125
|
+
phonemes, _ = zh_g2p(chunk)
|
|
126
|
+
parts.append(phonemes)
|
|
127
|
+
return " ".join(part for part in parts if part)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def to_pcm16(samples: Any) -> bytes:
|
|
131
|
+
clipped = np.clip(np.asarray(samples, dtype=np.float32), -1.0, 1.0)
|
|
132
|
+
return (clipped * 32767.0).astype("<i2").tobytes()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def wav_bytes(samples: Any, sample_rate: int) -> bytes:
|
|
136
|
+
buffer = io.BytesIO()
|
|
137
|
+
with wave.open(buffer, "wb") as handle:
|
|
138
|
+
handle.setnchannels(1)
|
|
139
|
+
handle.setsampwidth(2)
|
|
140
|
+
handle.setframerate(sample_rate)
|
|
141
|
+
handle.writeframes(to_pcm16(samples))
|
|
142
|
+
return buffer.getvalue()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TtsEngine:
|
|
146
|
+
"""懒加载 Kokoro。加载失败时把原因留在 probe() 里,供前端「缺什么」文案使用。"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, models_dir: str, model_file: str, voices_file: str) -> None:
|
|
149
|
+
self.models_dir = models_dir
|
|
150
|
+
self.model_file = model_file
|
|
151
|
+
self.voices_file = voices_file
|
|
152
|
+
self.model_path = str(Path(models_dir) / model_file)
|
|
153
|
+
self.voices_path = str(Path(models_dir) / voices_file)
|
|
154
|
+
self._kokoro: Any = None
|
|
155
|
+
self._zh_g2p: Any = None
|
|
156
|
+
self._load_error: str | None = None
|
|
157
|
+
self._lock = threading.Lock()
|
|
158
|
+
|
|
159
|
+
def missing_weights(self) -> list[str]:
|
|
160
|
+
pairs = ((self.model_file, self.model_path), (self.voices_file, self.voices_path))
|
|
161
|
+
return [name for name, path in pairs if not Path(path).is_file()]
|
|
162
|
+
|
|
163
|
+
def probe(self) -> dict:
|
|
164
|
+
"""不加载模型,只探测依赖可导入性与权重是否就位。"""
|
|
165
|
+
try:
|
|
166
|
+
import kokoro_onnx # noqa: F401
|
|
167
|
+
import misaki # noqa: F401
|
|
168
|
+
|
|
169
|
+
import_error: str | None = None
|
|
170
|
+
except Exception as exc: # noqa: BLE001
|
|
171
|
+
import_error = f"{type(exc).__name__}: {exc}"
|
|
172
|
+
missing = self.missing_weights()
|
|
173
|
+
status: dict = {
|
|
174
|
+
"installed": import_error is None and not missing,
|
|
175
|
+
"loaded": self._kokoro is not None,
|
|
176
|
+
"model": self.model_file,
|
|
177
|
+
"voicesFile": self.voices_file,
|
|
178
|
+
"voiceNames": self.voice_names(),
|
|
179
|
+
"device": "cpu",
|
|
180
|
+
"missingWeights": missing,
|
|
181
|
+
}
|
|
182
|
+
if import_error:
|
|
183
|
+
status["error"] = import_error
|
|
184
|
+
elif self._load_error:
|
|
185
|
+
status["error"] = self._load_error
|
|
186
|
+
return status
|
|
187
|
+
|
|
188
|
+
def load(self) -> Any:
|
|
189
|
+
with self._lock:
|
|
190
|
+
if self._kokoro is not None:
|
|
191
|
+
return self._kokoro
|
|
192
|
+
if self._load_error:
|
|
193
|
+
raise RuntimeError(self._load_error)
|
|
194
|
+
missing = self.missing_weights()
|
|
195
|
+
if missing:
|
|
196
|
+
raise RuntimeError(f"missing weights: {', '.join(missing)}")
|
|
197
|
+
try:
|
|
198
|
+
from kokoro_onnx import Kokoro
|
|
199
|
+
from misaki import zh
|
|
200
|
+
|
|
201
|
+
self._zh_g2p = zh.ZHG2P()
|
|
202
|
+
self._kokoro = Kokoro(self.model_path, self.voices_path)
|
|
203
|
+
except Exception as exc: # noqa: BLE001
|
|
204
|
+
self._load_error = f"{type(exc).__name__}: {exc}"
|
|
205
|
+
raise RuntimeError(self._load_error) from exc
|
|
206
|
+
log(f"model loaded ({self.model_file}, {len(self._kokoro.voices)} voices)")
|
|
207
|
+
return self._kokoro
|
|
208
|
+
|
|
209
|
+
def voice_names(self) -> list[str]:
|
|
210
|
+
"""音色名清单(voices 文件的 key),**不加载 ONNX 模型**。
|
|
211
|
+
|
|
212
|
+
为什么不用 available_voices():那条路径要过 load(),会把 114 MiB 模型读进内存,
|
|
213
|
+
而 probe() 是被设置页高频轮询的——这个代价付不起。模型已加载时直接取它的键,
|
|
214
|
+
与读文件是同一份数据,只是省一次解析。
|
|
215
|
+
"""
|
|
216
|
+
if self._kokoro is not None:
|
|
217
|
+
return sorted(self._kokoro.voices)
|
|
218
|
+
if not Path(self.voices_path).is_file():
|
|
219
|
+
return []
|
|
220
|
+
try:
|
|
221
|
+
import numpy
|
|
222
|
+
|
|
223
|
+
return sorted(numpy.load(self.voices_path, allow_pickle=False).files)
|
|
224
|
+
except Exception as exc: # noqa: BLE001
|
|
225
|
+
# 拿不到清单不该让整个状态接口失败:合成侧仍会按 lang 选默认音色
|
|
226
|
+
log(f"voice list unavailable: {exc}")
|
|
227
|
+
return []
|
|
228
|
+
|
|
229
|
+
def available_voices(self) -> list[str]:
|
|
230
|
+
return sorted(self.load().voices)
|
|
231
|
+
|
|
232
|
+
def resolve_voice(self, lang: str, voice: str | None) -> str:
|
|
233
|
+
available = self.available_voices()
|
|
234
|
+
wanted = voice or DEFAULT_VOICE.get(lang_key(lang), FALLBACK_VOICE)
|
|
235
|
+
if wanted in available:
|
|
236
|
+
return wanted
|
|
237
|
+
raise ValueError(f"unknown voice {wanted!r}; available: {', '.join(available)}")
|
|
238
|
+
|
|
239
|
+
def _phonemes(self, kokoro: Any, text: str, lang: str) -> str:
|
|
240
|
+
return build_phonemes(text, lang, kokoro.tokenizer, self._zh_g2p)
|
|
241
|
+
|
|
242
|
+
def synthesize(self, text: str, lang: str, voice: str | None, speed: float) -> tuple[bytes, int]:
|
|
243
|
+
kokoro = self.load()
|
|
244
|
+
phonemes = self._phonemes(kokoro, text, lang)
|
|
245
|
+
if not phonemes.strip():
|
|
246
|
+
raise ValueError("text produced no phonemes")
|
|
247
|
+
audio, rate = kokoro.create(
|
|
248
|
+
phonemes,
|
|
249
|
+
voice=self.resolve_voice(lang, voice),
|
|
250
|
+
speed=speed,
|
|
251
|
+
lang=lang,
|
|
252
|
+
is_phonemes=True,
|
|
253
|
+
)
|
|
254
|
+
return wav_bytes(audio, rate), rate
|
|
255
|
+
|
|
256
|
+
def synthesize_stream(
|
|
257
|
+
self, text: str, lang: str, voice: str | None, speed: float, write: Callable[[bytes], None]
|
|
258
|
+
) -> None:
|
|
259
|
+
"""边合成边写裸 PCM。放弃生成器即停止后续批次,客户端断连自然收敛。"""
|
|
260
|
+
kokoro = self.load()
|
|
261
|
+
phonemes = self._phonemes(kokoro, text, lang)
|
|
262
|
+
if not phonemes.strip():
|
|
263
|
+
raise ValueError("text produced no phonemes")
|
|
264
|
+
style = self.resolve_voice(lang, voice)
|
|
265
|
+
|
|
266
|
+
async def run() -> None:
|
|
267
|
+
async for audio, _rate in kokoro.create_stream(
|
|
268
|
+
phonemes, voice=style, speed=speed, lang=lang, is_phonemes=True
|
|
269
|
+
):
|
|
270
|
+
write(to_pcm16(audio))
|
|
271
|
+
|
|
272
|
+
asyncio.run(run())
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class TtsHttpServer(ThreadingHTTPServer):
|
|
276
|
+
daemon_threads = True
|
|
277
|
+
|
|
278
|
+
def __init__(self, address: tuple[str, int], engine: TtsEngine) -> None:
|
|
279
|
+
super().__init__(address, TtsHandler)
|
|
280
|
+
self.engine = engine
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class TtsHandler(BaseHTTPRequestHandler):
|
|
284
|
+
server_version = "DeepSeekCodeTts/1.0"
|
|
285
|
+
# 流式响应无 Content-Length,靠连接关闭定界;HTTP/1.0 正好是这个语义
|
|
286
|
+
protocol_version = "HTTP/1.0"
|
|
287
|
+
|
|
288
|
+
def log_message(self, fmt: str, *args: Any) -> None:
|
|
289
|
+
"""静音 http.server 默认的逐请求 stderr 噪音。"""
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def engine(self) -> TtsEngine:
|
|
293
|
+
return self.server.engine # type: ignore[attr-defined]
|
|
294
|
+
|
|
295
|
+
def _send_json(self, status: int, payload: dict) -> None:
|
|
296
|
+
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
297
|
+
self.send_response(status)
|
|
298
|
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
299
|
+
self.send_header("Content-Length", str(len(body)))
|
|
300
|
+
self.end_headers()
|
|
301
|
+
self.wfile.write(body)
|
|
302
|
+
|
|
303
|
+
def do_GET(self) -> None: # noqa: N802 (http.server 的约定命名)
|
|
304
|
+
if self.path == "/health":
|
|
305
|
+
self._send_json(200, {"ok": True})
|
|
306
|
+
elif self.path == "/status":
|
|
307
|
+
self._send_json(200, self.engine.probe())
|
|
308
|
+
else:
|
|
309
|
+
self._send_json(404, {"error": "not found"})
|
|
310
|
+
|
|
311
|
+
def do_POST(self) -> None: # noqa: N802
|
|
312
|
+
if self.path != "/synthesize":
|
|
313
|
+
self._send_json(404, {"error": "not found"})
|
|
314
|
+
return
|
|
315
|
+
try:
|
|
316
|
+
length = int(self.headers.get("Content-Length") or 0)
|
|
317
|
+
payload = json.loads(self.rfile.read(length) or b"{}")
|
|
318
|
+
except Exception as exc: # noqa: BLE001
|
|
319
|
+
self._send_json(400, {"error": f"bad request body: {exc}"})
|
|
320
|
+
return
|
|
321
|
+
log(f"-> synthesize {len(payload.get('text') or '')} chars lang={payload.get('lang')}")
|
|
322
|
+
|
|
323
|
+
text = str(payload.get("text") or "").strip()
|
|
324
|
+
if not text:
|
|
325
|
+
self._send_json(400, {"error": "text is required"})
|
|
326
|
+
return
|
|
327
|
+
if len(text) > MAX_TEXT_CHARS:
|
|
328
|
+
self._send_json(400, {"error": f"text too long (> {MAX_TEXT_CHARS} chars)"})
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
lang = str(payload.get("lang") or "en-us")
|
|
332
|
+
voice = payload.get("voice") or None
|
|
333
|
+
speed = float(payload.get("speed") or 1.0)
|
|
334
|
+
if not 0.5 <= speed <= 2.0:
|
|
335
|
+
self._send_json(400, {"error": f"speed must be within 0.5~2.0, got {speed}"})
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
if payload.get("stream"):
|
|
339
|
+
self._stream(text, lang, voice, speed)
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
audio, rate = self.engine.synthesize(text, lang, voice, speed)
|
|
344
|
+
except ValueError as exc:
|
|
345
|
+
self._send_json(400, {"error": str(exc)})
|
|
346
|
+
except Exception as exc: # noqa: BLE001
|
|
347
|
+
self._send_json(500, {"error": f"{type(exc).__name__}: {exc}"})
|
|
348
|
+
else:
|
|
349
|
+
log(f"<- synthesized {len(audio)} bytes, responding")
|
|
350
|
+
self.send_response(200)
|
|
351
|
+
self.send_header("Content-Type", "audio/wav")
|
|
352
|
+
self.send_header("X-Sample-Rate", str(rate))
|
|
353
|
+
self.send_header("Content-Length", str(len(audio)))
|
|
354
|
+
self.end_headers()
|
|
355
|
+
self.wfile.write(audio)
|
|
356
|
+
|
|
357
|
+
def _stream(self, text: str, lang: str, voice: str | None, speed: float) -> None:
|
|
358
|
+
"""头一旦发出就无法再改状态码,所以失败只能记日志 + 断流(客户端按截断处理)。"""
|
|
359
|
+
self.send_response(200)
|
|
360
|
+
self.send_header("Content-Type", "audio/L16")
|
|
361
|
+
self.send_header("X-Sample-Rate", str(SAMPLE_RATE))
|
|
362
|
+
self.send_header("Connection", "close")
|
|
363
|
+
self.end_headers()
|
|
364
|
+
try:
|
|
365
|
+
self.engine.synthesize_stream(text, lang, voice, speed, self.wfile.write)
|
|
366
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
367
|
+
log("client closed the stream")
|
|
368
|
+
except Exception as exc: # noqa: BLE001
|
|
369
|
+
log(f"stream failed: {type(exc).__name__}: {exc}")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def main() -> None:
|
|
373
|
+
models_dir = os.environ.get("DSC_TTS_MODELS_DIR") or DEFAULT_MODELS_DIR
|
|
374
|
+
Path(models_dir).mkdir(parents=True, exist_ok=True)
|
|
375
|
+
model_file = os.environ.get("DSC_TTS_MODEL") or DEFAULT_MODEL
|
|
376
|
+
voices_file = os.environ.get("DSC_TTS_VOICES") or DEFAULT_VOICES
|
|
377
|
+
port = int(_arg_value("--port") or os.environ.get("DSC_TTS_PORT") or "0")
|
|
378
|
+
raw_parent = _arg_value("--parent-pid")
|
|
379
|
+
parent_pid = int(raw_parent) if raw_parent and raw_parent.isdigit() else None
|
|
380
|
+
|
|
381
|
+
engine = TtsEngine(models_dir, model_file, voices_file)
|
|
382
|
+
server = TtsHttpServer(("127.0.0.1", port), engine)
|
|
383
|
+
log(f"listening on 127.0.0.1:{server.server_address[1]} (models_dir={models_dir})")
|
|
384
|
+
|
|
385
|
+
def watchdog() -> None:
|
|
386
|
+
"""父进程(Node server)退出后自动退出,避免残留孤儿进程(Windows 上尤为必要)。"""
|
|
387
|
+
while True:
|
|
388
|
+
time.sleep(5)
|
|
389
|
+
if not parent_alive(parent_pid):
|
|
390
|
+
log(f"parent pid {parent_pid} gone, exiting")
|
|
391
|
+
server.shutdown()
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
if parent_pid is not None:
|
|
395
|
+
threading.Thread(target=watchdog, daemon=True).start()
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
server.serve_forever()
|
|
399
|
+
except KeyboardInterrupt:
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if __name__ == "__main__":
|
|
404
|
+
main()
|
package/cli-wrapper.cjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fallback launcher for the @deepseekcode/cli wrapper package.
|
|
3
|
+
//
|
|
4
|
+
// Normally the postinstall script (install.cjs) copies the native binary over
|
|
5
|
+
// bin/dscode(.exe), so this file is never invoked. It exists for environments
|
|
6
|
+
// where postinstall doesn't run (--ignore-scripts) — users can run
|
|
7
|
+
// `node cli-wrapper.cjs` directly and pay the Node-process overhead as the
|
|
8
|
+
// price. Marks the child with DEEPSEEKCODE_INSTALLED_VIA_NPM_WRAPPER so the
|
|
9
|
+
// binary knows it was reached through the npm wrapper (update path decisions).
|
|
10
|
+
|
|
11
|
+
const { spawnSync } = require('child_process')
|
|
12
|
+
const { arch, constants } = require('os')
|
|
13
|
+
const path = require('path')
|
|
14
|
+
|
|
15
|
+
// Platform detection + PLATFORMS map is duplicated in install.cjs — keep in sync.
|
|
16
|
+
const WRAPPER_NAME = require('./package.json').name
|
|
17
|
+
// Same scope as the wrapper (@<scope>/cli-<platform>) — derived, not hard-coded,
|
|
18
|
+
// so one source file serves every published scope. Keep in sync with install.cjs.
|
|
19
|
+
const PACKAGE_PREFIX = WRAPPER_NAME
|
|
20
|
+
const BINARY_NAME = 'dscode'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const PLATFORMS = {
|
|
24
|
+
'darwin-arm64': { pkg: PACKAGE_PREFIX + '-darwin-arm64', bin: BINARY_NAME },
|
|
25
|
+
'darwin-x64': { pkg: PACKAGE_PREFIX + '-darwin-x64', bin: BINARY_NAME },
|
|
26
|
+
'linux-x64': { pkg: PACKAGE_PREFIX + '-linux-x64', bin: BINARY_NAME },
|
|
27
|
+
'linux-arm64': { pkg: PACKAGE_PREFIX + '-linux-arm64', bin: BINARY_NAME },
|
|
28
|
+
'linux-x64-musl': { pkg: PACKAGE_PREFIX + '-linux-x64-musl', bin: BINARY_NAME },
|
|
29
|
+
'linux-arm64-musl': { pkg: PACKAGE_PREFIX + '-linux-arm64-musl', bin: BINARY_NAME },
|
|
30
|
+
'win32-x64': { pkg: PACKAGE_PREFIX + '-win32-x64', bin: BINARY_NAME + '.exe' },
|
|
31
|
+
'win32-arm64': { pkg: PACKAGE_PREFIX + '-win32-arm64', bin: BINARY_NAME + '.exe' },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function detectMusl() {
|
|
35
|
+
if (process.platform !== 'linux') return false
|
|
36
|
+
const report =
|
|
37
|
+
typeof process.report?.getReport === 'function'
|
|
38
|
+
? process.report.getReport()
|
|
39
|
+
: null
|
|
40
|
+
return report != null && report.header?.glibcVersionRuntime === undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getPlatformKey() {
|
|
44
|
+
const platform = process.platform
|
|
45
|
+
let cpu = arch()
|
|
46
|
+
if (platform === 'linux') {
|
|
47
|
+
return 'linux-' + cpu + (detectMusl() ? '-musl' : '')
|
|
48
|
+
}
|
|
49
|
+
if (platform === 'darwin' && cpu === 'x64') {
|
|
50
|
+
const r = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8' })
|
|
51
|
+
if (r.stdout?.trim() === '1') cpu = 'arm64'
|
|
52
|
+
}
|
|
53
|
+
return platform + '-' + cpu
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getBinaryPath() {
|
|
57
|
+
const platformKey = getPlatformKey()
|
|
58
|
+
const info = PLATFORMS[platformKey]
|
|
59
|
+
if (!info) {
|
|
60
|
+
console.error(`[${WRAPPER_NAME}] Unsupported platform: ${process.platform} ${arch()}. Supported: ${Object.keys(PLATFORMS).join(', ')}`)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
const optionalDeps = require('./package.json').optionalDependencies || {}
|
|
64
|
+
if (!optionalDeps[info.pkg]) {
|
|
65
|
+
console.error(`[${WRAPPER_NAME}] Native binaries for ${platformKey} are not available on this release channel.`)
|
|
66
|
+
console.error(` Available: ${Object.keys(optionalDeps).map((p) => p.replace(PACKAGE_PREFIX + '-', '')).join(', ')}`)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const pkgDir = path.dirname(require.resolve(info.pkg + '/package.json'))
|
|
71
|
+
return path.join(pkgDir, info.bin)
|
|
72
|
+
} catch {
|
|
73
|
+
console.error(`[${WRAPPER_NAME}] Could not find native binary package "${info.pkg}".`)
|
|
74
|
+
if (platformKey === 'darwin-arm64' && arch() === 'x64') {
|
|
75
|
+
console.error(' You are running x64 Node under Rosetta 2 on Apple Silicon. npm only')
|
|
76
|
+
console.error(' installed the darwin-x64 binary.')
|
|
77
|
+
} else {
|
|
78
|
+
console.error(' Try reinstalling with: npm install')
|
|
79
|
+
}
|
|
80
|
+
process.exit(1)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
const binaryPath = getBinaryPath()
|
|
86
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
env: { ...process.env, DEEPSEEKCODE_INSTALLED_VIA_NPM_WRAPPER: '1' },
|
|
89
|
+
// windowsHide:本 wrapper 会被 daemon 当作 process.argv[1] 再次执行(见
|
|
90
|
+
// src/instance.ts 的 detached spawn,那层已带 windowsHide)。无控制台宿主下
|
|
91
|
+
// 这里拉起原生二进制若不隐藏,每次启动 daemon 都会补闪一个黑框。
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
})
|
|
94
|
+
if (result.error) {
|
|
95
|
+
console.error(`[${WRAPPER_NAME}] Failed to execute native binary at ` + binaryPath)
|
|
96
|
+
console.error(' ' + result.error.message)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
if (result.signal) {
|
|
100
|
+
// Node.js ignores some signals (e.g. SIGPIPE → SIG_IGN), so re-raising is
|
|
101
|
+
// unreliable. Exit with the POSIX convention 128+signum instead.
|
|
102
|
+
const signum = constants.signals[result.signal] ?? 0
|
|
103
|
+
process.exit(128 + signum)
|
|
104
|
+
} else {
|
|
105
|
+
process.exit(result.status ?? 1)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main()
|
package/install.cjs
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Postinstall for the @deepseekcode/cli wrapper package.
|
|
3
|
+
//
|
|
4
|
+
// Mirrors the Claude Code 2.x distribution model: the npm package is a thin
|
|
5
|
+
// installer. This script detects the platform, finds the matching native binary
|
|
6
|
+
// from optionalDependencies (@deepseekcode/cli-<platform>), and copies it over
|
|
7
|
+
// the bin/dscode.exe placeholder. After this runs, `dscode` execs the native
|
|
8
|
+
// binary directly — no Node process stays resident.
|
|
9
|
+
//
|
|
10
|
+
// If the native package isn't present (--omit=optional), prints instructions and
|
|
11
|
+
// leaves the placeholder stub in place. cli-wrapper.cjs (same directory) can be
|
|
12
|
+
// invoked manually as a fallback.
|
|
13
|
+
//
|
|
14
|
+
// On Windows the stub (a non-PE .exe) surfaces as a native "not a valid Win32
|
|
15
|
+
// application" error, so its message is best-effort; install.cjs failure output
|
|
16
|
+
// above is the primary guidance when postinstall does run.
|
|
17
|
+
//
|
|
18
|
+
// Platform detection + PLATFORMS map is duplicated in cli-wrapper.cjs — keep in sync.
|
|
19
|
+
|
|
20
|
+
const { spawnSync } = require('child_process')
|
|
21
|
+
const {
|
|
22
|
+
copyFileSync,
|
|
23
|
+
linkSync,
|
|
24
|
+
unlinkSync,
|
|
25
|
+
chmodSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
writeFileSync,
|
|
28
|
+
statSync,
|
|
29
|
+
} = require('fs')
|
|
30
|
+
const { arch } = require('os')
|
|
31
|
+
const path = require('path')
|
|
32
|
+
|
|
33
|
+
const WRAPPER_NAME = require('./package.json').name
|
|
34
|
+
// Platform packages live in the SAME npm scope as the wrapper
|
|
35
|
+
// (@<scope>/cli-<platform>). Derived rather than hard-coded so one source file
|
|
36
|
+
// serves every scope the release is published to (see scripts/multi-publish.mjs).
|
|
37
|
+
const PACKAGE_PREFIX = WRAPPER_NAME
|
|
38
|
+
const BINARY_NAME = 'dscode'
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const PLATFORMS = {
|
|
42
|
+
'darwin-arm64': { pkg: PACKAGE_PREFIX + '-darwin-arm64', bin: BINARY_NAME },
|
|
43
|
+
'darwin-x64': { pkg: PACKAGE_PREFIX + '-darwin-x64', bin: BINARY_NAME },
|
|
44
|
+
'linux-x64': { pkg: PACKAGE_PREFIX + '-linux-x64', bin: BINARY_NAME },
|
|
45
|
+
'linux-arm64': { pkg: PACKAGE_PREFIX + '-linux-arm64', bin: BINARY_NAME },
|
|
46
|
+
'linux-x64-musl': { pkg: PACKAGE_PREFIX + '-linux-x64-musl', bin: BINARY_NAME },
|
|
47
|
+
'linux-arm64-musl': { pkg: PACKAGE_PREFIX + '-linux-arm64-musl', bin: BINARY_NAME },
|
|
48
|
+
'win32-x64': { pkg: PACKAGE_PREFIX + '-win32-x64', bin: BINARY_NAME + '.exe' },
|
|
49
|
+
'win32-arm64': { pkg: PACKAGE_PREFIX + '-win32-arm64', bin: BINARY_NAME + '.exe' },
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectMusl() {
|
|
53
|
+
if (process.platform !== 'linux') return false
|
|
54
|
+
// process.report is available in Node ≥12; glibcVersionRuntime is absent on musl.
|
|
55
|
+
// Faster than spawning `ldd` and avoids the ENOENT→musl false positive when ldd
|
|
56
|
+
// isn't on PATH (minimal containers).
|
|
57
|
+
const report =
|
|
58
|
+
typeof process.report?.getReport === 'function'
|
|
59
|
+
? process.report.getReport()
|
|
60
|
+
: null
|
|
61
|
+
return report != null && report.header?.glibcVersionRuntime === undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getPlatformKey() {
|
|
65
|
+
const platform = process.platform
|
|
66
|
+
let cpu = arch()
|
|
67
|
+
if (platform === 'linux') {
|
|
68
|
+
return 'linux-' + cpu + (detectMusl() ? '-musl' : '')
|
|
69
|
+
}
|
|
70
|
+
// Rosetta 2: an x64 Node on Apple Silicon reports arch()==='x64'. Prefer the
|
|
71
|
+
// native arm64 binary — the x64 build needs AVX, which Rosetta doesn't emulate.
|
|
72
|
+
if (platform === 'darwin' && cpu === 'x64') {
|
|
73
|
+
const r = spawnSync('sysctl', ['-n', 'sysctl.proc_translated'], { encoding: 'utf8' })
|
|
74
|
+
if (r.stdout?.trim() === '1') cpu = 'arm64'
|
|
75
|
+
}
|
|
76
|
+
return platform + '-' + cpu
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function placeBinary(src, dest) {
|
|
80
|
+
// Try hardlink first (instant, zero extra disk for a ~110MB binary; src and
|
|
81
|
+
// dest are both under node_modules/ so same-filesystem is the common case).
|
|
82
|
+
// We attempt the link BEFORE touching dest — if src is missing (partial
|
|
83
|
+
// extraction) the first linkSync throws ENOENT and the fallback stub stays.
|
|
84
|
+
try {
|
|
85
|
+
linkSync(src, dest)
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (err.code === 'EEXIST') {
|
|
88
|
+
// Read the stub before unlinking so we can restore it if both link and
|
|
89
|
+
// copy fail (ENOSPC, NFS error mid-copy). Only read if dest is
|
|
90
|
+
// stub-sized — on re-install dest is the ~110MB binary.
|
|
91
|
+
const stub = statSync(dest).size < 4096 ? readFileSync(dest) : null
|
|
92
|
+
unlinkSync(dest)
|
|
93
|
+
try {
|
|
94
|
+
linkSync(src, dest)
|
|
95
|
+
} catch {
|
|
96
|
+
try {
|
|
97
|
+
copyFileSync(src, dest)
|
|
98
|
+
} catch (copyErr) {
|
|
99
|
+
if (stub) {
|
|
100
|
+
try {
|
|
101
|
+
writeFileSync(dest, stub, { mode: 0o755 })
|
|
102
|
+
} catch {
|
|
103
|
+
// Do nothing
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
throw copyErr
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else if (err.code === 'EXDEV' || err.code === 'EPERM') {
|
|
110
|
+
// Cross-device or no-link-perms — copyFileSync overwrites existing dest.
|
|
111
|
+
copyFileSync(src, dest)
|
|
112
|
+
} else {
|
|
113
|
+
throw err
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (process.platform !== 'win32') {
|
|
117
|
+
chmodSync(dest, 0o755)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function main() {
|
|
122
|
+
const platformKey = getPlatformKey()
|
|
123
|
+
const info = PLATFORMS[platformKey]
|
|
124
|
+
|
|
125
|
+
if (!info) {
|
|
126
|
+
console.error(
|
|
127
|
+
`[${WRAPPER_NAME} postinstall] Unsupported platform: ${process.platform} ${arch()}`,
|
|
128
|
+
)
|
|
129
|
+
console.error(` Supported: ${Object.keys(PLATFORMS).join(', ')}`)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const optionalDeps = require('./package.json').optionalDependencies || {}
|
|
134
|
+
if (!optionalDeps[info.pkg]) {
|
|
135
|
+
console.error(
|
|
136
|
+
`[${WRAPPER_NAME} postinstall] Native binaries for ${platformKey} are not available on this release channel.`,
|
|
137
|
+
)
|
|
138
|
+
console.error(` Available: ${Object.keys(optionalDeps).map((p) => p.replace(PACKAGE_PREFIX + '-', '')).join(', ')}`)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let src
|
|
143
|
+
try {
|
|
144
|
+
const pkgDir = path.dirname(require.resolve(info.pkg + '/package.json'))
|
|
145
|
+
src = path.join(pkgDir, info.bin)
|
|
146
|
+
} catch {
|
|
147
|
+
console.error(`[${WRAPPER_NAME} postinstall] Native package "${info.pkg}" not found.`)
|
|
148
|
+
if (platformKey === 'darwin-arm64' && arch() === 'x64') {
|
|
149
|
+
console.error(' You are running x64 Node under Rosetta 2 on Apple Silicon. npm only')
|
|
150
|
+
console.error(' installed the darwin-x64 binary. Install arm64 Node and reinstall:')
|
|
151
|
+
console.error(' arch -arm64 zsh -c "nvm install --lts && npm i -g ' + WRAPPER_NAME + '"')
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
console.error(' This happens with --omit=optional or when the download failed.')
|
|
155
|
+
console.error(' The `dscode` command will print instructions when invoked.')
|
|
156
|
+
console.error(' Fallback: node ' + path.join(__dirname, 'cli-wrapper.cjs'))
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Always write to bin/dscode.exe — the package.json bin field points here
|
|
161
|
+
// (npm generates its shim/symlink from the .exe extension at install time).
|
|
162
|
+
// On Unix npm links `bin/dscode` → `bin/dscode.exe` and ignores the
|
|
163
|
+
// extension, so overwriting the .exe path replaces the running target on
|
|
164
|
+
// every platform. Same pattern as Claude Code / Bun npm packages.
|
|
165
|
+
const dest = path.join(__dirname, 'bin', 'dscode.exe')
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
placeBinary(src, dest)
|
|
169
|
+
console.log(`[${WRAPPER_NAME} postinstall] Native binary placed → ${dest}`)
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`[${WRAPPER_NAME} postinstall] Failed to place binary: ${err.message}`)
|
|
172
|
+
console.error(' Fallback: node ' + path.join(__dirname, 'cli-wrapper.cjs'))
|
|
173
|
+
process.exitCode = 1
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main()
|