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,285 @@
|
|
|
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.MobileGateway = void 0;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const express_1 = __importDefault(require("express"));
|
|
11
|
+
const ws_1 = require("ws");
|
|
12
|
+
const desktop_db_1 = require("../desktop-db");
|
|
13
|
+
const mobile_tls_1 = require("./mobile-tls");
|
|
14
|
+
const mobile_pairing_1 = require("./mobile-pairing");
|
|
15
|
+
const mobile_router_1 = require("./mobile-router");
|
|
16
|
+
const mobile_ws_1 = require("./mobile-ws");
|
|
17
|
+
const mobile_mdns_1 = require("./mobile-mdns");
|
|
18
|
+
const mobile_devices_1 = require("./mobile-devices");
|
|
19
|
+
const mobile_auth_1 = require("./mobile-auth");
|
|
20
|
+
// Lifecycle owner of the second HTTPS+WSS listener (default :4202), hard-isolated
|
|
21
|
+
// from the main server. Off by default; started on enable or boot-if-enabled.
|
|
22
|
+
const DEFAULT_PORT = 4202;
|
|
23
|
+
const SETTING = {
|
|
24
|
+
enabled: 'mobile.enabled',
|
|
25
|
+
port: 'mobile.port',
|
|
26
|
+
instanceId: 'mobile.desktop_instance_id',
|
|
27
|
+
name: 'mobile.desktop_name',
|
|
28
|
+
mdns: 'mobile.mdns_enabled',
|
|
29
|
+
fingerprint: 'mobile.cert_fingerprint',
|
|
30
|
+
};
|
|
31
|
+
// Legacy fallback — pre-rebrand (Specrails Hub) setting keys. Values are
|
|
32
|
+
// read-migrated to the renamed keys on first access so the stable instance id
|
|
33
|
+
// (which paired phones already store as `hubInstanceId`) survives the rename.
|
|
34
|
+
const LEGACY_SETTING = {
|
|
35
|
+
instanceId: 'mobile.hub_instance_id',
|
|
36
|
+
name: 'mobile.hub_name',
|
|
37
|
+
};
|
|
38
|
+
function lanAddresses() {
|
|
39
|
+
const out = [];
|
|
40
|
+
const ifaces = os_1.default.networkInterfaces();
|
|
41
|
+
for (const name of Object.keys(ifaces)) {
|
|
42
|
+
for (const ni of ifaces[name] ?? []) {
|
|
43
|
+
if (ni.family === 'IPv4' && !ni.internal)
|
|
44
|
+
out.push(ni.address);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
class MobileGateway {
|
|
50
|
+
_db;
|
|
51
|
+
_desktopPort;
|
|
52
|
+
_broadcast;
|
|
53
|
+
_bindHost;
|
|
54
|
+
_cert = null;
|
|
55
|
+
_server = null;
|
|
56
|
+
_wss = null;
|
|
57
|
+
_bridge = null;
|
|
58
|
+
_pairing = null;
|
|
59
|
+
_running = false;
|
|
60
|
+
_boundPort = DEFAULT_PORT;
|
|
61
|
+
_portOverride;
|
|
62
|
+
constructor(deps) {
|
|
63
|
+
this._db = deps.desktopDb;
|
|
64
|
+
this._desktopPort = deps.desktopPort;
|
|
65
|
+
this._broadcast = deps.broadcast;
|
|
66
|
+
this._bindHost = deps.bindHost ?? '0.0.0.0';
|
|
67
|
+
this._portOverride = deps.port;
|
|
68
|
+
}
|
|
69
|
+
get pairing() {
|
|
70
|
+
return this._pairing;
|
|
71
|
+
}
|
|
72
|
+
get bridge() {
|
|
73
|
+
return this._bridge;
|
|
74
|
+
}
|
|
75
|
+
get running() {
|
|
76
|
+
return this._running;
|
|
77
|
+
}
|
|
78
|
+
configuredPort() {
|
|
79
|
+
if (this._portOverride !== undefined)
|
|
80
|
+
return this._portOverride;
|
|
81
|
+
const raw = (0, desktop_db_1.getDesktopSetting)(this._db, SETTING.port);
|
|
82
|
+
const n = raw ? parseInt(raw, 10) : NaN;
|
|
83
|
+
return Number.isInteger(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT;
|
|
84
|
+
}
|
|
85
|
+
/** Read a setting, falling back to (and one-time migrating from) its
|
|
86
|
+
* pre-rebrand key. Legacy fallback — keeps values written by Specrails Hub. */
|
|
87
|
+
settingWithLegacyFallback(key, legacyKey) {
|
|
88
|
+
const v = (0, desktop_db_1.getDesktopSetting)(this._db, key);
|
|
89
|
+
if (v !== undefined)
|
|
90
|
+
return v;
|
|
91
|
+
const legacy = (0, desktop_db_1.getDesktopSetting)(this._db, legacyKey);
|
|
92
|
+
if (legacy !== undefined)
|
|
93
|
+
(0, desktop_db_1.setDesktopSetting)(this._db, key, legacy);
|
|
94
|
+
return legacy;
|
|
95
|
+
}
|
|
96
|
+
desktopName() {
|
|
97
|
+
const v = this.settingWithLegacyFallback(SETTING.name, LEGACY_SETTING.name);
|
|
98
|
+
if (v && v.trim())
|
|
99
|
+
return v;
|
|
100
|
+
try {
|
|
101
|
+
return os_1.default.hostname();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return 'Specrails';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
instanceId() {
|
|
108
|
+
let id = this.settingWithLegacyFallback(SETTING.instanceId, LEGACY_SETTING.instanceId);
|
|
109
|
+
if (!id) {
|
|
110
|
+
id = (0, crypto_1.randomUUID)();
|
|
111
|
+
(0, desktop_db_1.setDesktopSetting)(this._db, SETTING.instanceId, id);
|
|
112
|
+
}
|
|
113
|
+
return id;
|
|
114
|
+
}
|
|
115
|
+
mdnsEnabled() {
|
|
116
|
+
return (0, desktop_db_1.getDesktopSetting)(this._db, SETTING.mdns) !== 'false';
|
|
117
|
+
}
|
|
118
|
+
isEnabledSetting() {
|
|
119
|
+
return (0, desktop_db_1.getDesktopSetting)(this._db, SETTING.enabled) === 'true';
|
|
120
|
+
}
|
|
121
|
+
status() {
|
|
122
|
+
return {
|
|
123
|
+
enabled: this.isEnabledSetting(),
|
|
124
|
+
running: this._running,
|
|
125
|
+
port: this._running ? this._boundPort : this.configuredPort(),
|
|
126
|
+
certFingerprint: this._cert?.fingerprint ?? (0, desktop_db_1.getDesktopSetting)(this._db, SETTING.fingerprint) ?? null,
|
|
127
|
+
lanAddresses: lanAddresses(),
|
|
128
|
+
mdnsEnabled: this.mdnsEnabled(),
|
|
129
|
+
desktopName: this.desktopName(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Flip the persisted enable flag + (start|stop). */
|
|
133
|
+
async setEnabled(enabled) {
|
|
134
|
+
(0, desktop_db_1.setDesktopSetting)(this._db, SETTING.enabled, enabled ? 'true' : 'false');
|
|
135
|
+
if (enabled)
|
|
136
|
+
await this.start();
|
|
137
|
+
else
|
|
138
|
+
await this.stop();
|
|
139
|
+
this._broadcast({ type: 'mobile.gateway_state', running: this._running, port: this._boundPort, timestamp: new Date().toISOString() });
|
|
140
|
+
return this.status();
|
|
141
|
+
}
|
|
142
|
+
/** Idempotent. Loads/creates cert, binds the listener, starts the WS bridge. */
|
|
143
|
+
async start() {
|
|
144
|
+
if (this._running)
|
|
145
|
+
return;
|
|
146
|
+
// Sliding-expiry sweep on each (re)start.
|
|
147
|
+
try {
|
|
148
|
+
(0, mobile_devices_1.sweepExpiredDevices)(this._db);
|
|
149
|
+
}
|
|
150
|
+
catch { /* non-fatal */ }
|
|
151
|
+
this._cert = await (0, mobile_tls_1.loadOrCreateCert)((0, mobile_tls_1.mobileDir)());
|
|
152
|
+
(0, desktop_db_1.setDesktopSetting)(this._db, SETTING.fingerprint, this._cert.fingerprint);
|
|
153
|
+
this._pairing = new mobile_pairing_1.PairingManager({
|
|
154
|
+
certFingerprint: () => this._cert.fingerprint,
|
|
155
|
+
desktopInstanceId: () => this.instanceId(),
|
|
156
|
+
desktopName: () => this.desktopName(),
|
|
157
|
+
port: () => this._boundPort,
|
|
158
|
+
lanAddresses,
|
|
159
|
+
createDevice: ({ name, platform, token, certFingerprint }) => {
|
|
160
|
+
const row = (0, mobile_devices_1.createDevice)(this._db, { name, platform, tokenHash: (0, mobile_devices_1.hashToken)(token), certFingerprint });
|
|
161
|
+
this._broadcast({ type: 'mobile.device_paired', deviceId: row.id, name: row.name, timestamp: new Date().toISOString() });
|
|
162
|
+
return row.id;
|
|
163
|
+
},
|
|
164
|
+
onClaimed: (device) => {
|
|
165
|
+
this._broadcast({ type: 'mobile.pair_requested', deviceName: device.name, platform: device.platform, timestamp: new Date().toISOString() });
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const app = (0, express_1.default)();
|
|
169
|
+
app.use(express_1.default.json({ limit: '256kb' }));
|
|
170
|
+
app.use((0, mobile_router_1.createMobileRouter)({
|
|
171
|
+
db: this._db,
|
|
172
|
+
desktopPort: this._desktopPort,
|
|
173
|
+
currentFingerprint: () => this._cert.fingerprint,
|
|
174
|
+
pairing: this._pairing,
|
|
175
|
+
}));
|
|
176
|
+
const server = https_1.default.createServer({ cert: this._cert.certPem, key: this._cert.keyPem }, app);
|
|
177
|
+
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
178
|
+
const bridge = new mobile_ws_1.MobileWsBridge();
|
|
179
|
+
bridge.start();
|
|
180
|
+
server.on('upgrade', (request, socket, head) => {
|
|
181
|
+
if ((request.url ?? '').split('?')[0] !== '/mws') {
|
|
182
|
+
socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n');
|
|
183
|
+
socket.destroy();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const token = tokenFromUpgrade(request);
|
|
187
|
+
const device = (0, mobile_auth_1.resolveDevice)(this._db, token, this._cert.fingerprint);
|
|
188
|
+
if (!device) {
|
|
189
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
190
|
+
socket.destroy();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
194
|
+
bridge.attach(ws, device.id);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
const port = this.configuredPort();
|
|
198
|
+
await new Promise((resolve, reject) => {
|
|
199
|
+
const onError = (err) => {
|
|
200
|
+
server.removeListener('listening', onListening);
|
|
201
|
+
// EADDRINUSE (or any bind error) must NOT crash the sidecar — surface it.
|
|
202
|
+
reject(new Error(err.code === 'EADDRINUSE' ? `Port ${port} is already in use` : (err.message || 'listen failed')));
|
|
203
|
+
};
|
|
204
|
+
const onListening = () => {
|
|
205
|
+
server.removeListener('error', onError);
|
|
206
|
+
const addr = server.address();
|
|
207
|
+
this._boundPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
208
|
+
resolve();
|
|
209
|
+
};
|
|
210
|
+
server.once('error', onError);
|
|
211
|
+
server.once('listening', onListening);
|
|
212
|
+
server.listen(port, this._bindHost);
|
|
213
|
+
});
|
|
214
|
+
this._server = server;
|
|
215
|
+
this._wss = wss;
|
|
216
|
+
this._bridge = bridge;
|
|
217
|
+
this._running = true;
|
|
218
|
+
if (this.mdnsEnabled()) {
|
|
219
|
+
void (0, mobile_mdns_1.advertiseMdns)({
|
|
220
|
+
name: this.desktopName(),
|
|
221
|
+
port: this._boundPort,
|
|
222
|
+
instanceId: this.instanceId(),
|
|
223
|
+
fingerprint: this._cert.fingerprint,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Idempotent teardown. */
|
|
228
|
+
async stop() {
|
|
229
|
+
await (0, mobile_mdns_1.withdrawMdns)();
|
|
230
|
+
if (this._bridge) {
|
|
231
|
+
this._bridge.stop();
|
|
232
|
+
this._bridge = null;
|
|
233
|
+
}
|
|
234
|
+
if (this._wss) {
|
|
235
|
+
try {
|
|
236
|
+
this._wss.close();
|
|
237
|
+
}
|
|
238
|
+
catch { /* ignore */ }
|
|
239
|
+
this._wss = null;
|
|
240
|
+
}
|
|
241
|
+
if (this._server) {
|
|
242
|
+
await new Promise((resolve) => this._server.close(() => resolve()));
|
|
243
|
+
this._server = null;
|
|
244
|
+
}
|
|
245
|
+
this._running = false;
|
|
246
|
+
}
|
|
247
|
+
/** "Reset mobile identity": new cert + revoke every device + relisten. */
|
|
248
|
+
async rotateCert() {
|
|
249
|
+
await (0, mobile_tls_1.rotateCert)((0, mobile_tls_1.mobileDir)());
|
|
250
|
+
(0, mobile_devices_1.revokeAllDevices)(this._db);
|
|
251
|
+
const wasRunning = this._running;
|
|
252
|
+
if (wasRunning) {
|
|
253
|
+
await this.stop();
|
|
254
|
+
await this.start();
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
this._cert = await (0, mobile_tls_1.loadOrCreateCert)((0, mobile_tls_1.mobileDir)());
|
|
258
|
+
(0, desktop_db_1.setDesktopSetting)(this._db, SETTING.fingerprint, this._cert.fingerprint);
|
|
259
|
+
}
|
|
260
|
+
this._broadcast({ type: 'mobile.gateway_state', running: this._running, port: this._boundPort, timestamp: new Date().toISOString() });
|
|
261
|
+
return this.status();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.MobileGateway = MobileGateway;
|
|
265
|
+
/** Extract a device token from a /mws upgrade: Authorization header (native
|
|
266
|
+
* clients can set it) or a token-carrying subprotocol. */
|
|
267
|
+
function tokenFromUpgrade(request) {
|
|
268
|
+
const fake = { headers: request.headers };
|
|
269
|
+
const bearer = (0, mobile_auth_1.extractBearer)(fake);
|
|
270
|
+
if (bearer)
|
|
271
|
+
return bearer;
|
|
272
|
+
const proto = request.headers['sec-websocket-protocol'];
|
|
273
|
+
if (typeof proto === 'string') {
|
|
274
|
+
for (const part of proto.split(',')) {
|
|
275
|
+
const p = part.trim();
|
|
276
|
+
if (p.startsWith('desktop-token.'))
|
|
277
|
+
return p.slice('desktop-token.'.length).trim();
|
|
278
|
+
// mobile-app v1 wire compat — do not rename: the phone app (v1.0.0)
|
|
279
|
+
// carries its device token in a `hub-token.<token>` subprotocol.
|
|
280
|
+
if (p.startsWith('hub-token.'))
|
|
281
|
+
return p.slice('hub-token.'.length).trim();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Best-effort mDNS/DNS-SD advertising via @homebridge/ciao (pure-JS, RFC
|
|
3
|
+
// 6762/6763, Windows-safe). Discovery is a SECONDARY convenience — the QR is the
|
|
4
|
+
// primary path — so any failure here is logged and swallowed; the gateway works
|
|
5
|
+
// without it. TXT carries only { id, fp } (never a token or a revealing name).
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.advertiseMdns = advertiseMdns;
|
|
41
|
+
exports.withdrawMdns = withdrawMdns;
|
|
42
|
+
let responder = null;
|
|
43
|
+
let service = null;
|
|
44
|
+
async function advertiseMdns(opts) {
|
|
45
|
+
try {
|
|
46
|
+
const mod = (await Promise.resolve().then(() => __importStar(require('@homebridge/ciao'))));
|
|
47
|
+
const getResponder = mod.getResponder ?? mod.default?.getResponder;
|
|
48
|
+
if (!getResponder)
|
|
49
|
+
return false;
|
|
50
|
+
responder = getResponder();
|
|
51
|
+
service = responder.createService({
|
|
52
|
+
name: opts.name,
|
|
53
|
+
// mobile-app v1 wire compat — mDNS service type is frozen, do not rename.
|
|
54
|
+
type: 'specrailshub',
|
|
55
|
+
port: opts.port,
|
|
56
|
+
txt: { id: opts.instanceId, fp: opts.fingerprint },
|
|
57
|
+
});
|
|
58
|
+
await service.advertise();
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.warn('[mobile-mdns] advertise failed (non-fatal):', err instanceof Error ? err.message : err);
|
|
63
|
+
responder = null;
|
|
64
|
+
service = null;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function withdrawMdns() {
|
|
69
|
+
try {
|
|
70
|
+
if (service)
|
|
71
|
+
await service.end();
|
|
72
|
+
}
|
|
73
|
+
catch { /* ignore */ }
|
|
74
|
+
try {
|
|
75
|
+
if (responder)
|
|
76
|
+
await responder.shutdown();
|
|
77
|
+
}
|
|
78
|
+
catch { /* ignore */ }
|
|
79
|
+
service = null;
|
|
80
|
+
responder = null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PairingManager = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const auth_1 = require("../auth");
|
|
6
|
+
// In-memory pairing state machine. At most one pairing session is open at a time
|
|
7
|
+
// (the desktop opens it when the user clicks "Pair device"). Sessions, lockout
|
|
8
|
+
// counters, and the one-time token delivery live ONLY in memory — they are voided
|
|
9
|
+
// by a process restart/sleep/self-update, which is acceptable (paired devices
|
|
10
|
+
// survive in desktop.sqlite; an in-progress pair just restarts).
|
|
11
|
+
//
|
|
12
|
+
// Security (verified gaps from the adversarial review):
|
|
13
|
+
// - secret: 16 random bytes (128-bit), single-use, 60s TTL → no PAKE needed.
|
|
14
|
+
// - claimId: separate 16-byte handle; the token is delivered EXACTLY ONCE then
|
|
15
|
+
// scrubbed (a later poll returns approved-without-token).
|
|
16
|
+
// - lockout: per-IP (5 bad secrets → 60s) AND a global attempt cap (defeats
|
|
17
|
+
// IPv6 privacy-address rotation) that destroys the session on breach.
|
|
18
|
+
// - desktop Approve click gates token issuance (QR possession is not enough).
|
|
19
|
+
// 5 minutes: long enough for a manual copy→paste→type flow (e.g. pairing a
|
|
20
|
+
// simulator with no camera) while still single-use + gated by the desktop
|
|
21
|
+
// approval click + a 128-bit secret, so the leaked-QR window stays small.
|
|
22
|
+
const DEFAULT_TTL_MS = 300_000;
|
|
23
|
+
const PER_IP_MAX_FAILS = 5;
|
|
24
|
+
const PER_IP_LOCKOUT_MS = 60_000;
|
|
25
|
+
const GLOBAL_WINDOW_MS = 60_000;
|
|
26
|
+
const GLOBAL_MAX_ATTEMPTS = 20;
|
|
27
|
+
class PairingManager {
|
|
28
|
+
_session = null;
|
|
29
|
+
_perIp = new Map();
|
|
30
|
+
_globalAttempts = [];
|
|
31
|
+
_deps;
|
|
32
|
+
constructor(deps) {
|
|
33
|
+
this._deps = {
|
|
34
|
+
...deps,
|
|
35
|
+
clock: deps.clock ?? (() => Date.now()),
|
|
36
|
+
genBytes: deps.genBytes ?? ((n) => (0, crypto_1.randomBytes)(n)),
|
|
37
|
+
ttlMs: deps.ttlMs ?? DEFAULT_TTL_MS,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
now() {
|
|
41
|
+
return this._deps.clock();
|
|
42
|
+
}
|
|
43
|
+
b64url(n) {
|
|
44
|
+
return this._deps.genBytes(n).toString('base64url');
|
|
45
|
+
}
|
|
46
|
+
/** Open (or replace) a pairing session and return the QR payload to render. */
|
|
47
|
+
createSession() {
|
|
48
|
+
const now = this.now();
|
|
49
|
+
const session = {
|
|
50
|
+
secret: this.b64url(16),
|
|
51
|
+
claimId: this.b64url(16),
|
|
52
|
+
exp: now + this._deps.ttlMs,
|
|
53
|
+
status: 'pending',
|
|
54
|
+
delivered: false,
|
|
55
|
+
};
|
|
56
|
+
this._session = session;
|
|
57
|
+
this._perIp.clear();
|
|
58
|
+
this._globalAttempts = [];
|
|
59
|
+
return {
|
|
60
|
+
v: 1,
|
|
61
|
+
// mobile-app v1 wire compat — QR field name `hub` is frozen, do not rename.
|
|
62
|
+
hub: this._deps.desktopInstanceId(),
|
|
63
|
+
name: this._deps.desktopName(),
|
|
64
|
+
addrs: this._deps.lanAddresses(),
|
|
65
|
+
port: this._deps.port(),
|
|
66
|
+
fp: this._deps.certFingerprint(),
|
|
67
|
+
secret: session.secret,
|
|
68
|
+
claimId: session.claimId,
|
|
69
|
+
exp: Math.floor(session.exp / 1000),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
isExpired(s) {
|
|
73
|
+
return this.now() > s.exp && s.status !== 'approved';
|
|
74
|
+
}
|
|
75
|
+
/** Phone submits the QR secret. Enforces lockout + single-claim. */
|
|
76
|
+
claim(secret, device, ip) {
|
|
77
|
+
const s = this._session;
|
|
78
|
+
if (!s)
|
|
79
|
+
return { ok: false, reason: 'no-session' };
|
|
80
|
+
if (this.isExpired(s)) {
|
|
81
|
+
this._session = null;
|
|
82
|
+
return { ok: false, reason: 'expired' };
|
|
83
|
+
}
|
|
84
|
+
// Per-IP lockout
|
|
85
|
+
const rec = this._perIp.get(ip);
|
|
86
|
+
if (rec && rec.until > this.now())
|
|
87
|
+
return { ok: false, reason: 'locked' };
|
|
88
|
+
// Global attempt cap (sliding 60s window) — defeats IPv6 privacy rotation.
|
|
89
|
+
const now = this.now();
|
|
90
|
+
this._globalAttempts = this._globalAttempts.filter((t) => now - t < GLOBAL_WINDOW_MS);
|
|
91
|
+
this._globalAttempts.push(now);
|
|
92
|
+
if (this._globalAttempts.length > GLOBAL_MAX_ATTEMPTS) {
|
|
93
|
+
this._session = null;
|
|
94
|
+
return { ok: false, reason: 'locked' };
|
|
95
|
+
}
|
|
96
|
+
if (s.status !== 'pending')
|
|
97
|
+
return { ok: false, reason: 'already-claimed' };
|
|
98
|
+
if (!(0, auth_1.safeEqual)(secret, s.secret)) {
|
|
99
|
+
const next = { count: (rec?.count ?? 0) + 1, until: rec?.until ?? 0 };
|
|
100
|
+
if (next.count >= PER_IP_MAX_FAILS)
|
|
101
|
+
next.until = now + PER_IP_LOCKOUT_MS;
|
|
102
|
+
this._perIp.set(ip, next);
|
|
103
|
+
return { ok: false, reason: 'invalid' };
|
|
104
|
+
}
|
|
105
|
+
s.status = 'claimed';
|
|
106
|
+
s.device = { name: device.name.slice(0, 80) || 'Unknown device', platform: device.platform };
|
|
107
|
+
try {
|
|
108
|
+
this._deps.onClaimed?.(s.device);
|
|
109
|
+
}
|
|
110
|
+
catch { /* non-fatal */ }
|
|
111
|
+
return { ok: true };
|
|
112
|
+
}
|
|
113
|
+
/** Desktop approves → issue a per-device token (delivered once via pollStatus). */
|
|
114
|
+
approve() {
|
|
115
|
+
const s = this._session;
|
|
116
|
+
if (!s)
|
|
117
|
+
return { ok: false, reason: 'no-session' };
|
|
118
|
+
if (s.status !== 'claimed' || !s.device)
|
|
119
|
+
return { ok: false, reason: 'not-claimed' };
|
|
120
|
+
const token = this._deps.genBytes(32).toString('hex');
|
|
121
|
+
const deviceId = this._deps.createDevice({
|
|
122
|
+
name: s.device.name,
|
|
123
|
+
platform: s.device.platform,
|
|
124
|
+
token,
|
|
125
|
+
certFingerprint: this._deps.certFingerprint(),
|
|
126
|
+
});
|
|
127
|
+
s.approved = {
|
|
128
|
+
approved: true,
|
|
129
|
+
deviceToken: token,
|
|
130
|
+
deviceId,
|
|
131
|
+
// mobile-app v1 wire compat — `hubName`/`hubInstanceId` field names are
|
|
132
|
+
// frozen (delivered to the phone via /pair/status), do not rename.
|
|
133
|
+
hubName: this._deps.desktopName(),
|
|
134
|
+
hubInstanceId: this._deps.desktopInstanceId(),
|
|
135
|
+
};
|
|
136
|
+
s.status = 'approved';
|
|
137
|
+
return { ok: true };
|
|
138
|
+
}
|
|
139
|
+
deny() {
|
|
140
|
+
if (this._session)
|
|
141
|
+
this._session.status = 'denied';
|
|
142
|
+
}
|
|
143
|
+
cancel() {
|
|
144
|
+
this._session = null;
|
|
145
|
+
}
|
|
146
|
+
/** Desktop UI poll. */
|
|
147
|
+
getDesktopState() {
|
|
148
|
+
const s = this._session;
|
|
149
|
+
if (!s)
|
|
150
|
+
return null;
|
|
151
|
+
const status = this.isExpired(s) ? 'expired' : s.status;
|
|
152
|
+
return { status, claimId: s.claimId, device: s.device };
|
|
153
|
+
}
|
|
154
|
+
/** Phone poll. Delivers the token EXACTLY ONCE, then scrubs it. */
|
|
155
|
+
pollStatus(claimId) {
|
|
156
|
+
const s = this._session;
|
|
157
|
+
if (!s || !(0, auth_1.safeEqual)(claimId, s.claimId))
|
|
158
|
+
return { status: 'expired' };
|
|
159
|
+
if (s.status === 'approved' && s.approved) {
|
|
160
|
+
if (!s.delivered) {
|
|
161
|
+
s.delivered = true;
|
|
162
|
+
const result = s.approved;
|
|
163
|
+
// Scrub the token from memory after the single delivery.
|
|
164
|
+
s.approved = { ...result, deviceToken: '' };
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
// Already delivered — never hand the token out twice.
|
|
168
|
+
return { status: 'expired' };
|
|
169
|
+
}
|
|
170
|
+
if (this.isExpired(s))
|
|
171
|
+
return { status: 'expired' };
|
|
172
|
+
if (s.status === 'denied')
|
|
173
|
+
return { status: 'denied' };
|
|
174
|
+
if (s.status === 'claimed')
|
|
175
|
+
return { status: 'claimed' };
|
|
176
|
+
return { status: 'pending' };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.PairingManager = PairingManager;
|
|
@@ -0,0 +1,53 @@
|
|
|
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.stripHome = stripHome;
|
|
7
|
+
exports.redact = redact;
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
// Information-minimisation for everything the gateway hands a phone. Two rules:
|
|
10
|
+
// 1. Drop same-machine keys outright (filesystem paths, db handles).
|
|
11
|
+
// 2. Replace the user's home directory inside any remaining string with `~`
|
|
12
|
+
// so a `command` like `/specrails:implement #34` keeps its useful shape but
|
|
13
|
+
// an embedded absolute path (or a stray cwd) leaks no local layout.
|
|
14
|
+
//
|
|
15
|
+
// Applied to EVERY proxied REST JSON body and EVERY outbound WS payload.
|
|
16
|
+
const SENSITIVE_KEYS = new Set(['path', 'db_path', 'dbPath', 'absolutePath', 'cwd', 'filePath', 'projectPath']);
|
|
17
|
+
function homeDir() {
|
|
18
|
+
try {
|
|
19
|
+
return os_1.default.homedir();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Replace occurrences of the user's home dir with `~` (covers the common leak:
|
|
26
|
+
* project paths live under ~/...). Cheap and safe — never mangles normal text. */
|
|
27
|
+
function stripHome(s) {
|
|
28
|
+
const home = homeDir();
|
|
29
|
+
if (!home)
|
|
30
|
+
return s;
|
|
31
|
+
return s.split(home).join('~');
|
|
32
|
+
}
|
|
33
|
+
/** Deep-redact a JSON-serialisable value: drop sensitive keys, scrub home dir in
|
|
34
|
+
* strings. Returns a new structure; never mutates the input. */
|
|
35
|
+
function redact(value) {
|
|
36
|
+
return _redact(value);
|
|
37
|
+
}
|
|
38
|
+
function _redact(value) {
|
|
39
|
+
if (typeof value === 'string')
|
|
40
|
+
return stripHome(value);
|
|
41
|
+
if (Array.isArray(value))
|
|
42
|
+
return value.map(_redact);
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const [k, v] of Object.entries(value)) {
|
|
46
|
+
if (SENSITIVE_KEYS.has(k))
|
|
47
|
+
continue;
|
|
48
|
+
out[k] = _redact(v);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|