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,317 @@
|
|
|
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.redactSecrets = redactSecrets;
|
|
7
|
+
exports.createDiagnosticZip = createDiagnosticZip;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
10
|
+
// ─── Minimal ZIP writer ───────────────────────────────────────────────────────
|
|
11
|
+
// ZIP format is simple: local file headers + data + central directory + EOCD.
|
|
12
|
+
// We write stored (no compression) entries for simplicity — the outer gzip on
|
|
13
|
+
// the blob is already compressed; NDJSON text compresses fine at transfer layer.
|
|
14
|
+
function crc32(buf) {
|
|
15
|
+
// Standard CRC-32 polynomial table
|
|
16
|
+
const table = crc32Table();
|
|
17
|
+
let crc = 0xffffffff;
|
|
18
|
+
for (let i = 0; i < buf.length; i++) {
|
|
19
|
+
crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xff];
|
|
20
|
+
}
|
|
21
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
22
|
+
}
|
|
23
|
+
let _crcTable = null;
|
|
24
|
+
function crc32Table() {
|
|
25
|
+
if (_crcTable)
|
|
26
|
+
return _crcTable;
|
|
27
|
+
_crcTable = new Uint32Array(256);
|
|
28
|
+
for (let i = 0; i < 256; i++) {
|
|
29
|
+
let c = i;
|
|
30
|
+
for (let k = 0; k < 8; k++) {
|
|
31
|
+
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
32
|
+
}
|
|
33
|
+
_crcTable[i] = c;
|
|
34
|
+
}
|
|
35
|
+
return _crcTable;
|
|
36
|
+
}
|
|
37
|
+
function buildZip(entries) {
|
|
38
|
+
const localHeaders = [];
|
|
39
|
+
const centralDirs = [];
|
|
40
|
+
let offset = 0;
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const nameBytes = Buffer.from(entry.name, 'utf-8');
|
|
43
|
+
const data = entry.data;
|
|
44
|
+
const crc = crc32(data);
|
|
45
|
+
const size = data.length;
|
|
46
|
+
// Local file header (30 bytes + name)
|
|
47
|
+
const local = Buffer.alloc(30 + nameBytes.length);
|
|
48
|
+
local.writeUInt32LE(0x04034b50, 0); // signature
|
|
49
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
50
|
+
local.writeUInt16LE(0, 6); // flags
|
|
51
|
+
local.writeUInt16LE(0, 8); // compression = STORED
|
|
52
|
+
local.writeUInt16LE(0, 10); // mod time
|
|
53
|
+
local.writeUInt16LE(0, 12); // mod date
|
|
54
|
+
local.writeUInt32LE(crc, 14); // crc-32
|
|
55
|
+
local.writeUInt32LE(size, 18); // compressed size
|
|
56
|
+
local.writeUInt32LE(size, 22); // uncompressed size
|
|
57
|
+
local.writeUInt16LE(nameBytes.length, 26); // file name length
|
|
58
|
+
local.writeUInt16LE(0, 28); // extra field length
|
|
59
|
+
nameBytes.copy(local, 30);
|
|
60
|
+
// Central directory header (46 bytes + name)
|
|
61
|
+
const central = Buffer.alloc(46 + nameBytes.length);
|
|
62
|
+
central.writeUInt32LE(0x02014b50, 0); // signature
|
|
63
|
+
central.writeUInt16LE(20, 4); // version made by
|
|
64
|
+
central.writeUInt16LE(20, 6); // version needed
|
|
65
|
+
central.writeUInt16LE(0, 8); // flags
|
|
66
|
+
central.writeUInt16LE(0, 10); // compression = STORED
|
|
67
|
+
central.writeUInt16LE(0, 12); // mod time
|
|
68
|
+
central.writeUInt16LE(0, 14); // mod date
|
|
69
|
+
central.writeUInt32LE(crc, 16); // crc-32
|
|
70
|
+
central.writeUInt32LE(size, 20); // compressed size
|
|
71
|
+
central.writeUInt32LE(size, 24); // uncompressed size
|
|
72
|
+
central.writeUInt16LE(nameBytes.length, 28); // file name length
|
|
73
|
+
central.writeUInt16LE(0, 30); // extra field length
|
|
74
|
+
central.writeUInt16LE(0, 32); // file comment length
|
|
75
|
+
central.writeUInt16LE(0, 34); // disk number start
|
|
76
|
+
central.writeUInt16LE(0, 36); // int file attributes
|
|
77
|
+
central.writeUInt32LE(0, 38); // ext file attributes
|
|
78
|
+
central.writeUInt32LE(offset, 42); // relative offset of local header
|
|
79
|
+
nameBytes.copy(central, 46);
|
|
80
|
+
localHeaders.push(local, data);
|
|
81
|
+
centralDirs.push(central);
|
|
82
|
+
offset += local.length + size;
|
|
83
|
+
}
|
|
84
|
+
const centralDirBuf = Buffer.concat(centralDirs);
|
|
85
|
+
const centralDirSize = centralDirBuf.length;
|
|
86
|
+
const centralDirOffset = offset;
|
|
87
|
+
// End of central directory record
|
|
88
|
+
const eocd = Buffer.alloc(22);
|
|
89
|
+
eocd.writeUInt32LE(0x06054b50, 0); // signature
|
|
90
|
+
eocd.writeUInt16LE(0, 4); // disk number
|
|
91
|
+
eocd.writeUInt16LE(0, 6); // disk with CD
|
|
92
|
+
eocd.writeUInt16LE(entries.length, 8); // entries on this disk
|
|
93
|
+
eocd.writeUInt16LE(entries.length, 10); // total entries
|
|
94
|
+
eocd.writeUInt32LE(centralDirSize, 12); // central dir size
|
|
95
|
+
eocd.writeUInt32LE(centralDirOffset, 16); // central dir offset
|
|
96
|
+
eocd.writeUInt16LE(0, 20); // comment length
|
|
97
|
+
return Buffer.concat([...localHeaders, centralDirBuf, eocd]);
|
|
98
|
+
}
|
|
99
|
+
// ─── NDJSON reader ────────────────────────────────────────────────────────────
|
|
100
|
+
function decompressNdjson(filePath) {
|
|
101
|
+
try {
|
|
102
|
+
const compressed = fs_1.default.readFileSync(filePath);
|
|
103
|
+
return zlib_1.default.gunzipSync(compressed).toString('utf-8');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function hasTruncationMarker(filePath) {
|
|
110
|
+
const content = decompressNdjson(filePath);
|
|
111
|
+
return content.includes('"logs_truncated"');
|
|
112
|
+
}
|
|
113
|
+
// ─── Summary markdown ─────────────────────────────────────────────────────────
|
|
114
|
+
function buildSummaryFromRaw(filePath, job, truncated) {
|
|
115
|
+
const lines = [];
|
|
116
|
+
if (truncated) {
|
|
117
|
+
lines.push('> **truncated: true** — The 10 MB raw telemetry cap was hit during this run.');
|
|
118
|
+
lines.push('> Log payloads were dropped after the cap. Traces and metrics are complete.');
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
121
|
+
lines.push(`# Diagnostic Summary — Job ${job.id}`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
lines.push(`- **Command**: \`${job.command}\``);
|
|
124
|
+
lines.push(`- **Status**: ${job.status}`);
|
|
125
|
+
lines.push(`- **Started**: ${job.started_at}`);
|
|
126
|
+
lines.push(`- **Finished**: ${job.finished_at ?? 'N/A'}`);
|
|
127
|
+
if (job.duration_ms != null)
|
|
128
|
+
lines.push(`- **Duration**: ${job.duration_ms}ms`);
|
|
129
|
+
if (job.total_cost_usd != null)
|
|
130
|
+
lines.push(`- **Cost**: $${job.total_cost_usd.toFixed(4)}`);
|
|
131
|
+
if (job.tokens_in != null)
|
|
132
|
+
lines.push(`- **Tokens in**: ${job.tokens_in}`);
|
|
133
|
+
if (job.tokens_out != null)
|
|
134
|
+
lines.push(`- **Tokens out**: ${job.tokens_out}`);
|
|
135
|
+
lines.push('');
|
|
136
|
+
lines.push('Raw telemetry is available in `telemetry.ndjson`.');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
function buildSummaryFromRows(summaries, job) {
|
|
140
|
+
const lines = [];
|
|
141
|
+
lines.push(`# Diagnostic Summary — Job ${job.id}`);
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(`- **Command**: \`${job.command}\``);
|
|
144
|
+
lines.push(`- **Status**: ${job.status}`);
|
|
145
|
+
lines.push(`- **Started**: ${job.started_at}`);
|
|
146
|
+
lines.push(`- **Finished**: ${job.finished_at ?? 'N/A'}`);
|
|
147
|
+
lines.push('');
|
|
148
|
+
lines.push('> Raw telemetry has been compacted (older than 7 days). Per-phase summary below.');
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push('## Phase Summaries');
|
|
151
|
+
lines.push('');
|
|
152
|
+
for (const s of summaries) {
|
|
153
|
+
lines.push(`### Phase: ${s.phase}`);
|
|
154
|
+
if (s.durationMs != null)
|
|
155
|
+
lines.push(`- Duration: ${s.durationMs}ms`);
|
|
156
|
+
if (s.tokensInput != null)
|
|
157
|
+
lines.push(`- Tokens in: ${s.tokensInput}`);
|
|
158
|
+
if (s.tokensOutput != null)
|
|
159
|
+
lines.push(`- Tokens out: ${s.tokensOutput}`);
|
|
160
|
+
if (s.tokensCache != null)
|
|
161
|
+
lines.push(`- Cache tokens: ${s.tokensCache}`);
|
|
162
|
+
if (s.costUsd != null)
|
|
163
|
+
lines.push(`- Cost: $${s.costUsd.toFixed(4)}`);
|
|
164
|
+
if (s.apiErrors)
|
|
165
|
+
lines.push(`- API errors: ${s.apiErrors}`);
|
|
166
|
+
if (s.toolCalls) {
|
|
167
|
+
try {
|
|
168
|
+
const tc = JSON.parse(s.toolCalls);
|
|
169
|
+
lines.push(`- Tool calls: ${Object.entries(tc).map(([k, v]) => `${k}×${v}`).join(', ')}`);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
lines.push(`- Tool calls: ${s.toolCalls}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
lines.push('');
|
|
176
|
+
}
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
// ─── Log extraction ────────────────────────────────────────────────────────────
|
|
180
|
+
// App log output is never written to a flat file — it is streamed over
|
|
181
|
+
// WebSocket and persisted per-event in the `events` table. Reconstruct a
|
|
182
|
+
// readable logs.txt by formatting every event row in seq order.
|
|
183
|
+
function buildLogsFromEvents(events) {
|
|
184
|
+
if (events.length === 0) {
|
|
185
|
+
return '(No events recorded for this job.)\n';
|
|
186
|
+
}
|
|
187
|
+
const lines = [];
|
|
188
|
+
for (const ev of events) {
|
|
189
|
+
const ts = ev.timestamp;
|
|
190
|
+
const src = ev.source ?? ev.event_type;
|
|
191
|
+
let text = ev.payload;
|
|
192
|
+
// Most log events carry { line: string }; other event types may carry
|
|
193
|
+
// structured JSON. Extract the line field when present, else stringify.
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(ev.payload);
|
|
196
|
+
if (typeof parsed.line === 'string')
|
|
197
|
+
text = parsed.line;
|
|
198
|
+
else if (typeof parsed.message === 'string')
|
|
199
|
+
text = parsed.message;
|
|
200
|
+
else
|
|
201
|
+
text = JSON.stringify(parsed);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Payload isn't JSON — use raw string as-is
|
|
205
|
+
}
|
|
206
|
+
lines.push(`[${ts}] [${src}] ${redactSecrets(text)}`);
|
|
207
|
+
}
|
|
208
|
+
return lines.join('\n') + '\n';
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Best-effort redaction of obvious secrets from diagnostic log text (B52). The
|
|
212
|
+
* exported ZIP is meant to be shared (support, bug reports), but the CLI event
|
|
213
|
+
* log can echo `export ANTHROPIC_API_KEY=...`, Bearer tokens, or provider keys.
|
|
214
|
+
* This scrubs the common shapes without destroying the log's diagnostic value.
|
|
215
|
+
*/
|
|
216
|
+
function redactSecrets(text) {
|
|
217
|
+
return text
|
|
218
|
+
// KEY=VALUE / TOKEN: VALUE for anything ending in API_KEY/TOKEN/SECRET/PASSWORD/ACCESS_KEY
|
|
219
|
+
.replace(/\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|ACCESS_KEY))(\s*[=:]\s*)(["']?)[^\s"']+\3/gi, '$1$2[REDACTED]')
|
|
220
|
+
// Provider key prefixes (Anthropic/OpenAI sk-..., GitHub gh*_...)
|
|
221
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}/g, 'sk-[REDACTED]')
|
|
222
|
+
.replace(/\bgh[pousr]_[A-Za-z0-9]{16,}/g, 'gh_[REDACTED]')
|
|
223
|
+
// Authorization: Bearer <token>
|
|
224
|
+
.replace(/\b(Bearer)\s+[A-Za-z0-9._-]{12,}/gi, '$1 [REDACTED]');
|
|
225
|
+
}
|
|
226
|
+
async function createDiagnosticZip(res, opts) {
|
|
227
|
+
const { job, blob, summaries, events, profile, pluginSnapshotPath } = opts;
|
|
228
|
+
const entries = [];
|
|
229
|
+
// plugins.json — per-job plugin snapshot, if present.
|
|
230
|
+
let pluginsSummary = null;
|
|
231
|
+
if (pluginSnapshotPath && fs_1.default.existsSync(pluginSnapshotPath)) {
|
|
232
|
+
try {
|
|
233
|
+
const raw = fs_1.default.readFileSync(pluginSnapshotPath, 'utf8');
|
|
234
|
+
pluginsSummary = JSON.parse(raw);
|
|
235
|
+
entries.push({ name: 'plugins.json', data: Buffer.from(raw, 'utf-8') });
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// ignore malformed snapshot — diagnostic still useful without it
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// job-metadata.json
|
|
242
|
+
const metadata = {
|
|
243
|
+
id: job.id,
|
|
244
|
+
command: job.command,
|
|
245
|
+
status: job.status,
|
|
246
|
+
started_at: job.started_at,
|
|
247
|
+
finished_at: job.finished_at,
|
|
248
|
+
duration_ms: job.duration_ms,
|
|
249
|
+
total_cost_usd: job.total_cost_usd,
|
|
250
|
+
tokens_in: job.tokens_in,
|
|
251
|
+
tokens_out: job.tokens_out,
|
|
252
|
+
tokens_cache_read: job.tokens_cache_read,
|
|
253
|
+
tokens_cache_create: job.tokens_cache_create,
|
|
254
|
+
num_turns: job.num_turns,
|
|
255
|
+
model: job.model,
|
|
256
|
+
session_id: job.session_id,
|
|
257
|
+
};
|
|
258
|
+
entries.push({
|
|
259
|
+
name: 'job-metadata.json',
|
|
260
|
+
data: Buffer.from(JSON.stringify({ ...metadata, profile_name: profile?.name ?? null }, null, 2), 'utf-8'),
|
|
261
|
+
});
|
|
262
|
+
// profile.json — the snapshot the job ran under (if any)
|
|
263
|
+
if (profile && profile.json) {
|
|
264
|
+
entries.push({
|
|
265
|
+
name: 'profile.json',
|
|
266
|
+
data: Buffer.from(profile.json, 'utf-8'),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// telemetry.ndjson
|
|
270
|
+
let truncated = false;
|
|
271
|
+
if (blob.state === 'active' && blob.path && fs_1.default.existsSync(blob.path)) {
|
|
272
|
+
truncated = hasTruncationMarker(blob.path);
|
|
273
|
+
const ndjsonContent = decompressNdjson(blob.path);
|
|
274
|
+
entries.push({
|
|
275
|
+
name: 'telemetry.ndjson',
|
|
276
|
+
data: Buffer.from(ndjsonContent, 'utf-8'),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Compacted: include a header note
|
|
281
|
+
const header = '# Telemetry data has been compacted (raw blob older than 7 days).\n# See summary.md for per-phase aggregates.\n';
|
|
282
|
+
entries.push({
|
|
283
|
+
name: 'telemetry.ndjson',
|
|
284
|
+
data: Buffer.from(header, 'utf-8'),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// logs.txt — reconstructed from persisted events
|
|
288
|
+
const logsContent = buildLogsFromEvents(events);
|
|
289
|
+
entries.push({
|
|
290
|
+
name: 'logs.txt',
|
|
291
|
+
data: Buffer.from(logsContent, 'utf-8'),
|
|
292
|
+
});
|
|
293
|
+
// summary.md
|
|
294
|
+
let summaryContent;
|
|
295
|
+
if (blob.state === 'active' && blob.path) {
|
|
296
|
+
summaryContent = buildSummaryFromRaw(blob.path, job, truncated);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
summaryContent = buildSummaryFromRows(summaries, job);
|
|
300
|
+
}
|
|
301
|
+
if (pluginsSummary) {
|
|
302
|
+
const lines = ['', '## Plugins'];
|
|
303
|
+
if (pluginsSummary.active.length > 0) {
|
|
304
|
+
lines.push('', '### Active', ...pluginsSummary.active.map((p) => `- ${p.name}@${p.version}`));
|
|
305
|
+
}
|
|
306
|
+
if (pluginsSummary.degraded.length > 0) {
|
|
307
|
+
lines.push('', '### Degraded', ...pluginsSummary.degraded.map((p) => `- ${p.name} (${p.reason})`));
|
|
308
|
+
}
|
|
309
|
+
summaryContent += '\n' + lines.join('\n') + '\n';
|
|
310
|
+
}
|
|
311
|
+
entries.push({
|
|
312
|
+
name: 'summary.md',
|
|
313
|
+
data: Buffer.from(summaryContent, 'utf-8'),
|
|
314
|
+
});
|
|
315
|
+
const zipBuf = buildZip(entries);
|
|
316
|
+
res.end(zipBuf);
|
|
317
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
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.createTelemetryRouter = createTelemetryRouter;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
11
|
+
const express_1 = require("express");
|
|
12
|
+
const db_1 = require("./db");
|
|
13
|
+
const db_2 = require("./db");
|
|
14
|
+
// 10 MB uncompressed cap per job blob
|
|
15
|
+
const BLOB_SIZE_CAP = 10 * 1024 * 1024;
|
|
16
|
+
// B2: the soft cap above only dropped `logs`, so traces/metrics could grow a
|
|
17
|
+
// per-job blob without bound (disk-fill DoS). This hard cap bounds the TOTAL
|
|
18
|
+
// blob across ALL signals while still letting traces/metrics outlive logs.
|
|
19
|
+
const BLOB_HARD_CAP = 50 * 1024 * 1024;
|
|
20
|
+
// Bounded in-memory append queue per (projectId, jobId) key — protects the
|
|
21
|
+
// Express event loop from a burst of OTLP payloads all trying to gzip-append
|
|
22
|
+
// simultaneously. Drops events (with a warning) when the queue exceeds this.
|
|
23
|
+
const QUEUE_CAP = 10_000;
|
|
24
|
+
const _blobState = new Map();
|
|
25
|
+
function getBlobState(key) {
|
|
26
|
+
let s = _blobState.get(key);
|
|
27
|
+
if (!s) {
|
|
28
|
+
s = { uncompressedSize: 0, truncationMarkerWritten: false, queue: [], writing: false };
|
|
29
|
+
_blobState.set(key, s);
|
|
30
|
+
}
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
function blobKey(projectId, jobId) {
|
|
34
|
+
return `${projectId}:${jobId}`;
|
|
35
|
+
}
|
|
36
|
+
// ─── Blob path ────────────────────────────────────────────────────────────────
|
|
37
|
+
function telemetryDir(projectSlug) {
|
|
38
|
+
return path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', projectSlug, 'telemetry');
|
|
39
|
+
}
|
|
40
|
+
function blobPath(projectSlug, jobId) {
|
|
41
|
+
return path_1.default.join(telemetryDir(projectSlug), `${jobId}.ndjson.gz`);
|
|
42
|
+
}
|
|
43
|
+
// ─── Extract resource attributes ─────────────────────────────────────────────
|
|
44
|
+
function extractAttr(attributes, key) {
|
|
45
|
+
if (!attributes)
|
|
46
|
+
return undefined;
|
|
47
|
+
const attr = attributes.find((a) => a.key === key);
|
|
48
|
+
if (!attr)
|
|
49
|
+
return undefined;
|
|
50
|
+
const v = attr.value;
|
|
51
|
+
if (typeof v.stringValue === 'string')
|
|
52
|
+
return v.stringValue;
|
|
53
|
+
if (v.intValue != null)
|
|
54
|
+
return String(v.intValue);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function extractJobAndProject(body) {
|
|
58
|
+
if (typeof body !== 'object' || body === null)
|
|
59
|
+
return null;
|
|
60
|
+
// OTLP/JSON: resourceSpans | resourceMetrics | resourceLogs
|
|
61
|
+
const b = body;
|
|
62
|
+
const resourceItems = [
|
|
63
|
+
...(Array.isArray(b.resourceSpans) ? b.resourceSpans : []),
|
|
64
|
+
...(Array.isArray(b.resourceMetrics) ? b.resourceMetrics : []),
|
|
65
|
+
...(Array.isArray(b.resourceLogs) ? b.resourceLogs : []),
|
|
66
|
+
];
|
|
67
|
+
for (const rs of resourceItems) {
|
|
68
|
+
const attrs = rs.resource?.attributes;
|
|
69
|
+
const jobId = extractAttr(attrs, 'specrails.job_id');
|
|
70
|
+
const projectId = extractAttr(attrs, 'specrails.project_id');
|
|
71
|
+
if (jobId && projectId)
|
|
72
|
+
return { jobId, projectId };
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
// ─── Gzip append ─────────────────────────────────────────────────────────────
|
|
77
|
+
function appendToGzip(filePath, line) {
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const data = Buffer.from(line + '\n', 'utf-8');
|
|
80
|
+
// Append a gzip member to the file. gzip is a concatenated format:
|
|
81
|
+
// a reader decompresses member by member, giving correct NDJSON.
|
|
82
|
+
const gz = zlib_1.default.createGzip();
|
|
83
|
+
const chunks = [];
|
|
84
|
+
gz.on('data', (c) => chunks.push(c));
|
|
85
|
+
gz.on('end', () => {
|
|
86
|
+
const compressed = Buffer.concat(chunks);
|
|
87
|
+
fs_1.default.appendFile(filePath, compressed, (err) => {
|
|
88
|
+
if (err)
|
|
89
|
+
reject(err);
|
|
90
|
+
else
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
gz.on('error', reject);
|
|
95
|
+
gz.end(data);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// ─── Enqueue write task ───────────────────────────────────────────────────────
|
|
99
|
+
function enqueueWrite(state, task) {
|
|
100
|
+
if (state.queue.length >= QUEUE_CAP) {
|
|
101
|
+
// Drop with a warning — we don't want to stall the event loop
|
|
102
|
+
console.warn('[telemetry-receiver] append queue full — dropping telemetry event');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const wrappedTask = () => {
|
|
106
|
+
task().then(drain, drain);
|
|
107
|
+
};
|
|
108
|
+
function drain() {
|
|
109
|
+
state.writing = false;
|
|
110
|
+
const next = state.queue.shift();
|
|
111
|
+
if (next) {
|
|
112
|
+
state.writing = true;
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
state.queue.push(wrappedTask);
|
|
117
|
+
if (!state.writing) {
|
|
118
|
+
state.writing = true;
|
|
119
|
+
const next = state.queue.shift();
|
|
120
|
+
next();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ─── Ingest handler ───────────────────────────────────────────────────────────
|
|
124
|
+
async function handleIngest(signal, body, registry, res) {
|
|
125
|
+
const ids = extractJobAndProject(body);
|
|
126
|
+
if (!ids) {
|
|
127
|
+
res.status(400).json({ error: 'Missing specrails.job_id or specrails.project_id in resource.attributes' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const { jobId, projectId } = ids;
|
|
131
|
+
const projectCtx = registry.getContext(projectId);
|
|
132
|
+
if (!projectCtx) {
|
|
133
|
+
res.status(404).json({ error: 'Project not found' });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { db, project } = projectCtx;
|
|
137
|
+
const job = (0, db_1.getJob)(db, jobId);
|
|
138
|
+
if (!job) {
|
|
139
|
+
res.status(404).json({ error: 'Job not found' });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const key = blobKey(projectId, jobId);
|
|
143
|
+
const state = getBlobState(key);
|
|
144
|
+
const filePath = blobPath(project.slug, jobId);
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
// Cap enforcement: drop logs once the 10 MB soft cap is reached (B2: and drop
|
|
147
|
+
// ANY signal — traces/metrics included — once the 50 MB hard cap is reached, so
|
|
148
|
+
// a job streaming endless spans can't fill the disk).
|
|
149
|
+
if (state.uncompressedSize >= BLOB_HARD_CAP || (state.uncompressedSize >= BLOB_SIZE_CAP && signal === 'logs')) {
|
|
150
|
+
res.status(200).json({ ok: true, dropped: true });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Determine raw line size for cap tracking (uncompressed JSON length)
|
|
154
|
+
const payloadStr = JSON.stringify(body);
|
|
155
|
+
const lineObj = { signal, receivedAt: new Date().toISOString(), payload: body };
|
|
156
|
+
const lineStr = JSON.stringify(lineObj);
|
|
157
|
+
const lineSize = Buffer.byteLength(lineStr, 'utf-8');
|
|
158
|
+
// Check if this write will push us over the cap
|
|
159
|
+
const willExceedCap = state.uncompressedSize + lineSize > BLOB_SIZE_CAP;
|
|
160
|
+
const prevSize = state.uncompressedSize;
|
|
161
|
+
state.uncompressedSize += lineSize;
|
|
162
|
+
// Ensure directory and blob pointer row exist before we enqueue the write
|
|
163
|
+
const dir = telemetryDir(project.slug);
|
|
164
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
165
|
+
const existingBlob = (0, db_2.getTelemetryBlob)(db, jobId);
|
|
166
|
+
if (!existingBlob) {
|
|
167
|
+
(0, db_2.upsertTelemetryBlob)(db, {
|
|
168
|
+
jobId,
|
|
169
|
+
path: filePath,
|
|
170
|
+
byteSize: 0,
|
|
171
|
+
startedAt: now,
|
|
172
|
+
endedAt: now,
|
|
173
|
+
state: 'active',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
(0, db_2.upsertTelemetryBlob)(db, {
|
|
178
|
+
...existingBlob,
|
|
179
|
+
byteSize: state.uncompressedSize,
|
|
180
|
+
endedAt: now,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Enqueue the actual file append
|
|
184
|
+
enqueueWrite(state, async () => {
|
|
185
|
+
if (willExceedCap && signal === 'logs' && !state.truncationMarkerWritten) {
|
|
186
|
+
// Write the truncation marker once, before dropping this log event
|
|
187
|
+
state.truncationMarkerWritten = true;
|
|
188
|
+
const marker = JSON.stringify({ signal: 'control', event: 'logs_truncated', at: new Date().toISOString() });
|
|
189
|
+
await appendToGzip(filePath, marker);
|
|
190
|
+
}
|
|
191
|
+
// Drop further logs after cap
|
|
192
|
+
if (prevSize >= BLOB_SIZE_CAP && signal === 'logs')
|
|
193
|
+
return;
|
|
194
|
+
await appendToGzip(filePath, lineStr);
|
|
195
|
+
// Update byteSize in DB after each write
|
|
196
|
+
(0, db_2.upsertTelemetryBlob)(db, {
|
|
197
|
+
jobId,
|
|
198
|
+
path: filePath,
|
|
199
|
+
byteSize: state.uncompressedSize,
|
|
200
|
+
startedAt: existingBlob?.startedAt ?? now,
|
|
201
|
+
endedAt: now,
|
|
202
|
+
state: 'active',
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
// Suppress unused-variable warning for payloadStr — it's used above for size calc
|
|
206
|
+
void payloadStr;
|
|
207
|
+
res.status(200).json({ ok: true });
|
|
208
|
+
}
|
|
209
|
+
// ─── Router factory ───────────────────────────────────────────────────────────
|
|
210
|
+
function createTelemetryRouter(registry) {
|
|
211
|
+
const router = (0, express_1.Router)();
|
|
212
|
+
const handle = (signal) => (req, res) => {
|
|
213
|
+
handleIngest(signal, req.body, registry, res).catch((err) => {
|
|
214
|
+
console.error(`[telemetry-receiver] error handling ${signal}:`, err);
|
|
215
|
+
if (!res.headersSent) {
|
|
216
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
router.post('/v1/traces', handle('traces'));
|
|
221
|
+
router.post('/v1/metrics', handle('metrics'));
|
|
222
|
+
router.post('/v1/logs', handle('logs'));
|
|
223
|
+
return router;
|
|
224
|
+
}
|