specrails-desktop 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/specrails/batch-implement.md +287 -0
- package/.claude/commands/specrails/compat-check.md +271 -0
- package/.claude/commands/specrails/doctor.md +62 -0
- package/.claude/commands/specrails/enrich.md +1635 -0
- package/.claude/commands/specrails/explore-spec.md +173 -0
- package/.claude/commands/specrails/health-check.md +527 -0
- package/.claude/commands/specrails/implement.md +1457 -0
- package/.claude/commands/specrails/memory-inspect.md +259 -0
- package/.claude/commands/specrails/opsx-diff.md +419 -0
- package/.claude/commands/specrails/propose-spec.md +102 -0
- package/.claude/commands/specrails/reconfig.md +89 -0
- package/.claude/commands/specrails/refactor-recommender.md +212 -0
- package/.claude/commands/specrails/retry.md +363 -0
- package/.claude/commands/specrails/telemetry.md +552 -0
- package/.claude/commands/specrails/why.md +96 -0
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/cli/dist/specrails-desktop.js +1098 -0
- package/client/dist/assets/ActivityFeedPage-Gy4x8dBt.js +1 -0
- package/client/dist/assets/AgentsPage-CPgu--Fb.js +86 -0
- package/client/dist/assets/AnalyticsPage-B5sJEee2.js +1 -0
- package/client/dist/assets/BarChart-7IMQ8HY1.js +33 -0
- package/client/dist/assets/CodePage-CBdFvbwe.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-w0rdTq4w.js +1 -0
- package/client/dist/assets/DocsDialog-BZUYM7wm.js +11 -0
- package/client/dist/assets/DocsPage-9QglWl46.js +11 -0
- package/client/dist/assets/ExportDropdown-BLZFXtNi.js +1 -0
- package/client/dist/assets/IntegrationsPage-BxBE4y99.js +3 -0
- package/client/dist/assets/JobDetailPage-DydWx_5S.js +16 -0
- package/client/dist/assets/JobsPage-20ibw0IO.js +1 -0
- package/client/dist/assets/abap-Bw6f2wDG.js +1 -0
- package/client/dist/assets/activity-BEIp_Y1A.js +1 -0
- package/client/dist/assets/activity-BdrPln96.js +1 -0
- package/client/dist/assets/activity-CpkRS8Sx.js +1 -0
- package/client/dist/assets/activity-DKCpESPt.js +1 -0
- package/client/dist/assets/activity-DOUVEjJi.js +1 -0
- package/client/dist/assets/activity-DRwkql_y.js +1 -0
- package/client/dist/assets/activity-DcDQ7tjw.js +1 -0
- package/client/dist/assets/activity-Dv6H7wEr.js +1 -0
- package/client/dist/assets/addon-image-3WCl5Vhd.js +1 -0
- package/client/dist/assets/addon-ligatures-C5OdliKs.js +2 -0
- package/client/dist/assets/addon-webgl-BbX6pSjl.js +44 -0
- package/client/dist/assets/addspec-B5yl4Loj.js +1 -0
- package/client/dist/assets/addspec-BEeF5-zc.js +1 -0
- package/client/dist/assets/addspec-D33ocMxf.js +1 -0
- package/client/dist/assets/addspec-DFswZ0jK.js +1 -0
- package/client/dist/assets/addspec-DRE-jZv7.js +1 -0
- package/client/dist/assets/addspec-DVZ15Jp8.js +1 -0
- package/client/dist/assets/addspec-Fkv91Opc.js +1 -0
- package/client/dist/assets/addspec-GWm4ffKl.js +1 -0
- package/client/dist/assets/agents-1nCDWRmP.js +1 -0
- package/client/dist/assets/agents-Bm9rPqnt.js +1 -0
- package/client/dist/assets/agents-CMxtJMLD.js +1 -0
- package/client/dist/assets/agents-DK-Dlc0i.js +1 -0
- package/client/dist/assets/agents-Q6Ldfpxx.js +1 -0
- package/client/dist/assets/agents-TeOSy-ax.js +1 -0
- package/client/dist/assets/agents-iTqjRajS.js +1 -0
- package/client/dist/assets/agents-s87sMGzL.js +1 -0
- package/client/dist/assets/agentstudio-B6Wb59E7.js +1 -0
- package/client/dist/assets/agentstudio-BADhZ41e.js +1 -0
- package/client/dist/assets/agentstudio-BSnWLR63.js +1 -0
- package/client/dist/assets/agentstudio-BdidyBzZ.js +1 -0
- package/client/dist/assets/agentstudio-CxlUllqI.js +1 -0
- package/client/dist/assets/agentstudio-D3I62TLJ.js +1 -0
- package/client/dist/assets/agentstudio-DuH9TogZ.js +1 -0
- package/client/dist/assets/agentstudio-Kw88_dUF.js +1 -0
- package/client/dist/assets/aiedit-BWxHGsYA.js +1 -0
- package/client/dist/assets/aiedit-D2ji6Qy0.js +1 -0
- package/client/dist/assets/aiedit-DAhZTvtk.js +1 -0
- package/client/dist/assets/aiedit-DJMny-D5.js +1 -0
- package/client/dist/assets/aiedit-DOcxERkU.js +1 -0
- package/client/dist/assets/aiedit-DvrcbwGv.js +1 -0
- package/client/dist/assets/aiedit-TTwzL1TS.js +1 -0
- package/client/dist/assets/aiedit-WBSjT_C1.js +1 -0
- package/client/dist/assets/analytics-BIdr0YfL.js +1 -0
- package/client/dist/assets/analytics-C6EzgtdE.js +1 -0
- package/client/dist/assets/analytics-C9Zc-rkM.js +1 -0
- package/client/dist/assets/analytics-CVx3YOc0.js +1 -0
- package/client/dist/assets/analytics-CYj0tfj7.js +1 -0
- package/client/dist/assets/analytics-CnY4kNG3.js +1 -0
- package/client/dist/assets/analytics-CrPCZRJ-.js +1 -0
- package/client/dist/assets/analytics-DMCto-TF.js +1 -0
- package/client/dist/assets/apex-Cw8_REBo.js +1 -0
- package/client/dist/assets/atom-one-dark-B-oHczHB.css +1 -0
- package/client/dist/assets/attachments-BIsSSnHJ.js +1 -0
- package/client/dist/assets/attachments-BW4L3l2L.js +1 -0
- package/client/dist/assets/attachments-Bcf6BG6V.js +1 -0
- package/client/dist/assets/attachments-Bke8sCU4.js +1 -0
- package/client/dist/assets/attachments-COcrGRFz.js +1 -0
- package/client/dist/assets/attachments-DYHGA2Dj.js +1 -0
- package/client/dist/assets/attachments-Dd92KpUH.js +1 -0
- package/client/dist/assets/attachments-DzdU6DV6.js +1 -0
- package/client/dist/assets/azcli-Cz6HAoOw.js +1 -0
- package/client/dist/assets/bat-CcJ-xyqL.js +1 -0
- package/client/dist/assets/bicep-z1WDCKYz.js +2 -0
- package/client/dist/assets/browser-5ErDlJoR.js +1 -0
- package/client/dist/assets/browser-Bc-YdlVg.js +1 -0
- package/client/dist/assets/browser-BlYF4OOq.js +1 -0
- package/client/dist/assets/browser-CT-ReZGt.js +1 -0
- package/client/dist/assets/browser-DGITz3fC.js +1 -0
- package/client/dist/assets/browser-JsAIGCEW.js +1 -0
- package/client/dist/assets/browser-M5-rbPlw.js +1 -0
- package/client/dist/assets/browser-Qya9cARy.js +1 -0
- package/client/dist/assets/cameligo-BRewOpfa.js +1 -0
- package/client/dist/assets/chat-BEGuC03z.js +1 -0
- package/client/dist/assets/chat-BEW60P_u.js +1 -0
- package/client/dist/assets/chat-BQNMD0PL.js +1 -0
- package/client/dist/assets/chat-BsbNGPW9.js +1 -0
- package/client/dist/assets/chat-CboQguCi.js +1 -0
- package/client/dist/assets/chat-DRCa9pOt.js +1 -0
- package/client/dist/assets/chat-DwUm6W9z.js +1 -0
- package/client/dist/assets/chat-yoXwguQu.js +1 -0
- package/client/dist/assets/chunk-CilyBKbf.js +1 -0
- package/client/dist/assets/clojure-DBjRWN6g.js +1 -0
- package/client/dist/assets/clsx-DnqN-uhr.js +1 -0
- package/client/dist/assets/code-AL1rVIMb.js +1 -0
- package/client/dist/assets/code-C0BKpkht.js +1 -0
- package/client/dist/assets/code-C0FTS3ew.js +1 -0
- package/client/dist/assets/code-CPcHxzxw.js +1 -0
- package/client/dist/assets/code-D3ryDniw.js +1 -0
- package/client/dist/assets/code-D3zVVQTj.js +1 -0
- package/client/dist/assets/code-PCmfS3dn.js +1 -0
- package/client/dist/assets/code-exI0G5Wd.js +1 -0
- package/client/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/client/dist/assets/coffee-Cfk_XHGR.js +1 -0
- package/client/dist/assets/commands-B772IyDa.js +1 -0
- package/client/dist/assets/commands-BDDp6xFG.js +1 -0
- package/client/dist/assets/commands-CJxCry-o.js +1 -0
- package/client/dist/assets/commands-CfgY-_of.js +1 -0
- package/client/dist/assets/commands-DLrvnPNg.js +1 -0
- package/client/dist/assets/commands-IXMOKBYt.js +1 -0
- package/client/dist/assets/commands-UD1NzmwX.js +1 -0
- package/client/dist/assets/commands-sqrqsxyE.js +1 -0
- package/client/dist/assets/common-DCr6VzJ7.js +1 -0
- package/client/dist/assets/common-Dard9UNH.js +1 -0
- package/client/dist/assets/common-DeDELLZJ.js +1 -0
- package/client/dist/assets/common-DltqHaAe.js +1 -0
- package/client/dist/assets/common-Dmm1GhdD.js +1 -0
- package/client/dist/assets/common-DnjcgkPH.js +1 -0
- package/client/dist/assets/common-GbpxfPG8.js +1 -0
- package/client/dist/assets/common-wA36jmj1.js +1 -0
- package/client/dist/assets/cpp-BVob6BaP.js +1 -0
- package/client/dist/assets/csharp-C4fbRuOu.js +1 -0
- package/client/dist/assets/csp-DthFP_vT.js +1 -0
- package/client/dist/assets/css-CGMH0hcW.js +3 -0
- package/client/dist/assets/css.worker-Wv5dxAWO.js +89 -0
- package/client/dist/assets/cssMode-Cc6ozl-J.js +1 -0
- package/client/dist/assets/cypher-Pnf68BRV.js +1 -0
- package/client/dist/assets/dart-PMMOtxZX.js +1 -0
- package/client/dist/assets/dashboard-B4ixDVk8.js +1 -0
- package/client/dist/assets/dashboard-BZBADHSj.js +1 -0
- package/client/dist/assets/dashboard-C1MfeUHs.js +1 -0
- package/client/dist/assets/dashboard-C7SK6xu5.js +1 -0
- package/client/dist/assets/dashboard-CB6Le1yN.js +1 -0
- package/client/dist/assets/dashboard-CoTpMOBM.js +1 -0
- package/client/dist/assets/dashboard-Duo4DDCW.js +1 -0
- package/client/dist/assets/dashboard-I19DXBxw.js +1 -0
- package/client/dist/assets/dist-js-BY-Fv_fg.js +1 -0
- package/client/dist/assets/dist-js-Bakc4uxT.js +1 -0
- package/client/dist/assets/dockerfile-di1nsJCc.js +1 -0
- package/client/dist/assets/ecl-D_WVcB5M.js +1 -0
- package/client/dist/assets/editor-Br_kD0ds.css +1 -0
- package/client/dist/assets/editor.api2-XLGzZfbc.js +872 -0
- package/client/dist/assets/editor.main-CfXxHimg.js +6 -0
- package/client/dist/assets/editor.worker-Bd9IXS8d.js +26 -0
- package/client/dist/assets/elixir-OAdJEMOn.js +1 -0
- package/client/dist/assets/explore-4mFpnrKU.js +1 -0
- package/client/dist/assets/explore-A8Ltoblq.js +1 -0
- package/client/dist/assets/explore-B9A3iN2W.js +1 -0
- package/client/dist/assets/explore-BV5Xxlsn.js +1 -0
- package/client/dist/assets/explore-BrBJvfjP.js +1 -0
- package/client/dist/assets/explore-C3FSE42C.js +1 -0
- package/client/dist/assets/explore-D2EFgt8J.js +1 -0
- package/client/dist/assets/explore-hFc3HFcp.js +1 -0
- package/client/dist/assets/flow9-D3QEZjgn.js +1 -0
- package/client/dist/assets/format-command-CwGuwzGA.js +1 -0
- package/client/dist/assets/freemarker2-DP7J1gG3.js +3 -0
- package/client/dist/assets/fsharp-BF0k_8N8.js +1 -0
- package/client/dist/assets/go-BAQO5Jsz.js +1 -0
- package/client/dist/assets/graphql-hdFVFkiV.js +1 -0
- package/client/dist/assets/handlebars-BjRlucw6.js +1 -0
- package/client/dist/assets/hcl-DWnl1o-X.js +1 -0
- package/client/dist/assets/html-OumBQJ-U.js +1 -0
- package/client/dist/assets/html.worker-CQP8QQsS.js +502 -0
- package/client/dist/assets/htmlMode-CStc3zXM.js +1 -0
- package/client/dist/assets/index-CimDRRi7.css +2 -0
- package/client/dist/assets/index-XGZaKl_u.js +142 -0
- package/client/dist/assets/ini-CB-6OVu3.js +1 -0
- package/client/dist/assets/integrations-C3p12Ms6.js +1 -0
- package/client/dist/assets/integrations-Cr6hH7XR.js +1 -0
- package/client/dist/assets/integrations-Cublz3m6.js +1 -0
- package/client/dist/assets/integrations-D28q1kF6.js +1 -0
- package/client/dist/assets/integrations-DRdbki5W.js +1 -0
- package/client/dist/assets/integrations-DaC4SzzL.js +1 -0
- package/client/dist/assets/integrations-DmQYCUvN.js +1 -0
- package/client/dist/assets/integrations-HIlUxXVs.js +1 -0
- package/client/dist/assets/java-d1CmfiHX.js +1 -0
- package/client/dist/assets/javascript-CMk--e7g.js +1 -0
- package/client/dist/assets/jobs-BE1siB0M.js +1 -0
- package/client/dist/assets/jobs-BHcQ_Faf.js +1 -0
- package/client/dist/assets/jobs-CFfc2dNX.js +1 -0
- package/client/dist/assets/jobs-CSi5n8X_.js +1 -0
- package/client/dist/assets/jobs-Dc3X86PY.js +1 -0
- package/client/dist/assets/jobs-De5tASex.js +1 -0
- package/client/dist/assets/jobs-DsoXEdo7.js +1 -0
- package/client/dist/assets/jobs-Wl-ApPMb.js +1 -0
- package/client/dist/assets/json.worker-DzV-CpCQ.js +58 -0
- package/client/dist/assets/jsonMode-C2h3ZcjZ.js +7 -0
- package/client/dist/assets/julia-Bgv08lKa.js +1 -0
- package/client/dist/assets/kotlin-u98kaVTf.js +1 -0
- package/client/dist/assets/less-CjYwpgg5.js +2 -0
- package/client/dist/assets/lexon-YTjaAFBB.js +1 -0
- package/client/dist/assets/lib-CPxTMOAq.js +7 -0
- package/client/dist/assets/liquid-mI3KJrBE.js +1 -0
- package/client/dist/assets/lspLanguageFeatures-DU09ggWi.js +4 -0
- package/client/dist/assets/lua-BzmkWv27.js +1 -0
- package/client/dist/assets/m3-CFwk9fw0.js +1 -0
- package/client/dist/assets/markdown-CR5iMpSZ.js +1 -0
- package/client/dist/assets/mdx-C41VDTR_.js +1 -0
- package/client/dist/assets/mips-CcEalc17.js +1 -0
- package/client/dist/assets/monaco.contribution-CPObAXMC.js +2 -0
- package/client/dist/assets/msdax-BQbkawnr.js +1 -0
- package/client/dist/assets/mysql-GTlaaW_P.js +1 -0
- package/client/dist/assets/nav-0fwkrgHt.js +1 -0
- package/client/dist/assets/nav-BEL3MTwK.js +1 -0
- package/client/dist/assets/nav-B_G-TJDW.js +1 -0
- package/client/dist/assets/nav-C2YXcbZS.js +1 -0
- package/client/dist/assets/nav-ClzOE4mA.js +1 -0
- package/client/dist/assets/nav-CtYwmMgu.js +1 -0
- package/client/dist/assets/nav-D2bOGSEg.js +1 -0
- package/client/dist/assets/nav-iH1V5j6o.js +1 -0
- package/client/dist/assets/objective-c-Byu1T5if.js +1 -0
- package/client/dist/assets/pascal-BrfzBfRm.js +1 -0
- package/client/dist/assets/pascaligo-BXXKFUeo.js +1 -0
- package/client/dist/assets/perl-B3OikKq-.js +1 -0
- package/client/dist/assets/pgsql-CTsa0Acc.js +1 -0
- package/client/dist/assets/php-DiQh3FUW.js +1 -0
- package/client/dist/assets/pla-92uH8Fzm.js +1 -0
- package/client/dist/assets/postiats-BbeWkKUr.js +1 -0
- package/client/dist/assets/powerquery-DgDMzpsm.js +1 -0
- package/client/dist/assets/powershell-BfdUUzaG.js +1 -0
- package/client/dist/assets/preload-helper-DSXbuxSR.js +1 -0
- package/client/dist/assets/protobuf-BojW2ftW.js +2 -0
- package/client/dist/assets/pug-BxqTg3IU.js +1 -0
- package/client/dist/assets/python-Y27rKQtk.js +1 -0
- package/client/dist/assets/qsharp-BX_A-MW9.js +1 -0
- package/client/dist/assets/r-D9BMnxvJ.js +1 -0
- package/client/dist/assets/razor-Cd5-q9Bp.js +1 -0
- package/client/dist/assets/redis-5cJqEQJJ.js +1 -0
- package/client/dist/assets/redshift-d8BBqiwb.js +1 -0
- package/client/dist/assets/restructuredtext-C8a6yIcZ.js +1 -0
- package/client/dist/assets/ruby-egeh-6KX.js +1 -0
- package/client/dist/assets/rust-a3r9IInB.js +1 -0
- package/client/dist/assets/sb-y8iRIDei.js +1 -0
- package/client/dist/assets/scala-BPDK2AmK.js +1 -0
- package/client/dist/assets/scheme-BIWUEoOs.js +1 -0
- package/client/dist/assets/scss-CA-PSzwg.js +3 -0
- package/client/dist/assets/settings-55oDcbSh.js +1 -0
- package/client/dist/assets/settings-Bd4Tq1RB.js +1 -0
- package/client/dist/assets/settings-CCSM-Fhn.js +1 -0
- package/client/dist/assets/settings-D3e_bDoW.js +1 -0
- package/client/dist/assets/settings-DKbTkbn7.js +1 -0
- package/client/dist/assets/settings-Dxpo6_w7.js +1 -0
- package/client/dist/assets/settings-bt84e3Aa.js +1 -0
- package/client/dist/assets/settings-nu68QukM.js +1 -0
- package/client/dist/assets/setup-BMqwfbW9.js +1 -0
- package/client/dist/assets/setup-Bb5LcG28.js +1 -0
- package/client/dist/assets/setup-BeEx2_da.js +1 -0
- package/client/dist/assets/setup-CCCrB53Q.js +1 -0
- package/client/dist/assets/setup-CJA0ATmd.js +1 -0
- package/client/dist/assets/setup-CeiDbZcb.js +1 -0
- package/client/dist/assets/setup-Cus7TApA.js +1 -0
- package/client/dist/assets/setup-D9qOs2Xo.js +1 -0
- package/client/dist/assets/shell--LiT1Bja.js +1 -0
- package/client/dist/assets/solidity-DdqZccZg.js +1 -0
- package/client/dist/assets/sophia-S6-YxNG_.js +1 -0
- package/client/dist/assets/sparql-BSf5kMp2.js +1 -0
- package/client/dist/assets/specs-BFfu3u-a.js +1 -0
- package/client/dist/assets/specs-B__C8-8a.js +1 -0
- package/client/dist/assets/specs-CZ1PsXsC.js +1 -0
- package/client/dist/assets/specs-D2FzlLn9.js +1 -0
- package/client/dist/assets/specs-DaUTrNF9.js +1 -0
- package/client/dist/assets/specs-Dyc5hYeE.js +1 -0
- package/client/dist/assets/specs-cKEh2LXt.js +1 -0
- package/client/dist/assets/specs-k0PyLDVt.js +1 -0
- package/client/dist/assets/sql-D7KgjR8G.js +1 -0
- package/client/dist/assets/st-BnoDa-Ml.js +1 -0
- package/client/dist/assets/swift-DEUHTkUX.js +1 -0
- package/client/dist/assets/systemverilog-Tqb_KPnW.js +1 -0
- package/client/dist/assets/tcl-BmBFS2qq.js +1 -0
- package/client/dist/assets/terminal-80yDMgMF.js +1 -0
- package/client/dist/assets/terminal-Bje4ziIa.js +1 -0
- package/client/dist/assets/terminal-C2WYcFHF.js +1 -0
- package/client/dist/assets/terminal-CSONJOex.js +1 -0
- package/client/dist/assets/terminal-DEqzGtcr.js +1 -0
- package/client/dist/assets/terminal-DeWzh6ys.js +1 -0
- package/client/dist/assets/terminal-YOlsJCQj.js +1 -0
- package/client/dist/assets/terminal-lkZYR4wJ.js +1 -0
- package/client/dist/assets/tickets-CB7N30gm.js +1 -0
- package/client/dist/assets/tickets-CF2PYelu.js +1 -0
- package/client/dist/assets/tickets-DNOANUXr.js +1 -0
- package/client/dist/assets/tickets-DU1aqsbr.js +1 -0
- package/client/dist/assets/tickets-DYvafSaY.js +1 -0
- package/client/dist/assets/tickets-DlpC_iTg.js +1 -0
- package/client/dist/assets/tickets-DucYgtdl.js +1 -0
- package/client/dist/assets/tickets-clefmXLv.js +1 -0
- package/client/dist/assets/ts.worker-METxwbDZ.js +67719 -0
- package/client/dist/assets/tsMode-B0y_xEci.js +11 -0
- package/client/dist/assets/twig-BQV8igWC.js +1 -0
- package/client/dist/assets/typescript-BzK0OgwW.js +1 -0
- package/client/dist/assets/typespec-DlFroUGY.js +1 -0
- package/client/dist/assets/useProjectCache-DSaiGFjV.js +1 -0
- package/client/dist/assets/vb-BlrJpIMX.js +1 -0
- package/client/dist/assets/wgsl-BWgIc6FZ.js +298 -0
- package/client/dist/assets/workers-rt--R2Qy.js +1 -0
- package/client/dist/assets/xml-eX9QXAmI.js +1 -0
- package/client/dist/assets/yaml-fcsNkpOt.js +1 -0
- package/client/dist/index.html +246 -0
- package/docs/README.md +54 -0
- package/docs/cli.md +198 -0
- package/docs/codex.md +210 -0
- package/docs/creating-specs.md +197 -0
- package/docs/customizing.md +197 -0
- package/docs/getting-started.md +140 -0
- package/docs/internals/README.md +25 -0
- package/docs/internals/adding-a-provider.md +238 -0
- package/docs/internals/api-reference.md +634 -0
- package/docs/internals/architecture.md +332 -0
- package/docs/internals/configuration.md +172 -0
- package/docs/internals/openspec-workflow.md +282 -0
- package/docs/internals/operations-runbook.md +198 -0
- package/docs/internals/profiles.md +152 -0
- package/docs/platforms/macos.md +130 -0
- package/docs/platforms/windows.md +81 -0
- package/docs/running-pipelines.md +240 -0
- package/docs/terminal.md +138 -0
- package/docs/tracking-cost.md +155 -0
- package/package.json +82 -0
- package/server/dist/agent-generator.js +232 -0
- package/server/dist/agent-refine-db.js +124 -0
- package/server/dist/agent-refine-manager.js +526 -0
- package/server/dist/ai-invocations.js +111 -0
- package/server/dist/attachment-manager.js +299 -0
- package/server/dist/auth.js +207 -0
- package/server/dist/binary-probe.js +35 -0
- package/server/dist/browser-capture-manager.js +576 -0
- package/server/dist/browser-capture-types.js +28 -0
- package/server/dist/browser-network.js +149 -0
- package/server/dist/browser-playwright.js +888 -0
- package/server/dist/build-dirs.js +44 -0
- package/server/dist/changes-reader.js +120 -0
- package/server/dist/chat-manager.js +1060 -0
- package/server/dist/chromium-resolver.js +311 -0
- package/server/dist/code-explorer-router.js +788 -0
- package/server/dist/codex-otel-bridge.js +235 -0
- package/server/dist/command-resolver.js +102 -0
- package/server/dist/config.js +306 -0
- package/server/dist/context-budget.js +113 -0
- package/server/dist/context-scope.js +279 -0
- package/server/dist/contract-refine-runner.js +521 -0
- package/server/dist/core-compat.js +207 -0
- package/server/dist/core-package.js +14 -0
- package/server/dist/db.js +1034 -0
- package/server/dist/desktop-analytics.js +156 -0
- package/server/dist/desktop-db.js +456 -0
- package/server/dist/desktop-router.js +735 -0
- package/server/dist/docs-router.js +207 -0
- package/server/dist/explore-contract-refine.js +421 -0
- package/server/dist/explore-cwd-manager.js +242 -0
- package/server/dist/explore-draft-title.js +47 -0
- package/server/dist/explore-smash.js +450 -0
- package/server/dist/feature-flags.js +17 -0
- package/server/dist/file-provenance.js +382 -0
- package/server/dist/file-summary-generator.js +221 -0
- package/server/dist/file-summary-manager.js +689 -0
- package/server/dist/hooks.js +102 -0
- package/server/dist/ids.js +7 -0
- package/server/dist/index.js +586 -0
- package/server/dist/metrics.js +136 -0
- package/server/dist/mobile/index.js +16 -0
- package/server/dist/mobile/mobile-admin-router.js +84 -0
- package/server/dist/mobile/mobile-auth.js +67 -0
- package/server/dist/mobile/mobile-devices.js +80 -0
- package/server/dist/mobile/mobile-event-bus.js +39 -0
- package/server/dist/mobile/mobile-gateway.js +285 -0
- package/server/dist/mobile/mobile-mdns.js +81 -0
- package/server/dist/mobile/mobile-pairing.js +179 -0
- package/server/dist/mobile/mobile-redact.js +53 -0
- package/server/dist/mobile/mobile-router.js +411 -0
- package/server/dist/mobile/mobile-tls.js +86 -0
- package/server/dist/mobile/mobile-types.js +9 -0
- package/server/dist/mobile/mobile-ws.js +275 -0
- package/server/dist/path-resolver.js +298 -0
- package/server/dist/plugin-manager.js +617 -0
- package/server/dist/plugins/claude-approval.js +179 -0
- package/server/dist/plugins/claude-md-mutation.js +146 -0
- package/server/dist/plugins/codex-mcp.js +108 -0
- package/server/dist/plugins/contributors.js +72 -0
- package/server/dist/plugins/drift.js +58 -0
- package/server/dist/plugins/index.js +14 -0
- package/server/dist/plugins/json-mutation.js +120 -0
- package/server/dist/plugins/manager.js +32 -0
- package/server/dist/plugins/ownership.js +86 -0
- package/server/dist/plugins/paths.js +37 -0
- package/server/dist/plugins/prereq-installer.js +104 -0
- package/server/dist/plugins/rail-integration.js +79 -0
- package/server/dist/plugins/serena/index.js +13 -0
- package/server/dist/plugins/serena/install.js +91 -0
- package/server/dist/plugins/serena/instructions-content.js +21 -0
- package/server/dist/plugins/serena/manifest.js +111 -0
- package/server/dist/plugins/serena/verify.js +78 -0
- package/server/dist/plugins-router.js +215 -0
- package/server/dist/pricing.js +89 -0
- package/server/dist/profile-manager.js +310 -0
- package/server/dist/profiles-router.js +759 -0
- package/server/dist/project-registry.js +443 -0
- package/server/dist/project-router.js +4016 -0
- package/server/dist/proposal-manager.js +291 -0
- package/server/dist/provider-selection.js +69 -0
- package/server/dist/providers/claude-adapter.js +281 -0
- package/server/dist/providers/codex-adapter.js +264 -0
- package/server/dist/providers/index.js +23 -0
- package/server/dist/providers/registry.js +37 -0
- package/server/dist/providers/types.js +22 -0
- package/server/dist/queue-manager.js +1511 -0
- package/server/dist/rails-router.js +362 -0
- package/server/dist/rails-store.js +116 -0
- package/server/dist/result-event.js +106 -0
- package/server/dist/schemas/profile.v1.json +151 -0
- package/server/dist/setup-manager.js +1165 -0
- package/server/dist/setup-prerequisites.js +372 -0
- package/server/dist/smash-runner.js +663 -0
- package/server/dist/spec-draft-parser.js +133 -0
- package/server/dist/spec-launcher-manager.js +174 -0
- package/server/dist/spec-models.js +32 -0
- package/server/dist/specrails-tech-client.js +82 -0
- package/server/dist/spending.js +448 -0
- package/server/dist/telemetry-compactor.js +180 -0
- package/server/dist/telemetry-export.js +317 -0
- package/server/dist/telemetry-receiver.js +224 -0
- package/server/dist/terminal-manager.js +633 -0
- package/server/dist/terminal-marks-store.js +117 -0
- package/server/dist/terminal-osc-parser.js +159 -0
- package/server/dist/terminal-settings.js +282 -0
- package/server/dist/terminal-shell-integration.js +196 -0
- package/server/dist/ticket-broadcast.js +47 -0
- package/server/dist/ticket-store.js +397 -0
- package/server/dist/ticket-watcher.js +117 -0
- package/server/dist/types.js +10 -0
- package/server/dist/user-mcp-config.js +117 -0
- package/server/dist/util/cli-prompt.js +181 -0
- package/server/dist/util/secure-fs.js +50 -0
- package/server/dist/util/win-spawn.js +43 -0
- package/server/dist/webhook-manager.js +89 -0
- package/server/dist/ws-routing.js +47 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MobileWsBridge = void 0;
|
|
4
|
+
const mobile_event_bus_1 = require("./mobile-event-bus");
|
|
5
|
+
const mobile_redact_1 = require("./mobile-redact");
|
|
6
|
+
// The gateway's WS fan-out. Subscribes ONCE to the in-process event bus and
|
|
7
|
+
// pushes a mobile-shaped, redacted, per-subscription-filtered stream to each
|
|
8
|
+
// attached socket. The raw /ws firehose (stream-json payloads, 256 KB
|
|
9
|
+
// scrollbacks) is never forwarded verbatim.
|
|
10
|
+
const OPEN = 1;
|
|
11
|
+
const INBOUND_MAX_BYTES = 4096;
|
|
12
|
+
const INBOUND_MAX_PER_MIN = 30;
|
|
13
|
+
const HEARTBEAT_MS = 30_000;
|
|
14
|
+
const LOG_FLUSH_MS = 250;
|
|
15
|
+
const LOG_LINE_MAX = 2048;
|
|
16
|
+
const LOG_BATCH_MAX = 60;
|
|
17
|
+
function topicFor(type) {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'queue': return 'queue';
|
|
20
|
+
case 'phase': return 'phase';
|
|
21
|
+
case 'ticket_created':
|
|
22
|
+
case 'ticket_updated':
|
|
23
|
+
case 'ticket_deleted':
|
|
24
|
+
case 'spec_gen_stream':
|
|
25
|
+
case 'spec_gen_done':
|
|
26
|
+
case 'spec_gen_error': return 'tickets';
|
|
27
|
+
case 'rail.job_started':
|
|
28
|
+
case 'rail.job_stopped':
|
|
29
|
+
case 'rail.job_completed':
|
|
30
|
+
case 'rail.updated': return 'rails';
|
|
31
|
+
case 'spending.invalidated': return 'spending';
|
|
32
|
+
case 'chat_stream':
|
|
33
|
+
case 'chat_done':
|
|
34
|
+
case 'chat_error':
|
|
35
|
+
case 'chat_title_update':
|
|
36
|
+
case 'chat_command_proposal':
|
|
37
|
+
case 'spec_draft.update': return 'chat';
|
|
38
|
+
case 'cost_alert':
|
|
39
|
+
case 'daily_budget_exceeded':
|
|
40
|
+
case 'desktop_daily_budget_exceeded': return 'alerts';
|
|
41
|
+
case 'log':
|
|
42
|
+
case 'event': return 'jobtail';
|
|
43
|
+
case 'desktop.projects':
|
|
44
|
+
case 'desktop.project_added':
|
|
45
|
+
case 'desktop.project_removed': return 'hub'; // mobile-app v1 wire compat — topic name frozen
|
|
46
|
+
default: return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ─── Mobile wire compat ───────────────────────────────────────────────────────
|
|
50
|
+
// mobile-app v1 wire compat — do not rename: the phone app (v1.0.0, in App
|
|
51
|
+
// Review) matches these exact legacy `type` strings (and the legacy budget
|
|
52
|
+
// payload field names). The internal broadcast types were renamed `desktop.*`
|
|
53
|
+
// in the Specrails Desktop rebrand; this outbound boundary translates them
|
|
54
|
+
// back before anything reaches a mobile socket.
|
|
55
|
+
const LEGACY_WIRE_TYPES = {
|
|
56
|
+
'desktop.projects': 'hub.projects',
|
|
57
|
+
'desktop.project_added': 'hub.project_added',
|
|
58
|
+
'desktop.project_removed': 'hub.project_removed',
|
|
59
|
+
desktop_daily_budget_exceeded: 'hub_daily_budget_exceeded',
|
|
60
|
+
};
|
|
61
|
+
function toMobileWire(msg) {
|
|
62
|
+
const legacyType = LEGACY_WIRE_TYPES[msg.type];
|
|
63
|
+
if (!legacyType)
|
|
64
|
+
return msg;
|
|
65
|
+
const out = { ...msg, type: legacyType };
|
|
66
|
+
if (msg.type === 'desktop_daily_budget_exceeded') {
|
|
67
|
+
// mobile-app v1 wire compat — restore the legacy payload field names.
|
|
68
|
+
if ('desktopDailySpend' in out) {
|
|
69
|
+
out.hubDailySpend = out.desktopDailySpend;
|
|
70
|
+
delete out.desktopDailySpend;
|
|
71
|
+
}
|
|
72
|
+
if ('desktopBudget' in out) {
|
|
73
|
+
out.hubBudget = out.desktopBudget;
|
|
74
|
+
delete out.desktopBudget;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
class MobileWsBridge {
|
|
80
|
+
_socks = new Set();
|
|
81
|
+
_unsub = null;
|
|
82
|
+
_flush = null;
|
|
83
|
+
_hb = null;
|
|
84
|
+
_clock;
|
|
85
|
+
constructor(opts = {}) {
|
|
86
|
+
this._clock = opts.clock ?? (() => Date.now());
|
|
87
|
+
}
|
|
88
|
+
start() {
|
|
89
|
+
if (this._unsub)
|
|
90
|
+
return;
|
|
91
|
+
this._unsub = (0, mobile_event_bus_1.getMobileEventBus)().onMessage((msg) => this.dispatch(msg));
|
|
92
|
+
this._flush = setInterval(() => this.flushLogs(), LOG_FLUSH_MS);
|
|
93
|
+
this._hb = setInterval(() => this.heartbeat(), HEARTBEAT_MS);
|
|
94
|
+
// Don't keep the event loop alive purely for these timers.
|
|
95
|
+
this._flush.unref?.();
|
|
96
|
+
this._hb.unref?.();
|
|
97
|
+
}
|
|
98
|
+
stop() {
|
|
99
|
+
if (this._unsub) {
|
|
100
|
+
this._unsub();
|
|
101
|
+
this._unsub = null;
|
|
102
|
+
}
|
|
103
|
+
if (this._flush) {
|
|
104
|
+
clearInterval(this._flush);
|
|
105
|
+
this._flush = null;
|
|
106
|
+
}
|
|
107
|
+
if (this._hb) {
|
|
108
|
+
clearInterval(this._hb);
|
|
109
|
+
this._hb = null;
|
|
110
|
+
}
|
|
111
|
+
for (const s of this._socks) {
|
|
112
|
+
try {
|
|
113
|
+
s.ws.close(1001, 'gateway stopping');
|
|
114
|
+
}
|
|
115
|
+
catch { /* ignore */ }
|
|
116
|
+
}
|
|
117
|
+
this._socks.clear();
|
|
118
|
+
}
|
|
119
|
+
get socketCount() {
|
|
120
|
+
return this._socks.size;
|
|
121
|
+
}
|
|
122
|
+
/** Register a freshly-upgraded socket for an authenticated device. */
|
|
123
|
+
attach(ws, deviceId) {
|
|
124
|
+
const state = {
|
|
125
|
+
ws, deviceId,
|
|
126
|
+
projects: new Set(), topics: new Set(), watchedJob: null,
|
|
127
|
+
inbound: [], alive: true,
|
|
128
|
+
logBuf: new Map(), logDropped: new Map(),
|
|
129
|
+
};
|
|
130
|
+
this._socks.add(state);
|
|
131
|
+
ws.on('message', (...args) => this.onInbound(state, args[0]));
|
|
132
|
+
ws.on('pong', () => { state.alive = true; });
|
|
133
|
+
ws.on('close', () => { this._socks.delete(state); });
|
|
134
|
+
ws.on('error', () => { this._socks.delete(state); });
|
|
135
|
+
}
|
|
136
|
+
/** Close every socket belonging to a (just-revoked) device. */
|
|
137
|
+
closeForDevice(deviceId) {
|
|
138
|
+
for (const s of this._socks) {
|
|
139
|
+
if (s.deviceId === deviceId) {
|
|
140
|
+
try {
|
|
141
|
+
s.ws.close(4401, 'revoked');
|
|
142
|
+
}
|
|
143
|
+
catch { /* ignore */ }
|
|
144
|
+
this._socks.delete(s);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
onInbound(state, raw) {
|
|
149
|
+
const text = typeof raw === 'string' ? raw : Buffer.isBuffer(raw) ? raw.toString('utf8') : '';
|
|
150
|
+
if (Buffer.byteLength(text) > INBOUND_MAX_BYTES) {
|
|
151
|
+
try {
|
|
152
|
+
state.ws.close(1009, 'message too large');
|
|
153
|
+
}
|
|
154
|
+
catch { /* ignore */ }
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const now = this._clock();
|
|
158
|
+
state.inbound = state.inbound.filter((t) => now - t < 60_000);
|
|
159
|
+
state.inbound.push(now);
|
|
160
|
+
if (state.inbound.length > INBOUND_MAX_PER_MIN) {
|
|
161
|
+
try {
|
|
162
|
+
state.ws.close(1008, 'rate limit');
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let msg;
|
|
168
|
+
try {
|
|
169
|
+
msg = JSON.parse(text);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
switch (msg.type) {
|
|
175
|
+
case 'subscribe': {
|
|
176
|
+
const projects = Array.isArray(msg.projects) ? msg.projects.filter((x) => typeof x === 'string') : [];
|
|
177
|
+
const topics = Array.isArray(msg.topics) ? msg.topics.filter((x) => typeof x === 'string') : [];
|
|
178
|
+
state.projects = new Set(projects);
|
|
179
|
+
state.topics = new Set(topics);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case 'watch_job': {
|
|
183
|
+
if (typeof msg.projectId === 'string' && typeof msg.jobId === 'string') {
|
|
184
|
+
state.watchedJob = { projectId: msg.projectId, jobId: msg.jobId };
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'unwatch_job':
|
|
189
|
+
state.watchedJob = null;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Fan a single bus message out to matching sockets. */
|
|
194
|
+
dispatch(msg) {
|
|
195
|
+
const topic = topicFor(msg.type);
|
|
196
|
+
if (!topic)
|
|
197
|
+
return;
|
|
198
|
+
for (const s of this._socks) {
|
|
199
|
+
if (topic === 'hub') {
|
|
200
|
+
// App-level (project registry): always forward, but translate to the
|
|
201
|
+
// legacy mobile wire types and redact (the projects payload carries `path`).
|
|
202
|
+
this.send(s, (0, mobile_redact_1.redact)(toMobileWire(msg)));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const projectId = msg.projectId;
|
|
206
|
+
if (!projectId || !s.projects.has(projectId))
|
|
207
|
+
continue;
|
|
208
|
+
if (topic === 'jobtail') {
|
|
209
|
+
if (!s.watchedJob || s.watchedJob.projectId !== projectId)
|
|
210
|
+
continue;
|
|
211
|
+
const jobId = msg.jobId
|
|
212
|
+
?? msg.processId;
|
|
213
|
+
if (jobId !== s.watchedJob.jobId)
|
|
214
|
+
continue;
|
|
215
|
+
if (msg.type === 'log') {
|
|
216
|
+
this.bufferLog(s, s.watchedJob.jobId, msg.line);
|
|
217
|
+
}
|
|
218
|
+
else if (msg.type === 'event') {
|
|
219
|
+
this.send(s, { type: 'job_event', projectId, jobId, eventType: msg.event_type });
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (s.topics.has(topic)) {
|
|
224
|
+
this.send(s, (0, mobile_redact_1.redact)(toMobileWire(msg)));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
bufferLog(s, jobId, line) {
|
|
229
|
+
const buf = s.logBuf.get(jobId) ?? [];
|
|
230
|
+
if (buf.length >= LOG_BATCH_MAX) {
|
|
231
|
+
s.logDropped.set(jobId, (s.logDropped.get(jobId) ?? 0) + 1);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
buf.push(line.length > LOG_LINE_MAX ? line.slice(0, LOG_LINE_MAX) + '…' : line);
|
|
235
|
+
s.logBuf.set(jobId, buf);
|
|
236
|
+
}
|
|
237
|
+
flushLogs() {
|
|
238
|
+
for (const s of this._socks) {
|
|
239
|
+
for (const [jobId, lines] of s.logBuf) {
|
|
240
|
+
if (lines.length === 0)
|
|
241
|
+
continue;
|
|
242
|
+
const dropped = s.logDropped.get(jobId) ?? 0;
|
|
243
|
+
this.send(s, { type: 'log_batch', jobId, lines, dropped });
|
|
244
|
+
}
|
|
245
|
+
s.logBuf.clear();
|
|
246
|
+
s.logDropped.clear();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
heartbeat() {
|
|
250
|
+
for (const s of this._socks) {
|
|
251
|
+
if (!s.alive) {
|
|
252
|
+
try {
|
|
253
|
+
s.ws.terminate();
|
|
254
|
+
}
|
|
255
|
+
catch { /* ignore */ }
|
|
256
|
+
this._socks.delete(s);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
s.alive = false;
|
|
260
|
+
try {
|
|
261
|
+
s.ws.ping();
|
|
262
|
+
}
|
|
263
|
+
catch { /* ignore */ }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
send(s, obj) {
|
|
267
|
+
if (s.ws.readyState !== OPEN)
|
|
268
|
+
return;
|
|
269
|
+
try {
|
|
270
|
+
s.ws.send(JSON.stringify(obj));
|
|
271
|
+
}
|
|
272
|
+
catch { /* ignore */ }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.MobileWsBridge = MobileWsBridge;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveBundledRuntimePath = resolveBundledRuntimePath;
|
|
7
|
+
exports.resolveStartupPath = resolveStartupPath;
|
|
8
|
+
exports.parseLoginShellOutput = parseLoginShellOutput;
|
|
9
|
+
exports.augmentPathFromLoginShell = augmentPathFromLoginShell;
|
|
10
|
+
exports.getPathDiagnostic = getPathDiagnostic;
|
|
11
|
+
exports.__resetPathResolverForTest = __resetPathResolverForTest;
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const PATH_BEGIN = '__SRH_PATH_BEGIN__';
|
|
17
|
+
const PATH_END = '__SRH_PATH_END__';
|
|
18
|
+
const LOGIN_SHELL_TIMEOUT_MS = 1500;
|
|
19
|
+
let diagnostic = {
|
|
20
|
+
pathSegments: [],
|
|
21
|
+
pathSources: [],
|
|
22
|
+
loginShellStatus: 'skipped',
|
|
23
|
+
};
|
|
24
|
+
let warnedLoginShell = false;
|
|
25
|
+
/**
|
|
26
|
+
* True once `resolveStartupPath()` has actually prepended at least one bundled
|
|
27
|
+
* runtime dir that exists on disk. Gates the login-shell no-op: when a desktop
|
|
28
|
+
* build ships no runtimes (e.g. Windows ARM64, or a partial CI extraction) the
|
|
29
|
+
* bundle is NOT active, so we fall back to system discovery + login-shell
|
|
30
|
+
* augmentation instead of going dark.
|
|
31
|
+
*/
|
|
32
|
+
let bundledRuntimesActive = false;
|
|
33
|
+
function fileExists(p) {
|
|
34
|
+
try {
|
|
35
|
+
return fs_1.default.existsSync(p);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the bin directory for each bundled tool family from the actual binary
|
|
43
|
+
* FILE (not just the directory), returning the dir to prepend or `null`. Keeping
|
|
44
|
+
* this file-level and symmetric with setup-prerequisites means "bundle active"
|
|
45
|
+
* means the same thing in both modules.
|
|
46
|
+
*/
|
|
47
|
+
function resolveBundledBinDirs(runtimesPath) {
|
|
48
|
+
const isWin = process.platform === 'win32';
|
|
49
|
+
const nodeBinDir = isWin
|
|
50
|
+
? (fileExists(path_1.default.join(runtimesPath, 'node', 'node.exe')) ? path_1.default.join(runtimesPath, 'node') : null)
|
|
51
|
+
: (fileExists(path_1.default.join(runtimesPath, 'node', 'bin', 'node')) ? path_1.default.join(runtimesPath, 'node', 'bin') : null);
|
|
52
|
+
let gitBinDir = null;
|
|
53
|
+
if (isWin) {
|
|
54
|
+
// PortableGit ships the real binary at git/cmd/git.exe with a redirector at git/bin/git.exe.
|
|
55
|
+
if (fileExists(path_1.default.join(runtimesPath, 'git', 'cmd', 'git.exe')))
|
|
56
|
+
gitBinDir = path_1.default.join(runtimesPath, 'git', 'cmd');
|
|
57
|
+
else if (fileExists(path_1.default.join(runtimesPath, 'git', 'bin', 'git.exe')))
|
|
58
|
+
gitBinDir = path_1.default.join(runtimesPath, 'git', 'bin');
|
|
59
|
+
}
|
|
60
|
+
else if (fileExists(path_1.default.join(runtimesPath, 'git', 'bin', 'git'))) {
|
|
61
|
+
gitBinDir = path_1.default.join(runtimesPath, 'git', 'bin');
|
|
62
|
+
}
|
|
63
|
+
return { nodeBinDir, gitBinDir };
|
|
64
|
+
}
|
|
65
|
+
function getDelimiter() {
|
|
66
|
+
return process.platform === 'win32' ? ';' : ':';
|
|
67
|
+
}
|
|
68
|
+
function splitPath(value) {
|
|
69
|
+
if (!value)
|
|
70
|
+
return [];
|
|
71
|
+
return value.split(getDelimiter()).filter((s) => s.length > 0);
|
|
72
|
+
}
|
|
73
|
+
function joinPath(segments) {
|
|
74
|
+
return segments.join(getDelimiter());
|
|
75
|
+
}
|
|
76
|
+
function fastPathDirectories() {
|
|
77
|
+
if (process.platform === 'darwin') {
|
|
78
|
+
return ['/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin', '/usr/local/sbin'];
|
|
79
|
+
}
|
|
80
|
+
if (process.platform === 'linux') {
|
|
81
|
+
return ['/usr/local/bin', '/usr/local/sbin', path_1.default.join(os_1.default.homedir(), '.local/bin')];
|
|
82
|
+
}
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns the absolute path to the bundled runtimes directory.
|
|
87
|
+
* Only valid when SPECRAILS_IS_DESKTOP=1 and SPECRAILS_BUNDLED_RUNTIMES_PATH is set.
|
|
88
|
+
* Throws if the env var is missing.
|
|
89
|
+
*/
|
|
90
|
+
function resolveBundledRuntimePath() {
|
|
91
|
+
const p = process.env.SPECRAILS_BUNDLED_RUNTIMES_PATH;
|
|
92
|
+
if (!p) {
|
|
93
|
+
throw new Error('[path-resolver] resolveBundledRuntimePath() called but SPECRAILS_BUNDLED_RUNTIMES_PATH is not set');
|
|
94
|
+
}
|
|
95
|
+
return p;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Synchronously prepend well-known package-manager bin directories to
|
|
99
|
+
* `process.env.PATH` if they are missing. No-op on Windows.
|
|
100
|
+
*
|
|
101
|
+
* Records the resulting segments and their sources for diagnostic reporting.
|
|
102
|
+
*/
|
|
103
|
+
function resolveStartupPath() {
|
|
104
|
+
// Desktop mode: bundled runtimes win when present. We existence-gate every
|
|
105
|
+
// candidate dir so a runtimes-less or partially-extracted build degrades to
|
|
106
|
+
// normal system PATH discovery instead of prepending dead dirs and disabling
|
|
107
|
+
// all fallback (which would dead-end Add Project with "corrupted-bundle").
|
|
108
|
+
if (process.env.SPECRAILS_IS_DESKTOP === '1') {
|
|
109
|
+
const runtimesPath = process.env.SPECRAILS_BUNDLED_RUNTIMES_PATH;
|
|
110
|
+
if (runtimesPath) {
|
|
111
|
+
const { nodeBinDir, gitBinDir } = resolveBundledBinDirs(runtimesPath);
|
|
112
|
+
// Activate the bundle only when BOTH node and git are present. A partial
|
|
113
|
+
// bundle (one tool present, the other missing — a botched extraction) is
|
|
114
|
+
// treated as NOT active so the full system fallback (fast-path + login-shell)
|
|
115
|
+
// runs for every tool. Otherwise the missing tool would fall through to a
|
|
116
|
+
// system probe against an un-augmented PATH and be wrongly reported missing.
|
|
117
|
+
if (nodeBinDir && gitBinDir) {
|
|
118
|
+
const inherited = splitPath(process.env.PATH);
|
|
119
|
+
const inheritedSet = new Set(inherited);
|
|
120
|
+
const toAdd = [nodeBinDir, gitBinDir].filter((d) => !inheritedSet.has(d));
|
|
121
|
+
const merged = [...toAdd, ...inherited];
|
|
122
|
+
process.env.PATH = joinPath(merged);
|
|
123
|
+
bundledRuntimesActive = true;
|
|
124
|
+
diagnostic = {
|
|
125
|
+
pathSegments: merged,
|
|
126
|
+
pathSources: [
|
|
127
|
+
...toAdd.map(() => 'bundled'),
|
|
128
|
+
...inherited.map(() => 'inherited'),
|
|
129
|
+
],
|
|
130
|
+
loginShellStatus: 'skipped',
|
|
131
|
+
};
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Bundle absent or incomplete → fall through to system PATH discovery below
|
|
135
|
+
// so system node/git still resolve (graceful fallback).
|
|
136
|
+
}
|
|
137
|
+
// No runtimes path, or no/partial bundle present: fall through (do NOT return).
|
|
138
|
+
}
|
|
139
|
+
const inherited = splitPath(process.env.PATH);
|
|
140
|
+
const inheritedSet = new Set(inherited);
|
|
141
|
+
if (process.platform === 'win32') {
|
|
142
|
+
diagnostic = {
|
|
143
|
+
pathSegments: inherited,
|
|
144
|
+
pathSources: inherited.map(() => 'inherited'),
|
|
145
|
+
loginShellStatus: 'skipped',
|
|
146
|
+
};
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const toPrepend = [];
|
|
150
|
+
for (const dir of fastPathDirectories()) {
|
|
151
|
+
if (!inheritedSet.has(dir)) {
|
|
152
|
+
toPrepend.push(dir);
|
|
153
|
+
inheritedSet.add(dir);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const merged = [...toPrepend, ...inherited];
|
|
157
|
+
process.env.PATH = joinPath(merged);
|
|
158
|
+
diagnostic = {
|
|
159
|
+
pathSegments: merged,
|
|
160
|
+
pathSources: [
|
|
161
|
+
...toPrepend.map(() => 'fast-path'),
|
|
162
|
+
...inherited.map(() => 'inherited'),
|
|
163
|
+
],
|
|
164
|
+
loginShellStatus: 'skipped',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Parse stdout from the login-shell probe. Returns the PATH between sentinel
|
|
169
|
+
* markers, or `null` if the markers are not present.
|
|
170
|
+
*/
|
|
171
|
+
function parseLoginShellOutput(stdout) {
|
|
172
|
+
const begin = stdout.indexOf(PATH_BEGIN);
|
|
173
|
+
if (begin === -1)
|
|
174
|
+
return null;
|
|
175
|
+
const start = begin + PATH_BEGIN.length;
|
|
176
|
+
const end = stdout.indexOf(PATH_END, start);
|
|
177
|
+
if (end === -1)
|
|
178
|
+
return null;
|
|
179
|
+
return stdout.slice(start, end);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Spawn the user's login shell once and merge any additional PATH segments
|
|
183
|
+
* it exposes (Volta/nvm/fnm/asdf shims) into `process.env.PATH`. Async,
|
|
184
|
+
* fire-and-forget — must not block startup.
|
|
185
|
+
*
|
|
186
|
+
* No-op on Windows and in test environments.
|
|
187
|
+
*/
|
|
188
|
+
async function augmentPathFromLoginShell(opts = {}) {
|
|
189
|
+
if (process.platform === 'win32') {
|
|
190
|
+
diagnostic.loginShellStatus = 'skipped';
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true') {
|
|
194
|
+
diagnostic.loginShellStatus = 'skipped';
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Desktop mode WITH an active bundle: login-shell augmentation must never run
|
|
198
|
+
// — it could prepend system node/git dirs ahead of bundled ones. But when the
|
|
199
|
+
// bundle is absent (runtimes-less build → system fallback), we DO want
|
|
200
|
+
// login-shell augmentation so nvm/volta/fnm shims are discovered.
|
|
201
|
+
if (process.env.SPECRAILS_IS_DESKTOP === '1' && bundledRuntimesActive) {
|
|
202
|
+
diagnostic.loginShellStatus = 'skipped';
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const spawnFn = opts.spawnFn ?? child_process_1.spawn;
|
|
206
|
+
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
207
|
+
const shell = process.env.SHELL || '/bin/sh';
|
|
208
|
+
const command = `printf "${PATH_BEGIN}%s${PATH_END}" "$PATH"`;
|
|
209
|
+
const status = await new Promise((resolve) => {
|
|
210
|
+
let child;
|
|
211
|
+
try {
|
|
212
|
+
child = spawnFn(shell, ['-l', '-i', '-c', command], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
resolve('error');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
let stdout = '';
|
|
219
|
+
let timedOut = false;
|
|
220
|
+
let settled = false;
|
|
221
|
+
const timer = setTimeout(() => {
|
|
222
|
+
timedOut = true;
|
|
223
|
+
try {
|
|
224
|
+
child.kill('SIGKILL');
|
|
225
|
+
}
|
|
226
|
+
catch { /* ignore */ }
|
|
227
|
+
}, timeoutMs);
|
|
228
|
+
child.stdout?.on('data', (chunk) => { stdout += chunk.toString('utf-8'); });
|
|
229
|
+
child.stderr?.on('data', () => { });
|
|
230
|
+
const finish = (s) => {
|
|
231
|
+
if (settled)
|
|
232
|
+
return;
|
|
233
|
+
settled = true;
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
resolve(s);
|
|
236
|
+
};
|
|
237
|
+
child.on('error', () => finish('error'));
|
|
238
|
+
child.on('close', (code) => {
|
|
239
|
+
if (timedOut) {
|
|
240
|
+
finish('timeout');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (code !== 0) {
|
|
244
|
+
finish('error');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const parsed = parseLoginShellOutput(stdout);
|
|
248
|
+
if (parsed === null) {
|
|
249
|
+
finish('error');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
mergeLoginShellPath(parsed);
|
|
253
|
+
finish('ok');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
diagnostic.loginShellStatus = status;
|
|
257
|
+
if (status !== 'ok' && !warnedLoginShell) {
|
|
258
|
+
warnedLoginShell = true;
|
|
259
|
+
console.warn(`[path-resolver] login-shell merge ${status}; using fast-path PATH only`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function mergeLoginShellPath(rawPath) {
|
|
263
|
+
const current = splitPath(process.env.PATH);
|
|
264
|
+
const currentSet = new Set(current);
|
|
265
|
+
const incoming = splitPath(rawPath);
|
|
266
|
+
const additions = [];
|
|
267
|
+
for (const dir of incoming) {
|
|
268
|
+
if (!currentSet.has(dir)) {
|
|
269
|
+
additions.push(dir);
|
|
270
|
+
currentSet.add(dir);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (additions.length === 0)
|
|
274
|
+
return;
|
|
275
|
+
const merged = [...additions, ...current];
|
|
276
|
+
process.env.PATH = joinPath(merged);
|
|
277
|
+
diagnostic = {
|
|
278
|
+
pathSegments: merged,
|
|
279
|
+
pathSources: [
|
|
280
|
+
...additions.map(() => 'login-shell'),
|
|
281
|
+
...diagnostic.pathSources,
|
|
282
|
+
],
|
|
283
|
+
loginShellStatus: diagnostic.loginShellStatus,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function getPathDiagnostic() {
|
|
287
|
+
return {
|
|
288
|
+
pathSegments: [...diagnostic.pathSegments],
|
|
289
|
+
pathSources: [...diagnostic.pathSources],
|
|
290
|
+
loginShellStatus: diagnostic.loginShellStatus,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/** Test-only helper to reset module state. */
|
|
294
|
+
function __resetPathResolverForTest() {
|
|
295
|
+
diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
|
|
296
|
+
warnedLoginShell = false;
|
|
297
|
+
bundledRuntimesActive = false;
|
|
298
|
+
}
|