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,117 @@
|
|
|
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.TicketWatcher = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const chokidar_1 = require("chokidar");
|
|
10
|
+
const TICKET_FILE = '.specrails/local-tickets.json';
|
|
11
|
+
const DEBOUNCE_MS = 150;
|
|
12
|
+
/**
|
|
13
|
+
* Watches `.specrails/local-tickets.json` for external changes and broadcasts
|
|
14
|
+
* ticket_updated via WebSocket. One instance per project context.
|
|
15
|
+
*/
|
|
16
|
+
class TicketWatcher {
|
|
17
|
+
_watcher = null;
|
|
18
|
+
_debounceTimer = null;
|
|
19
|
+
_projectPath;
|
|
20
|
+
_projectId;
|
|
21
|
+
_broadcast;
|
|
22
|
+
_lastRevision = null;
|
|
23
|
+
_closed = false;
|
|
24
|
+
constructor(projectPath, projectId, broadcast) {
|
|
25
|
+
this._projectPath = projectPath;
|
|
26
|
+
this._projectId = projectId;
|
|
27
|
+
this._broadcast = broadcast;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Start watching the ticket file. Safe to call if file doesn't exist yet —
|
|
31
|
+
* chokidar will detect when it's created.
|
|
32
|
+
*/
|
|
33
|
+
start() {
|
|
34
|
+
if (this._closed)
|
|
35
|
+
return;
|
|
36
|
+
const filePath = path_1.default.join(this._projectPath, TICKET_FILE);
|
|
37
|
+
// Seed initial revision so we can detect external changes
|
|
38
|
+
this._lastRevision = this._readRevision(filePath);
|
|
39
|
+
this._watcher = new chokidar_1.FSWatcher({
|
|
40
|
+
persistent: false, // don't keep the process alive
|
|
41
|
+
ignoreInitial: true,
|
|
42
|
+
// awaitWriteFinish helps with rapid writes from CLI agents
|
|
43
|
+
awaitWriteFinish: {
|
|
44
|
+
stabilityThreshold: 80,
|
|
45
|
+
pollInterval: 50,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
this._watcher.add(filePath);
|
|
49
|
+
this._watcher.on('change', () => this._onFileChange(filePath));
|
|
50
|
+
this._watcher.on('add', () => this._onFileChange(filePath));
|
|
51
|
+
this._watcher.on('error', (err) => {
|
|
52
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
53
|
+
console.error(`[ticket-watcher] error for project ${this._projectId}:`, msg);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stop watching and clean up. Safe to call multiple times.
|
|
58
|
+
*/
|
|
59
|
+
async close() {
|
|
60
|
+
this._closed = true;
|
|
61
|
+
if (this._debounceTimer) {
|
|
62
|
+
clearTimeout(this._debounceTimer);
|
|
63
|
+
this._debounceTimer = null;
|
|
64
|
+
}
|
|
65
|
+
if (this._watcher) {
|
|
66
|
+
await this._watcher.close();
|
|
67
|
+
this._watcher = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Notify the watcher that the app itself just wrote to the file,
|
|
72
|
+
* so the next file-change event should be skipped (avoids echo).
|
|
73
|
+
* Call this from ticket API mutation handlers.
|
|
74
|
+
*/
|
|
75
|
+
notifyDesktopWrite(newRevision) {
|
|
76
|
+
this._lastRevision = newRevision;
|
|
77
|
+
}
|
|
78
|
+
_onFileChange(filePath) {
|
|
79
|
+
if (this._debounceTimer) {
|
|
80
|
+
clearTimeout(this._debounceTimer);
|
|
81
|
+
}
|
|
82
|
+
this._debounceTimer = setTimeout(() => {
|
|
83
|
+
this._debounceTimer = null;
|
|
84
|
+
this._handleChange(filePath);
|
|
85
|
+
}, DEBOUNCE_MS);
|
|
86
|
+
}
|
|
87
|
+
_handleChange(filePath) {
|
|
88
|
+
const revision = this._readRevision(filePath);
|
|
89
|
+
if (revision === null)
|
|
90
|
+
return; // file unreadable or malformed
|
|
91
|
+
// Skip if revision hasn't changed (app's own write)
|
|
92
|
+
if (this._lastRevision !== null && revision === this._lastRevision)
|
|
93
|
+
return;
|
|
94
|
+
this._lastRevision = revision;
|
|
95
|
+
// Broadcast a generic ticket_updated with the full ticket set so the
|
|
96
|
+
// client can diff. We use a synthetic ticket with id 0 to signal
|
|
97
|
+
// "full refresh" — this is simpler than diffing individual tickets
|
|
98
|
+
// and the client will refetch via API anyway.
|
|
99
|
+
this._broadcast({
|
|
100
|
+
type: 'ticket_updated',
|
|
101
|
+
projectId: this._projectId,
|
|
102
|
+
ticket: { id: 0 },
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
_readRevision(filePath) {
|
|
107
|
+
try {
|
|
108
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
109
|
+
const data = JSON.parse(raw);
|
|
110
|
+
return typeof data.revision === 'number' ? data.revision : null;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.TicketWatcher = TicketWatcher;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VALID_PRIORITIES = exports.PRIORITY_WEIGHT = void 0;
|
|
4
|
+
exports.PRIORITY_WEIGHT = {
|
|
5
|
+
low: 0,
|
|
6
|
+
normal: 1,
|
|
7
|
+
high: 2,
|
|
8
|
+
critical: 3,
|
|
9
|
+
};
|
|
10
|
+
exports.VALID_PRIORITIES = new Set(['low', 'normal', 'high', 'critical']);
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// User-approved MCP injection for Explore Spec turns.
|
|
3
|
+
//
|
|
4
|
+
// When a conversation's ContextScope has `userMcp: true`, the app makes the
|
|
5
|
+
// user's OWN already-approved MCP servers available to the spawned CLI — the
|
|
6
|
+
// ones they registered locally with `claude mcp add` / `codex mcp add`, NOT the
|
|
7
|
+
// project-committed `.mcp.json` (that is the separate `mcp` toggle).
|
|
8
|
+
//
|
|
9
|
+
// Provider behaviour:
|
|
10
|
+
// - claude: read `~/.claude.json` and collect the user-scope (top-level
|
|
11
|
+
// `mcpServers`) and project-local-scope (`projects[<projectPath>].mcpServers`)
|
|
12
|
+
// server definitions, merge them (local wins on key conflict, mirroring
|
|
13
|
+
// claude's own scope precedence), write a `{ "mcpServers": {…} }` file and
|
|
14
|
+
// return `['--mcp-config', <file>]`. The CLI loads `--mcp-config` ADDITIVELY
|
|
15
|
+
// on top of whatever else it discovers (no `--strict-mcp-config`), and the
|
|
16
|
+
// existing `--dangerously-skip-permissions` flag means the tools are
|
|
17
|
+
// callable without an approval prompt. Verified e2e against claude 2.1.169.
|
|
18
|
+
// - codex: returns `[]`. codex chat turns spawn with `env: process.env` and no
|
|
19
|
+
// `CODEX_HOME` override, so codex already reads the user's global
|
|
20
|
+
// `~/.codex/config.toml` MCP servers natively — no injection needed.
|
|
21
|
+
//
|
|
22
|
+
// The spawn cwd is intentionally left unchanged (the Explore latency win of the
|
|
23
|
+
// app-managed `explore-cwd/` is preserved); `--mcp-config` carries an absolute
|
|
24
|
+
// path so cwd is irrelevant.
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.readUserClaudeMcpServers = readUserClaudeMcpServers;
|
|
30
|
+
exports.writeUserMcpConfig = writeUserMcpConfig;
|
|
31
|
+
exports.buildUserMcpArgs = buildUserMcpArgs;
|
|
32
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
33
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
34
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
35
|
+
/**
|
|
36
|
+
* Read the user's approved claude MCP servers from `~/.claude.json`:
|
|
37
|
+
* user-scope (top-level `mcpServers`) merged with the project's local-scope
|
|
38
|
+
* (`projects[projectPath].mcpServers`). Local scope overrides user scope on a
|
|
39
|
+
* key conflict, matching claude's documented precedence (local > user).
|
|
40
|
+
*
|
|
41
|
+
* Returns `{}` when the file is missing, unparseable, or has no servers. Never
|
|
42
|
+
* throws — a malformed user config must not break the chat turn.
|
|
43
|
+
*
|
|
44
|
+
* @param projectPath absolute path of the project (the local-scope key)
|
|
45
|
+
* @param homeDir override the home directory (tests)
|
|
46
|
+
*/
|
|
47
|
+
function readUserClaudeMcpServers(projectPath, homeDir) {
|
|
48
|
+
const home = homeDir ?? node_os_1.default.homedir();
|
|
49
|
+
const configPath = node_path_1.default.join(home, '.claude.json');
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const userScope = parsed && typeof parsed.mcpServers === 'object' && parsed.mcpServers
|
|
65
|
+
? parsed.mcpServers
|
|
66
|
+
: {};
|
|
67
|
+
const projectEntry = parsed && typeof parsed.projects === 'object' && parsed.projects
|
|
68
|
+
? parsed.projects[projectPath]
|
|
69
|
+
: undefined;
|
|
70
|
+
const localScope = projectEntry && typeof projectEntry.mcpServers === 'object' && projectEntry.mcpServers
|
|
71
|
+
? projectEntry.mcpServers
|
|
72
|
+
: {};
|
|
73
|
+
return { ...userScope, ...localScope };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Write the merged server map to a `{ "mcpServers": {…} }` file under the
|
|
77
|
+
* project's data directory and return its absolute path. chmod 600 because
|
|
78
|
+
* server `env` blocks can carry secrets (same trust domain as `~/.claude.json`).
|
|
79
|
+
*
|
|
80
|
+
* @param baseDir override `~/.specrails/projects` (tests)
|
|
81
|
+
*/
|
|
82
|
+
function writeUserMcpConfig(servers, slug, baseDir) {
|
|
83
|
+
// Defence-in-depth: slug is always DB-sourced and slugified upstream
|
|
84
|
+
// (`/^[a-z0-9-]+/`), but guard the filesystem write directly so a future
|
|
85
|
+
// caller passing untrusted input cannot escape the projects directory.
|
|
86
|
+
// buildUserMcpArgs() swallows the throw and falls back to no injection.
|
|
87
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
|
|
88
|
+
throw new Error(`unsafe project slug for user-mcp config: ${JSON.stringify(slug)}`);
|
|
89
|
+
}
|
|
90
|
+
const base = baseDir ?? node_path_1.default.join(node_os_1.default.homedir(), '.specrails', 'projects');
|
|
91
|
+
const dir = node_path_1.default.join(base, slug);
|
|
92
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
93
|
+
const file = node_path_1.default.join(dir, 'user-mcp.json');
|
|
94
|
+
node_fs_1.default.writeFileSync(file, JSON.stringify({ mcpServers: servers }, null, 2), { mode: 0o600 });
|
|
95
|
+
return file;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build the extra CLI args that load the user's approved MCP servers, or `[]`
|
|
99
|
+
* when there is nothing to inject (non-claude provider, or no user/local
|
|
100
|
+
* servers configured). Never throws.
|
|
101
|
+
*/
|
|
102
|
+
function buildUserMcpArgs(input) {
|
|
103
|
+
// codex reads ~/.codex natively; only claude needs explicit injection.
|
|
104
|
+
if (input.adapterId !== 'claude')
|
|
105
|
+
return [];
|
|
106
|
+
try {
|
|
107
|
+
const servers = readUserClaudeMcpServers(input.projectPath, input.homeDir);
|
|
108
|
+
if (Object.keys(servers).length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const file = writeUserMcpConfig(servers, input.slug, input.baseDir);
|
|
111
|
+
return ['--mcp-config', file];
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error('[user-mcp-config] failed to build --mcp-config args:', err);
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Centralized claude/codex spawn wrapper.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists:
|
|
5
|
+
//
|
|
6
|
+
// On Windows, cross-spawn invokes claude.cmd / codex.cmd through
|
|
7
|
+
// `cmd.exe /d /s /c "..."`. cmd.exe does NOT preserve newlines
|
|
8
|
+
// inside argv values: any `\n` in `--system-prompt`,
|
|
9
|
+
// `--append-system-prompt`, `-p`, or codex's positional prompt
|
|
10
|
+
// truncates the arg there and the rest of the command line gets
|
|
11
|
+
// reparsed as orphan tokens. Visible symptoms include
|
|
12
|
+
// "Input must be provided either through stdin or as a prompt
|
|
13
|
+
// argument when using --print" and assistant messages that look
|
|
14
|
+
// like "your message got cut off — you wrote 'are' but I'm not
|
|
15
|
+
// sure what you were asking".
|
|
16
|
+
//
|
|
17
|
+
// On POSIX argv passes through cleanly, so we keep that path.
|
|
18
|
+
//
|
|
19
|
+
// The helpers below detect multi-line argv values on Windows,
|
|
20
|
+
// reroute them through child stdin (claude reads stdin when
|
|
21
|
+
// `-p`/`--print` has no positional argument; codex `exec -` does the
|
|
22
|
+
// equivalent), and call spawnCli. POSIX is unchanged byte-for-byte.
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.transformClaudeArgsForWindows = transformClaudeArgsForWindows;
|
|
25
|
+
exports.transformCodexArgsForWindows = transformCodexArgsForWindows;
|
|
26
|
+
exports.ensureStdinPipe = ensureStdinPipe;
|
|
27
|
+
exports.spawnClaude = spawnClaude;
|
|
28
|
+
exports.spawnCodex = spawnCodex;
|
|
29
|
+
exports.spawnAiCli = spawnAiCli;
|
|
30
|
+
const win_spawn_1 = require("./win-spawn");
|
|
31
|
+
const isWin = process.platform === 'win32';
|
|
32
|
+
const CLAUDE_PROMPT_FLAGS = new Set([
|
|
33
|
+
'--system-prompt',
|
|
34
|
+
'--append-system-prompt',
|
|
35
|
+
'-p',
|
|
36
|
+
'--print',
|
|
37
|
+
]);
|
|
38
|
+
function transformClaudeArgsForWindows(args) {
|
|
39
|
+
const collected = [];
|
|
40
|
+
const out = [];
|
|
41
|
+
for (let i = 0; i < args.length; i++) {
|
|
42
|
+
const a = args[i];
|
|
43
|
+
if (CLAUDE_PROMPT_FLAGS.has(a) && i + 1 < args.length) {
|
|
44
|
+
collected.push(args[i + 1]);
|
|
45
|
+
i++; // skip the value
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
out.push(a);
|
|
49
|
+
}
|
|
50
|
+
if (collected.length === 0) {
|
|
51
|
+
return { args: out, stdinPayload: null };
|
|
52
|
+
}
|
|
53
|
+
// Re-add `-p` so claude knows to read stdin (--print mode).
|
|
54
|
+
out.push('-p');
|
|
55
|
+
return { args: out, stdinPayload: collected.join('\n\n---\n\n') };
|
|
56
|
+
}
|
|
57
|
+
// Codex `exec` flags we currently use that take a value (rest are
|
|
58
|
+
// boolean). Update if we ever pass new value-bearing flags.
|
|
59
|
+
const CODEX_EXEC_VALUE_FLAGS = new Set(['--model', '--sandbox', '-c']);
|
|
60
|
+
function transformCodexArgsForWindows(args) {
|
|
61
|
+
// Expected shapes:
|
|
62
|
+
// exec [...flags] <prompt> [...flags]
|
|
63
|
+
// exec resume [...flags] <sessionId> <prompt> [...flags]
|
|
64
|
+
if (args.length === 0 || args[0] !== 'exec') {
|
|
65
|
+
return { args, stdinPayload: null };
|
|
66
|
+
}
|
|
67
|
+
const out = ['exec'];
|
|
68
|
+
const isResume = args[1] === 'resume';
|
|
69
|
+
if (isResume)
|
|
70
|
+
out.push('resume');
|
|
71
|
+
let stdin = null;
|
|
72
|
+
let promptReplacedIdx = -1;
|
|
73
|
+
let positionalCount = 0;
|
|
74
|
+
let i = isResume ? 2 : 1;
|
|
75
|
+
while (i < args.length) {
|
|
76
|
+
const a = args[i];
|
|
77
|
+
if (a.startsWith('-') && a !== '-') {
|
|
78
|
+
out.push(a);
|
|
79
|
+
if (CODEX_EXEC_VALUE_FLAGS.has(a) && i + 1 < args.length) {
|
|
80
|
+
out.push(args[i + 1]);
|
|
81
|
+
i += 2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
i += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
positionalCount += 1;
|
|
88
|
+
const isPrompt = isResume ? positionalCount === 2 : positionalCount === 1;
|
|
89
|
+
if (isPrompt && stdin === null) {
|
|
90
|
+
stdin = a;
|
|
91
|
+
promptReplacedIdx = out.length;
|
|
92
|
+
out.push('-');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
out.push(a);
|
|
96
|
+
}
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
if (stdin === null || !stdin.includes('\n')) {
|
|
100
|
+
// Single-line prompts pass through cmd.exe fine — keep argv to
|
|
101
|
+
// dodge any codex versions that don't recognise `-` as stdin.
|
|
102
|
+
if (stdin !== null && promptReplacedIdx >= 0) {
|
|
103
|
+
out[promptReplacedIdx] = stdin;
|
|
104
|
+
}
|
|
105
|
+
return { args: out, stdinPayload: null };
|
|
106
|
+
}
|
|
107
|
+
return { args: out, stdinPayload: stdin };
|
|
108
|
+
}
|
|
109
|
+
function ensureStdinPipe(stdio) {
|
|
110
|
+
const fallback = ['pipe', 'pipe', 'pipe'];
|
|
111
|
+
if (stdio === undefined)
|
|
112
|
+
return fallback;
|
|
113
|
+
if (typeof stdio === 'string') {
|
|
114
|
+
// 'pipe' | 'inherit' | 'ignore' | 'overlapped'
|
|
115
|
+
return ['pipe', stdio, stdio];
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(stdio)) {
|
|
118
|
+
return [
|
|
119
|
+
stdio[0] === 'ignore' ? 'pipe' : (stdio[0] ?? 'pipe'),
|
|
120
|
+
stdio[1] ?? 'pipe',
|
|
121
|
+
stdio[2] ?? 'pipe',
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Spawn `claude` with arg-rewrite on Windows so multi-line prompts
|
|
128
|
+
* survive. POSIX call is identical to `spawnCli('claude', args, options)`.
|
|
129
|
+
*/
|
|
130
|
+
function spawnClaude(args, options = {}) {
|
|
131
|
+
if (!isWin) {
|
|
132
|
+
return (0, win_spawn_1.spawnCli)('claude', args, options);
|
|
133
|
+
}
|
|
134
|
+
/* c8 ignore start -- Windows-only branch; coverage runs on Linux/macOS */
|
|
135
|
+
const { args: winArgs, stdinPayload } = transformClaudeArgsForWindows(args);
|
|
136
|
+
if (stdinPayload === null) {
|
|
137
|
+
return (0, win_spawn_1.spawnCli)('claude', winArgs, options);
|
|
138
|
+
}
|
|
139
|
+
const child = (0, win_spawn_1.spawnCli)('claude', winArgs, {
|
|
140
|
+
...options,
|
|
141
|
+
stdio: ensureStdinPipe(options.stdio),
|
|
142
|
+
});
|
|
143
|
+
if (child.stdin)
|
|
144
|
+
child.stdin.end(stdinPayload);
|
|
145
|
+
return child;
|
|
146
|
+
/* c8 ignore stop */
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Spawn `codex` with arg-rewrite on Windows so multi-line prompts
|
|
150
|
+
* survive. POSIX call is identical to `spawnCli('codex', args, options)`.
|
|
151
|
+
*/
|
|
152
|
+
function spawnCodex(args, options = {}) {
|
|
153
|
+
if (!isWin) {
|
|
154
|
+
return (0, win_spawn_1.spawnCli)('codex', args, options);
|
|
155
|
+
}
|
|
156
|
+
/* c8 ignore start -- Windows-only branch; coverage runs on Linux/macOS */
|
|
157
|
+
const { args: winArgs, stdinPayload } = transformCodexArgsForWindows(args);
|
|
158
|
+
if (stdinPayload === null) {
|
|
159
|
+
return (0, win_spawn_1.spawnCli)('codex', winArgs, options);
|
|
160
|
+
}
|
|
161
|
+
const child = (0, win_spawn_1.spawnCli)('codex', winArgs, {
|
|
162
|
+
...options,
|
|
163
|
+
stdio: ensureStdinPipe(options.stdio),
|
|
164
|
+
});
|
|
165
|
+
if (child.stdin)
|
|
166
|
+
child.stdin.end(stdinPayload);
|
|
167
|
+
return child;
|
|
168
|
+
/* c8 ignore stop */
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Convenience: dispatch on binary name. Use when callsite picks the
|
|
172
|
+
* binary dynamically (claude vs codex). Anything else routes through
|
|
173
|
+
* the underlying spawnCli unchanged.
|
|
174
|
+
*/
|
|
175
|
+
function spawnAiCli(binary, args, options = {}) {
|
|
176
|
+
if (binary === 'claude')
|
|
177
|
+
return spawnClaude(args, options);
|
|
178
|
+
if (binary === 'codex')
|
|
179
|
+
return spawnCodex(args, options);
|
|
180
|
+
return (0, win_spawn_1.spawnCli)(binary, args, options);
|
|
181
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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.secureDir = secureDir;
|
|
7
|
+
exports.secureDbFile = secureDbFile;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
/**
|
|
10
|
+
* Best-effort filesystem hardening for the ~/.specrails data stores (H-13).
|
|
11
|
+
*
|
|
12
|
+
* The auth token is deliberately persisted 0600, but the SQLite databases were
|
|
13
|
+
* created with the default umask (typically dir 0755 / file 0644), leaving
|
|
14
|
+
* webhook HMAC secrets, chat transcripts, and verbatim terminal command history
|
|
15
|
+
* world-readable on a multi-user machine. These helpers restrict the data dir to
|
|
16
|
+
* owner-only and the db files (plus their WAL sidecars) to owner read/write.
|
|
17
|
+
*
|
|
18
|
+
* POSIX permission bits are a no-op on Windows (NTFS uses ACLs and the per-user
|
|
19
|
+
* profile already isolates the home dir), so both helpers early-return there —
|
|
20
|
+
* we never pretend a chmod that did nothing succeeded.
|
|
21
|
+
*/
|
|
22
|
+
/** Restrict a directory to owner-only (0700). No-op on Windows / on error. */
|
|
23
|
+
function secureDir(dir) {
|
|
24
|
+
if (process.platform === 'win32')
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
fs_1.default.chmodSync(dir, 0o700);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Best-effort: a chmod failure must never crash startup.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Restrict a SQLite db file and its `-wal`/`-shm` sidecars to 0600.
|
|
35
|
+
* No-op on Windows, for `:memory:`, and on error (sidecars may not exist yet).
|
|
36
|
+
*/
|
|
37
|
+
function secureDbFile(dbPath) {
|
|
38
|
+
if (process.platform === 'win32')
|
|
39
|
+
return;
|
|
40
|
+
if (dbPath === ':memory:')
|
|
41
|
+
return;
|
|
42
|
+
for (const file of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
43
|
+
try {
|
|
44
|
+
fs_1.default.chmodSync(file, 0o600);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Sidecar may not exist yet, or fs may reject — best-effort.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Cross-platform spawn wrapper.
|
|
3
|
+
//
|
|
4
|
+
// Two Windows-specific problems forced this helper:
|
|
5
|
+
//
|
|
6
|
+
// 1) `claude` (and similar npm-installed binaries) is shipped as a
|
|
7
|
+
// `.cmd` shim. `spawn('claude', ..., { shell: false })` fails
|
|
8
|
+
// with ENOENT because Node looks for an exact `claude` file
|
|
9
|
+
// without extension expansion.
|
|
10
|
+
//
|
|
11
|
+
// 2) Setting `shell: true` makes Windows resolve the shim, but
|
|
12
|
+
// cmd.exe then re-parses the concatenated command line and
|
|
13
|
+
// truncates any arg containing `\n` (e.g. claude's
|
|
14
|
+
// `--system-prompt "You are a...\n..."`).
|
|
15
|
+
//
|
|
16
|
+
// Since Node 20.12 / CVE-2024-27980 the obvious middle ground —
|
|
17
|
+
// `spawn('claude.cmd', ..., { shell: false })` — also fails, this
|
|
18
|
+
// time with EINVAL: Node refuses to spawn .cmd/.bat without a
|
|
19
|
+
// shell. `cross-spawn` is the de-facto fix: on Windows it
|
|
20
|
+
// internally launches `cmd.exe /d /s /c` with quoted-then-escaped
|
|
21
|
+
// args so newlines and shell metacharacters survive intact, and on
|
|
22
|
+
// POSIX it falls through to the native `child_process.spawn`.
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.spawnCli = spawnCli;
|
|
28
|
+
exports.resolveWindowsBinary = resolveWindowsBinary;
|
|
29
|
+
const child_process_1 = require("child_process");
|
|
30
|
+
const cross_spawn_1 = __importDefault(require("cross-spawn"));
|
|
31
|
+
function spawnCli(binary, args, options = {}) {
|
|
32
|
+
/* c8 ignore next 3 -- Windows-only branch; coverage runs on Linux/macOS */
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
return (0, cross_spawn_1.default)(binary, args, options);
|
|
35
|
+
}
|
|
36
|
+
return (0, child_process_1.spawn)(binary, args, options);
|
|
37
|
+
}
|
|
38
|
+
// Back-compat for callsites that only need the resolved binary
|
|
39
|
+
// (e.g. logging). Kept as a no-op identity on POSIX; on Windows
|
|
40
|
+
// `where`-based resolution lives inside cross-spawn now.
|
|
41
|
+
function resolveWindowsBinary(name) {
|
|
42
|
+
return name;
|
|
43
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookManager = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const desktop_db_1 = require("./desktop-db");
|
|
6
|
+
const WEBHOOK_TIMEOUT_MS = 10_000;
|
|
7
|
+
const MAX_ATTEMPTS = 3;
|
|
8
|
+
function delay(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
// ─── WebhookManager ───────────────────────────────────────────────────────────
|
|
12
|
+
class WebhookManager {
|
|
13
|
+
_desktopDb;
|
|
14
|
+
constructor(desktopDb) {
|
|
15
|
+
this._desktopDb = desktopDb;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Send a test ping to a single webhook (used by the test endpoint).
|
|
19
|
+
*/
|
|
20
|
+
deliverTest(webhook) {
|
|
21
|
+
const payload = {
|
|
22
|
+
event: 'job.completed',
|
|
23
|
+
timestamp: new Date().toISOString(),
|
|
24
|
+
projectId: webhook.project_id ?? '*',
|
|
25
|
+
data: { test: true, message: 'specrails-desktop webhook test ping' },
|
|
26
|
+
};
|
|
27
|
+
setImmediate(() => {
|
|
28
|
+
this._deliverWithRetry(webhook, payload).catch(() => { });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Deliver an event to all matching webhooks for a project.
|
|
33
|
+
* Non-blocking: fires and forgets with retry logic.
|
|
34
|
+
*/
|
|
35
|
+
deliver(projectId, event, data) {
|
|
36
|
+
const webhooks = (0, desktop_db_1.listWebhooksForProject)(this._desktopDb, projectId);
|
|
37
|
+
const matching = webhooks.filter((w) => {
|
|
38
|
+
try {
|
|
39
|
+
const events = JSON.parse(w.events);
|
|
40
|
+
return events.includes(event);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
for (const webhook of matching) {
|
|
47
|
+
const payload = {
|
|
48
|
+
event,
|
|
49
|
+
timestamp: new Date().toISOString(),
|
|
50
|
+
projectId,
|
|
51
|
+
data,
|
|
52
|
+
};
|
|
53
|
+
// Fire-and-forget with retry; errors are swallowed after exhausting attempts
|
|
54
|
+
setImmediate(() => {
|
|
55
|
+
this._deliverWithRetry(webhook, payload).catch(() => { });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async _deliverWithRetry(webhook, payload, attempt = 1) {
|
|
60
|
+
const body = JSON.stringify(payload);
|
|
61
|
+
const headers = {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'User-Agent': 'specrails-desktop',
|
|
64
|
+
};
|
|
65
|
+
if (webhook.secret) {
|
|
66
|
+
const sig = (0, crypto_1.createHmac)('sha256', webhook.secret).update(body).digest('hex');
|
|
67
|
+
headers['X-Specrails-Signature'] = `sha256=${sig}`;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(webhook.url, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers,
|
|
73
|
+
body,
|
|
74
|
+
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok && attempt < MAX_ATTEMPTS) {
|
|
77
|
+
await delay(1000 * attempt);
|
|
78
|
+
return this._deliverWithRetry(webhook, payload, attempt + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
83
|
+
await delay(1000 * attempt);
|
|
84
|
+
return this._deliverWithRetry(webhook, payload, attempt + 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.WebhookManager = WebhookManager;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pure, testable WebSocket routing decisions (H-09). Kept out of index.ts (which
|
|
3
|
+
// is excluded from coverage and not unit-tested) so the project-isolation logic
|
|
4
|
+
// that the Mobile Gateway will depend on is covered and verifiable.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.shouldDeliverToSubscriber = shouldDeliverToSubscriber;
|
|
7
|
+
exports.parseSubscribeFrame = parseSubscribeFrame;
|
|
8
|
+
/**
|
|
9
|
+
* Decide whether a broadcast message should be delivered to a given connection.
|
|
10
|
+
*
|
|
11
|
+
* - App-level messages (no projectId) go to everyone.
|
|
12
|
+
* - A connection that has NOT declared a subscription (`subscribedProjectId`
|
|
13
|
+
* null) receives everything — back-compat with the current web client, whose
|
|
14
|
+
* own client-side filter remains as a redundant second layer.
|
|
15
|
+
* - A connection subscribed to project P receives only P's project-scoped
|
|
16
|
+
* messages (plus all app-level ones).
|
|
17
|
+
*
|
|
18
|
+
* The Mobile Gateway turns this into a hard authorization boundary by always
|
|
19
|
+
* subscribing each device connection to exactly the project(s) it may see.
|
|
20
|
+
*/
|
|
21
|
+
function shouldDeliverToSubscriber(msgProjectId, subscribedProjectId) {
|
|
22
|
+
if (msgProjectId === undefined)
|
|
23
|
+
return true;
|
|
24
|
+
if (subscribedProjectId === null)
|
|
25
|
+
return true;
|
|
26
|
+
return subscribedProjectId === msgProjectId;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse an inbound WS control frame and return the projectId to subscribe to.
|
|
30
|
+
*
|
|
31
|
+
* Returns `{ subscribe: true, projectId }` for a well-formed
|
|
32
|
+
* `{ type: 'subscribe', projectId: <string|null> }` frame (a non-string
|
|
33
|
+
* projectId clears the subscription → null), or `{ subscribe: false }` for
|
|
34
|
+
* anything else / malformed input. Never throws.
|
|
35
|
+
*/
|
|
36
|
+
function parseSubscribeFrame(raw) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (parsed?.type === 'subscribe') {
|
|
40
|
+
return { subscribe: true, projectId: typeof parsed.projectId === 'string' ? parsed.projectId : null };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Malformed control frame — ignore.
|
|
45
|
+
}
|
|
46
|
+
return { subscribe: false, projectId: null };
|
|
47
|
+
}
|