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,617 @@
|
|
|
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.PluginManager = exports.PluginInstallError = exports.PluginAlreadyInstalledError = exports.PluginNotInstalledError = exports.PluginNotFoundError = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ownership_1 = require("./plugins/ownership");
|
|
10
|
+
const claude_approval_1 = require("./plugins/claude-approval");
|
|
11
|
+
const drift_1 = require("./plugins/drift");
|
|
12
|
+
const providers_1 = require("./providers");
|
|
13
|
+
const paths_1 = require("./plugins/paths");
|
|
14
|
+
function readMcpServersMap(projectPath) {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs_1.default.existsSync((0, paths_1.mcpJsonPath)(projectPath)))
|
|
17
|
+
return {};
|
|
18
|
+
const raw = fs_1.default.readFileSync((0, paths_1.mcpJsonPath)(projectPath), 'utf8');
|
|
19
|
+
if (!raw.trim())
|
|
20
|
+
return {};
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
return parsed.mcpServers ?? {};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const json_mutation_1 = require("./plugins/json-mutation");
|
|
29
|
+
const contributors_1 = require("./plugins/contributors");
|
|
30
|
+
class PluginNotFoundError extends Error {
|
|
31
|
+
constructor(name) {
|
|
32
|
+
super(`plugin not found in registry: ${name}`);
|
|
33
|
+
this.name = 'PluginNotFoundError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.PluginNotFoundError = PluginNotFoundError;
|
|
37
|
+
class PluginNotInstalledError extends Error {
|
|
38
|
+
constructor(name) {
|
|
39
|
+
super(`plugin is not installed: ${name}`);
|
|
40
|
+
this.name = 'PluginNotInstalledError';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.PluginNotInstalledError = PluginNotInstalledError;
|
|
44
|
+
class PluginAlreadyInstalledError extends Error {
|
|
45
|
+
constructor(name) {
|
|
46
|
+
super(`plugin is already installed: ${name}`);
|
|
47
|
+
this.name = 'PluginAlreadyInstalledError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.PluginAlreadyInstalledError = PluginAlreadyInstalledError;
|
|
51
|
+
class PluginInstallError extends Error {
|
|
52
|
+
cause;
|
|
53
|
+
constructor(message, cause) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'PluginInstallError';
|
|
56
|
+
this.cause = cause;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.PluginInstallError = PluginInstallError;
|
|
60
|
+
const DEFAULT_VERIFY_TIMEOUT_MS = 2000;
|
|
61
|
+
class PluginManager {
|
|
62
|
+
registry;
|
|
63
|
+
_options;
|
|
64
|
+
constructor(plugins, options = {}) {
|
|
65
|
+
this.registry = (0, ownership_1.buildOwnershipMap)(plugins);
|
|
66
|
+
this._options = {
|
|
67
|
+
defaultVerifyTimeoutMs: options.defaultVerifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS,
|
|
68
|
+
checkPrerequisite: options.checkPrerequisite,
|
|
69
|
+
claudeApprovalChecker: options.claudeApprovalChecker ?? claude_approval_1.getClaudeApprovalState,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// ─── State helpers ─────────────────────────────────────────────────────────
|
|
73
|
+
getProjectState(projectPath) {
|
|
74
|
+
return (0, json_mutation_1.readJsonOr)((0, paths_1.stateFilePath)(projectPath), {
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
plugins: {},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async _writeProjectState(projectPath, state) {
|
|
80
|
+
fs_1.default.mkdirSync((0, paths_1.pluginsDir)(projectPath), { recursive: true });
|
|
81
|
+
await (0, json_mutation_1.withFileLock)((0, paths_1.stateFilePath)(projectPath), async () => {
|
|
82
|
+
(0, json_mutation_1.atomicWriteFileSync)((0, paths_1.stateFilePath)(projectPath), JSON.stringify(state, null, 2) + '\n');
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// ─── Catalog ───────────────────────────────────────────────────────────────
|
|
86
|
+
async listAvailable(projectPath, providerId) {
|
|
87
|
+
const state = this.getProjectState(projectPath);
|
|
88
|
+
const entries = [];
|
|
89
|
+
// Bundled plugins, regardless of install state.
|
|
90
|
+
for (const plugin of this.registry.byName.values()) {
|
|
91
|
+
const m = plugin.manifest;
|
|
92
|
+
const stateEntry = state.plugins[m.name];
|
|
93
|
+
let status;
|
|
94
|
+
// Provider applicability: a plugin is `not-applicable` when the
|
|
95
|
+
// project's provider is registered, providerSupport is declared, and
|
|
96
|
+
// there's no entry for this provider. Plugins that don't declare
|
|
97
|
+
// providerSupport at all default to claude-compatible (preserves
|
|
98
|
+
// pre-§14 behaviour for unchanged manifests).
|
|
99
|
+
const supportsThisProvider = providerId === undefined
|
|
100
|
+
? true
|
|
101
|
+
: m.providerSupport === undefined
|
|
102
|
+
? providerId === 'claude'
|
|
103
|
+
: providerId in m.providerSupport;
|
|
104
|
+
if (!supportsThisProvider) {
|
|
105
|
+
status = 'not-applicable';
|
|
106
|
+
}
|
|
107
|
+
else if (!stateEntry) {
|
|
108
|
+
status = 'not-installed';
|
|
109
|
+
}
|
|
110
|
+
else if (stateEntry.health === 'degraded') {
|
|
111
|
+
status = 'degraded';
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Plugin install lives in two files:
|
|
115
|
+
// (a) state.json — the app's record that the plugin is installed
|
|
116
|
+
// (b) .mcp.json — the actual contract with Claude (loaded blindly)
|
|
117
|
+
// Active = both present. Deactivated = (a) without the (b) keys
|
|
118
|
+
// (user toggled off; install survives). For codex projects the
|
|
119
|
+
// (b) check is skipped because the registration lives outside the
|
|
120
|
+
// project filesystem (CODEX_HOME).
|
|
121
|
+
let allKeysPresent = true;
|
|
122
|
+
if (providerId === 'codex') {
|
|
123
|
+
// For codex we trust state.json — `codex mcp list` against the
|
|
124
|
+
// per-project CODEX_HOME is the source of truth, but it requires a
|
|
125
|
+
// subprocess which is too expensive for a catalog listing call.
|
|
126
|
+
allKeysPresent = true;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const mcpServers = readMcpServersMap(projectPath);
|
|
130
|
+
for (const server of m.owns.mcpServers ?? []) {
|
|
131
|
+
if (!(server in mcpServers)) {
|
|
132
|
+
allKeysPresent = false;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
status = allKeysPresent ? 'installed' : 'deactivated';
|
|
138
|
+
}
|
|
139
|
+
// Surface marketplace conflicts so UI can offer a "Disable global"
|
|
140
|
+
// affordance when our project-scoped install is being shadowed.
|
|
141
|
+
const conflicts = [];
|
|
142
|
+
const cachedDisabled = [];
|
|
143
|
+
for (const server of m.owns.mcpServers ?? []) {
|
|
144
|
+
for (const key of (0, claude_approval_1.findEnabledMarketplaceKeys)(server)) {
|
|
145
|
+
if (!conflicts.includes(key))
|
|
146
|
+
conflicts.push(key);
|
|
147
|
+
}
|
|
148
|
+
for (const key of (0, claude_approval_1.findInstalledButNotEnabledMarketplaceKeys)(server)) {
|
|
149
|
+
if (!cachedDisabled.includes(key))
|
|
150
|
+
cachedDisabled.push(key);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Drift detection: only meaningful when actually installed.
|
|
154
|
+
const updateAvailable = stateEntry ? (0, drift_1.detectMcpDrift)(projectPath, plugin) : false;
|
|
155
|
+
entries.push({
|
|
156
|
+
name: m.name,
|
|
157
|
+
version: m.version,
|
|
158
|
+
description: m.description,
|
|
159
|
+
whatItDoes: m.whatItDoes,
|
|
160
|
+
category: m.category,
|
|
161
|
+
requirements: m.requirements ?? [],
|
|
162
|
+
owns: m.owns,
|
|
163
|
+
status,
|
|
164
|
+
installedAt: stateEntry?.installedAt,
|
|
165
|
+
health: stateEntry?.health,
|
|
166
|
+
healthReason: stateEntry?.healthReason,
|
|
167
|
+
marketplaceConflicts: conflicts.length > 0 ? conflicts : undefined,
|
|
168
|
+
marketplaceCachedButDisabled: cachedDisabled.length > 0 ? cachedDisabled : undefined,
|
|
169
|
+
updateAvailable: updateAvailable || undefined,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Orphan plugins: present in state.json but not in the bundled registry.
|
|
173
|
+
for (const [name, entry] of Object.entries(state.plugins)) {
|
|
174
|
+
if (this.registry.byName.has(name))
|
|
175
|
+
continue;
|
|
176
|
+
entries.push({
|
|
177
|
+
name,
|
|
178
|
+
version: entry.version,
|
|
179
|
+
description: '(plugin no longer bundled)',
|
|
180
|
+
whatItDoes: [],
|
|
181
|
+
requirements: [],
|
|
182
|
+
owns: {},
|
|
183
|
+
status: 'orphan',
|
|
184
|
+
installedAt: entry.installedAt,
|
|
185
|
+
health: entry.health,
|
|
186
|
+
healthReason: entry.healthReason,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
190
|
+
}
|
|
191
|
+
// ─── Preview install ───────────────────────────────────────────────────────
|
|
192
|
+
async previewInstall(projectPath, projectId, name) {
|
|
193
|
+
const plugin = this.registry.byName.get(name);
|
|
194
|
+
if (!plugin)
|
|
195
|
+
throw new PluginNotFoundError(name);
|
|
196
|
+
let files;
|
|
197
|
+
if (plugin.previewInstall) {
|
|
198
|
+
files = await plugin.previewInstall({ projectPath, projectId });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
files = this._derivePreviewFiles(projectPath, plugin);
|
|
202
|
+
}
|
|
203
|
+
const requirements = await Promise.all((plugin.manifest.requirements ?? []).map(async (req) => {
|
|
204
|
+
if (this._options.checkPrerequisite) {
|
|
205
|
+
const r = await this._options.checkPrerequisite(req);
|
|
206
|
+
return { name: req.name, ...r };
|
|
207
|
+
}
|
|
208
|
+
return { name: req.name, installed: true, executable: true, meetsMinimum: true };
|
|
209
|
+
}));
|
|
210
|
+
const hostKey = `${process.platform}-${process.arch}`;
|
|
211
|
+
const platformNote = plugin.manifest.platformNotes?.[hostKey];
|
|
212
|
+
return {
|
|
213
|
+
pluginName: name,
|
|
214
|
+
files,
|
|
215
|
+
requirements,
|
|
216
|
+
platformNote,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
_derivePreviewFiles(projectPath, plugin) {
|
|
220
|
+
const out = [];
|
|
221
|
+
const m = plugin.manifest;
|
|
222
|
+
// .mcp.json
|
|
223
|
+
if ((m.owns.mcpServers ?? []).length > 0) {
|
|
224
|
+
const mcpExists = fs_1.default.existsSync((0, paths_1.mcpJsonPath)(projectPath));
|
|
225
|
+
out.push({
|
|
226
|
+
path: '.mcp.json',
|
|
227
|
+
op: mcpExists ? 'modify' : 'create',
|
|
228
|
+
summary: `+ mcpServers.${(m.owns.mcpServers ?? []).join(', mcpServers.')}`,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// Agent fragments
|
|
232
|
+
for (const frag of m.owns.agentFragments ?? []) {
|
|
233
|
+
const exists = fs_1.default.existsSync(path_1.default.join(projectPath, frag));
|
|
234
|
+
out.push({ path: frag, op: exists ? 'modify' : 'create' });
|
|
235
|
+
}
|
|
236
|
+
// Shared-file contributors (CLAUDE.md today, more in the future).
|
|
237
|
+
for (const rel of (0, contributors_1.contributorPaths)(plugin)) {
|
|
238
|
+
const exists = fs_1.default.existsSync(path_1.default.join(projectPath, rel));
|
|
239
|
+
out.push({
|
|
240
|
+
path: rel,
|
|
241
|
+
op: exists ? 'modify' : 'create',
|
|
242
|
+
summary: `+ <!-- specrails-desktop-managed:${m.name} --> block`,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// State file
|
|
246
|
+
const stateExists = fs_1.default.existsSync((0, paths_1.stateFilePath)(projectPath));
|
|
247
|
+
out.push({
|
|
248
|
+
path: '.specrails/plugins/state.json',
|
|
249
|
+
op: stateExists ? 'modify' : 'create',
|
|
250
|
+
summary: `+ plugins.${m.name}`,
|
|
251
|
+
});
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
// ─── Install ───────────────────────────────────────────────────────────────
|
|
255
|
+
async install(projectPath, projectId, name, broadcast, providerId) {
|
|
256
|
+
const plugin = this.registry.byName.get(name);
|
|
257
|
+
if (!plugin)
|
|
258
|
+
throw new PluginNotFoundError(name);
|
|
259
|
+
// Provider applicability gate: refuse to install a plugin that has no
|
|
260
|
+
// providerSupport entry for this project's provider. Plugins that omit
|
|
261
|
+
// providerSupport altogether default to claude-compatible.
|
|
262
|
+
if (providerId !== undefined && providerId !== 'claude') {
|
|
263
|
+
const declared = plugin.manifest.providerSupport;
|
|
264
|
+
if (declared !== undefined && !(providerId in declared)) {
|
|
265
|
+
throw new PluginInstallError(`plugin '${name}' is not applicable for provider '${providerId}'. Declared providers: ${Object.keys(declared).join(', ')}.`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const state = this.getProjectState(projectPath);
|
|
269
|
+
if (state.plugins[name])
|
|
270
|
+
throw new PluginAlreadyInstalledError(name);
|
|
271
|
+
// Check for ownership conflicts with user-authored `.mcp.json` entries.
|
|
272
|
+
// Only meaningful for `project-json` MCP registration providers (claude
|
|
273
|
+
// today). Codex registers via `codex mcp add` against per-project
|
|
274
|
+
// CODEX_HOME, which the plugin's install path checks via `codex mcp list`.
|
|
275
|
+
const adapter = providerId !== undefined && (0, providers_1.hasAdapter)(providerId)
|
|
276
|
+
? (0, providers_1.getAdapter)(providerId)
|
|
277
|
+
: null;
|
|
278
|
+
const usesProjectJsonMcp = adapter === null || adapter.mcpRegistration === 'project-json';
|
|
279
|
+
if (usesProjectJsonMcp) {
|
|
280
|
+
const mcpFile = (0, paths_1.mcpJsonPath)(projectPath);
|
|
281
|
+
if (fs_1.default.existsSync(mcpFile)) {
|
|
282
|
+
const raw = fs_1.default.readFileSync(mcpFile, 'utf8');
|
|
283
|
+
let parsed;
|
|
284
|
+
try {
|
|
285
|
+
parsed = raw.trim() ? JSON.parse(raw) : {};
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// A hand-edited / broken `.mcp.json` should yield an actionable 409,
|
|
289
|
+
// not an opaque 500 with a raw "Unexpected token" SyntaxError.
|
|
290
|
+
throw new PluginInstallError(`cannot install '${name}': '${(0, paths_1.mcpJsonPath)(projectPath)}' is not valid JSON; fix it first.`);
|
|
291
|
+
}
|
|
292
|
+
const servers = parsed.mcpServers ?? {};
|
|
293
|
+
for (const key of plugin.manifest.owns.mcpServers ?? []) {
|
|
294
|
+
if (key in servers) {
|
|
295
|
+
throw new PluginInstallError(`cannot install '${name}': '${(0, paths_1.mcpJsonPath)(projectPath)}' already has a 'mcpServers.${key}' entry. Remove it first.`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Snapshot pre-install state of every file the plugin might touch — we
|
|
301
|
+
// need exact bytes to roll back if install/verify fails.
|
|
302
|
+
const targetPaths = [
|
|
303
|
+
(0, paths_1.mcpJsonPath)(projectPath),
|
|
304
|
+
(0, paths_1.stateFilePath)(projectPath),
|
|
305
|
+
...(plugin.manifest.owns.agentFragments ?? []).map((f) => path_1.default.join(projectPath, f)),
|
|
306
|
+
// Include the shared instructions file (CLAUDE.md / AGENTS.md) so a failed
|
|
307
|
+
// install rolls it back too — otherwise an applyContributors write that
|
|
308
|
+
// survives a later failure leaves an orphaned managed block with no state
|
|
309
|
+
// entry, which uninstall can never remove (breaks byte-identical restore).
|
|
310
|
+
...(0, contributors_1.contributorPaths)(plugin, providerId).map((rel) => path_1.default.join(projectPath, rel)),
|
|
311
|
+
];
|
|
312
|
+
const preState = new Map();
|
|
313
|
+
for (const p of targetPaths) {
|
|
314
|
+
preState.set(p, fs_1.default.existsSync(p) ? fs_1.default.readFileSync(p) : null);
|
|
315
|
+
}
|
|
316
|
+
const installedFiles = [];
|
|
317
|
+
const onLog = (line) => {
|
|
318
|
+
const msg = {
|
|
319
|
+
type: 'plugin.install_progress',
|
|
320
|
+
projectId,
|
|
321
|
+
name,
|
|
322
|
+
line,
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
};
|
|
325
|
+
broadcast(msg);
|
|
326
|
+
};
|
|
327
|
+
const ctx = {
|
|
328
|
+
projectPath,
|
|
329
|
+
projectId,
|
|
330
|
+
providerId,
|
|
331
|
+
recordInstalledFile: (rel) => { installedFiles.push(rel); },
|
|
332
|
+
log: onLog,
|
|
333
|
+
};
|
|
334
|
+
try {
|
|
335
|
+
await plugin.install(ctx);
|
|
336
|
+
// Verify immediately. A degraded result also triggers rollback because
|
|
337
|
+
// the spec requires verify-pass before we commit state.
|
|
338
|
+
const verify = await this._runVerify(plugin, projectPath, projectId);
|
|
339
|
+
if (!verify.ok) {
|
|
340
|
+
throw new PluginInstallError(`verify failed after install: ${verify.reason ?? 'unknown'}`);
|
|
341
|
+
}
|
|
342
|
+
// Commit: write state.json with the install record.
|
|
343
|
+
const stateNow = this.getProjectState(projectPath);
|
|
344
|
+
stateNow.plugins[name] = {
|
|
345
|
+
version: plugin.manifest.version,
|
|
346
|
+
installedAt: new Date().toISOString(),
|
|
347
|
+
installedFiles,
|
|
348
|
+
health: 'ok',
|
|
349
|
+
};
|
|
350
|
+
await this._writeProjectState(projectPath, stateNow);
|
|
351
|
+
// No additional approval write needed: any server in `.mcp.json` loads
|
|
352
|
+
// automatically when Claude opens the project. Install IS active.
|
|
353
|
+
// Apply shared-file contributors (CLAUDE.md block today, more in the
|
|
354
|
+
// future). Each contributor is per-plugin and idempotent.
|
|
355
|
+
const sharedTouched = await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
|
|
356
|
+
if (sharedTouched.length > 0) {
|
|
357
|
+
for (const p of sharedTouched) {
|
|
358
|
+
if (!installedFiles.includes(p))
|
|
359
|
+
installedFiles.push(p);
|
|
360
|
+
}
|
|
361
|
+
const stateNow2 = this.getProjectState(projectPath);
|
|
362
|
+
if (stateNow2.plugins[name])
|
|
363
|
+
stateNow2.plugins[name].installedFiles = installedFiles;
|
|
364
|
+
await this._writeProjectState(projectPath, stateNow2);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
// Roll back every file we snapshotted. Byte-identical restore.
|
|
369
|
+
for (const [p, bytes] of preState.entries()) {
|
|
370
|
+
try {
|
|
371
|
+
if (bytes === null) {
|
|
372
|
+
if (fs_1.default.existsSync(p))
|
|
373
|
+
fs_1.default.unlinkSync(p);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Write the raw Buffer (not .toString('utf8')) so a snapshot with
|
|
377
|
+
// non-UTF8 bytes restores byte-for-byte.
|
|
378
|
+
(0, json_mutation_1.atomicWriteFileSync)(p, bytes);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Best-effort rollback; any failure here will surface via verify on
|
|
383
|
+
// the next install attempt.
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
throw err instanceof PluginInstallError ? err : new PluginInstallError(`install of '${name}' failed: ${err?.message ?? String(err)}`, err);
|
|
387
|
+
}
|
|
388
|
+
const msg = {
|
|
389
|
+
type: 'plugin.installed',
|
|
390
|
+
projectId,
|
|
391
|
+
name,
|
|
392
|
+
version: plugin.manifest.version,
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
};
|
|
395
|
+
broadcast(msg);
|
|
396
|
+
}
|
|
397
|
+
// ─── Uninstall ─────────────────────────────────────────────────────────────
|
|
398
|
+
async uninstall(projectPath, projectId, name, broadcast, providerId) {
|
|
399
|
+
const state = this.getProjectState(projectPath);
|
|
400
|
+
const entry = state.plugins[name];
|
|
401
|
+
if (!entry)
|
|
402
|
+
throw new PluginNotInstalledError(name);
|
|
403
|
+
const plugin = this.registry.byName.get(name);
|
|
404
|
+
const onLog = (line) => {
|
|
405
|
+
broadcast({
|
|
406
|
+
type: 'plugin.install_progress',
|
|
407
|
+
projectId,
|
|
408
|
+
name,
|
|
409
|
+
line,
|
|
410
|
+
timestamp: new Date().toISOString(),
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
if (plugin) {
|
|
414
|
+
// Revert shared-file contributors first so a partial uninstall doesn't
|
|
415
|
+
// leave dangling instructions referencing missing tools.
|
|
416
|
+
await (0, contributors_1.revertContributors)(plugin, projectPath, providerId);
|
|
417
|
+
await plugin.uninstall({
|
|
418
|
+
projectPath,
|
|
419
|
+
projectId,
|
|
420
|
+
providerId,
|
|
421
|
+
recordInstalledFile: () => { },
|
|
422
|
+
log: onLog,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// Orphan removal: no plugin code available. Best-effort cleanup of
|
|
427
|
+
// recorded installedFiles + drop the state entry. We cannot know which
|
|
428
|
+
// mcpServers keys belonged to this plugin, so we leave .mcp.json alone.
|
|
429
|
+
const root = path_1.default.resolve(projectPath);
|
|
430
|
+
for (const rel of entry.installedFiles ?? []) {
|
|
431
|
+
const abs = path_1.default.resolve(projectPath, rel);
|
|
432
|
+
// M5: installedFiles comes from state.json, which a hostile repo can
|
|
433
|
+
// ship. Without containment, `rel` of "../../../Users/victim/x" (or an
|
|
434
|
+
// absolute path) turns orphan removal into an arbitrary-file-deletion
|
|
435
|
+
// primitive. Skip anything that resolves outside the project root.
|
|
436
|
+
const within = path_1.default.relative(root, abs);
|
|
437
|
+
if (within === '' || within.startsWith('..') || path_1.default.isAbsolute(within)) {
|
|
438
|
+
console.warn(`[plugin-manager] skipping out-of-project installedFile during orphan removal: ${rel}`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
if (fs_1.default.existsSync(abs))
|
|
443
|
+
fs_1.default.unlinkSync(abs);
|
|
444
|
+
}
|
|
445
|
+
catch { /* ignore */ }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const stateNow = this.getProjectState(projectPath);
|
|
449
|
+
delete stateNow.plugins[name];
|
|
450
|
+
await this._writeProjectState(projectPath, stateNow);
|
|
451
|
+
broadcast({
|
|
452
|
+
type: 'plugin.uninstalled',
|
|
453
|
+
projectId,
|
|
454
|
+
name,
|
|
455
|
+
timestamp: new Date().toISOString(),
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Re-write the project's `.mcp.json` entries owned by this plugin to match
|
|
460
|
+
* the bundled manifest's canonical shape. Surgical: only the plugin's
|
|
461
|
+
* `owns.mcpServers` keys are touched; user entries are preserved. Used to
|
|
462
|
+
* resolve drift surfaced by `updateAvailable`.
|
|
463
|
+
*/
|
|
464
|
+
async updateMcpEntry(projectPath, projectId, name, broadcast, providerId) {
|
|
465
|
+
const plugin = this.registry.byName.get(name);
|
|
466
|
+
if (!plugin)
|
|
467
|
+
throw new PluginNotFoundError(name);
|
|
468
|
+
const state = this.getProjectState(projectPath);
|
|
469
|
+
if (!state.plugins[name])
|
|
470
|
+
throw new PluginNotInstalledError(name);
|
|
471
|
+
const expected = plugin.expectedMcpEntry?.();
|
|
472
|
+
if (!expected) {
|
|
473
|
+
throw new PluginInstallError(`'${name}' does not declare expectedMcpEntry; cannot update`);
|
|
474
|
+
}
|
|
475
|
+
const owned = plugin.manifest.owns.mcpServers ?? [];
|
|
476
|
+
const entries = {};
|
|
477
|
+
for (const key of owned)
|
|
478
|
+
entries[key] = expected;
|
|
479
|
+
await PluginManager.mergeMcpServers(projectPath, entries);
|
|
480
|
+
// Refresh shared-file contributions too: a drift may exist in CLAUDE.md
|
|
481
|
+
// even when the .mcp.json entry matches.
|
|
482
|
+
await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
|
|
483
|
+
broadcast({
|
|
484
|
+
type: 'plugin.health_changed',
|
|
485
|
+
projectId,
|
|
486
|
+
name,
|
|
487
|
+
status: 'unknown',
|
|
488
|
+
reason: 'updated',
|
|
489
|
+
timestamp: new Date().toISOString(),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Toggle a plugin between active and deactivated. Reality of Claude's MCP
|
|
494
|
+
* loading: any server present in `<project>/.mcp.json` is loaded by Claude
|
|
495
|
+
* regardless of `enabledMcpjsonServers` flags. So:
|
|
496
|
+
*
|
|
497
|
+
* - active=true → re-write the canonical mcpServers entry (from the
|
|
498
|
+
* plugin's `expectedMcpEntry`) into `.mcp.json`
|
|
499
|
+
* - active=false → remove only the owned mcpServers keys; preserve
|
|
500
|
+
* state.json so `installed` memory survives, and
|
|
501
|
+
* preserve any user-authored sibling entries
|
|
502
|
+
*
|
|
503
|
+
* Plugin install state survives across toggles. Uninstall is the only
|
|
504
|
+
* action that clears state.json + custom-*.md fragments.
|
|
505
|
+
*/
|
|
506
|
+
async setActive(projectPath, projectId, name, active, broadcast, providerId) {
|
|
507
|
+
const plugin = this.registry.byName.get(name);
|
|
508
|
+
if (!plugin)
|
|
509
|
+
throw new PluginNotFoundError(name);
|
|
510
|
+
const state = this.getProjectState(projectPath);
|
|
511
|
+
if (!state.plugins[name])
|
|
512
|
+
throw new PluginNotInstalledError(name);
|
|
513
|
+
const owned = plugin.manifest.owns.mcpServers ?? [];
|
|
514
|
+
if (owned.length === 0) {
|
|
515
|
+
throw new PluginInstallError(`'${name}' owns no mcpServers; cannot toggle activation`);
|
|
516
|
+
}
|
|
517
|
+
if (active) {
|
|
518
|
+
const expected = plugin.expectedMcpEntry?.();
|
|
519
|
+
if (!expected) {
|
|
520
|
+
throw new PluginInstallError(`'${name}' does not declare expectedMcpEntry; cannot activate`);
|
|
521
|
+
}
|
|
522
|
+
const entries = {};
|
|
523
|
+
for (const k of owned)
|
|
524
|
+
entries[k] = expected;
|
|
525
|
+
await PluginManager.mergeMcpServers(projectPath, entries);
|
|
526
|
+
await (0, contributors_1.applyContributors)(plugin, projectPath, providerId);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
await PluginManager.removeMcpServers(projectPath, owned);
|
|
530
|
+
await (0, contributors_1.revertContributors)(plugin, projectPath, providerId);
|
|
531
|
+
}
|
|
532
|
+
broadcast({
|
|
533
|
+
type: 'plugin.health_changed',
|
|
534
|
+
projectId,
|
|
535
|
+
name,
|
|
536
|
+
status: active ? 'ok' : 'unknown',
|
|
537
|
+
reason: active ? 'activated' : 'deactivated',
|
|
538
|
+
timestamp: new Date().toISOString(),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/** Drop a state.json entry for a plugin no longer in the registry. */
|
|
542
|
+
async removeOrphan(projectPath, projectId, name, broadcast) {
|
|
543
|
+
if (this.registry.byName.has(name)) {
|
|
544
|
+
throw new PluginInstallError(`'${name}' is not orphan; it is still bundled. Use uninstall instead.`);
|
|
545
|
+
}
|
|
546
|
+
return this.uninstall(projectPath, projectId, name, broadcast);
|
|
547
|
+
}
|
|
548
|
+
// ─── Verify ────────────────────────────────────────────────────────────────
|
|
549
|
+
async verify(projectPath, projectId, name, broadcast) {
|
|
550
|
+
const plugin = this.registry.byName.get(name);
|
|
551
|
+
if (!plugin)
|
|
552
|
+
throw new PluginNotFoundError(name);
|
|
553
|
+
const result = await this._runVerify(plugin, projectPath, projectId);
|
|
554
|
+
await this._cacheHealth(projectPath, projectId, name, result, broadcast);
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
async _runVerify(plugin, projectPath, projectId) {
|
|
558
|
+
const timeout = plugin.manifest.verifyTimeoutMs ?? this._options.defaultVerifyTimeoutMs;
|
|
559
|
+
const checkedAt = new Date().toISOString();
|
|
560
|
+
try {
|
|
561
|
+
const result = await Promise.race([
|
|
562
|
+
plugin.verify({ projectPath, projectId }),
|
|
563
|
+
new Promise((resolve) => setTimeout(() => resolve({ __timeout: true }), timeout).unref?.()),
|
|
564
|
+
]);
|
|
565
|
+
if ('__timeout' in result) {
|
|
566
|
+
return { ok: false, reason: 'verify-timeout', checkedAt };
|
|
567
|
+
}
|
|
568
|
+
return { ok: result.ok, reason: result.reason, checkedAt: result.checkedAt ?? checkedAt };
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
return { ok: false, reason: `verify-exception: ${err?.message ?? String(err)}`, checkedAt };
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async _cacheHealth(projectPath, projectId, name, result, broadcast) {
|
|
575
|
+
const state = this.getProjectState(projectPath);
|
|
576
|
+
const entry = state.plugins[name];
|
|
577
|
+
if (!entry)
|
|
578
|
+
return;
|
|
579
|
+
const newHealth = result.ok ? 'ok' : 'degraded';
|
|
580
|
+
const changed = entry.health !== newHealth || entry.healthReason !== result.reason;
|
|
581
|
+
if (!changed)
|
|
582
|
+
return; // nothing to persist — avoids per-spawn write churn (verify runs on every rail spawn)
|
|
583
|
+
entry.health = newHealth;
|
|
584
|
+
entry.healthReason = result.reason;
|
|
585
|
+
await this._writeProjectState(projectPath, state);
|
|
586
|
+
if (broadcast) {
|
|
587
|
+
const msg = {
|
|
588
|
+
type: 'plugin.health_changed',
|
|
589
|
+
projectId,
|
|
590
|
+
name,
|
|
591
|
+
status: newHealth,
|
|
592
|
+
reason: result.reason,
|
|
593
|
+
timestamp: new Date().toISOString(),
|
|
594
|
+
};
|
|
595
|
+
broadcast(msg);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// ─── Surgical helpers exposed to plugins ───────────────────────────────────
|
|
599
|
+
/** Helper: surgically merge `mcpServers.<key>` entries into .mcp.json. */
|
|
600
|
+
static async mergeMcpServers(projectPath, entries) {
|
|
601
|
+
await (0, json_mutation_1.surgicalMergeJson)((0, paths_1.mcpJsonPath)(projectPath), (current) => {
|
|
602
|
+
const next = (current ?? {});
|
|
603
|
+
const servers = (next.mcpServers ?? {});
|
|
604
|
+
for (const [k, v] of Object.entries(entries))
|
|
605
|
+
servers[k] = v;
|
|
606
|
+
next.mcpServers = servers;
|
|
607
|
+
return next;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/** Helper: remove specific `mcpServers.<key>` entries from .mcp.json. */
|
|
611
|
+
static async removeMcpServers(projectPath, keys) {
|
|
612
|
+
if (keys.length === 0)
|
|
613
|
+
return;
|
|
614
|
+
await (0, json_mutation_1.surgicalRemoveKeys)((0, paths_1.mcpJsonPath)(projectPath), keys.map((k) => `mcpServers.${k}`));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
exports.PluginManager = PluginManager;
|