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.
Files changed (455) hide show
  1. package/.claude/commands/specrails/batch-implement.md +287 -0
  2. package/.claude/commands/specrails/compat-check.md +271 -0
  3. package/.claude/commands/specrails/doctor.md +62 -0
  4. package/.claude/commands/specrails/enrich.md +1635 -0
  5. package/.claude/commands/specrails/explore-spec.md +173 -0
  6. package/.claude/commands/specrails/health-check.md +527 -0
  7. package/.claude/commands/specrails/implement.md +1457 -0
  8. package/.claude/commands/specrails/memory-inspect.md +259 -0
  9. package/.claude/commands/specrails/opsx-diff.md +419 -0
  10. package/.claude/commands/specrails/propose-spec.md +102 -0
  11. package/.claude/commands/specrails/reconfig.md +89 -0
  12. package/.claude/commands/specrails/refactor-recommender.md +212 -0
  13. package/.claude/commands/specrails/retry.md +363 -0
  14. package/.claude/commands/specrails/telemetry.md +552 -0
  15. package/.claude/commands/specrails/why.md +96 -0
  16. package/LICENSE +21 -0
  17. package/README.md +290 -0
  18. package/cli/dist/specrails-desktop.js +1098 -0
  19. package/client/dist/assets/ActivityFeedPage-Gy4x8dBt.js +1 -0
  20. package/client/dist/assets/AgentsPage-CPgu--Fb.js +86 -0
  21. package/client/dist/assets/AnalyticsPage-B5sJEee2.js +1 -0
  22. package/client/dist/assets/BarChart-7IMQ8HY1.js +33 -0
  23. package/client/dist/assets/CodePage-CBdFvbwe.js +2 -0
  24. package/client/dist/assets/DesktopAnalyticsPage-w0rdTq4w.js +1 -0
  25. package/client/dist/assets/DocsDialog-BZUYM7wm.js +11 -0
  26. package/client/dist/assets/DocsPage-9QglWl46.js +11 -0
  27. package/client/dist/assets/ExportDropdown-BLZFXtNi.js +1 -0
  28. package/client/dist/assets/IntegrationsPage-BxBE4y99.js +3 -0
  29. package/client/dist/assets/JobDetailPage-DydWx_5S.js +16 -0
  30. package/client/dist/assets/JobsPage-20ibw0IO.js +1 -0
  31. package/client/dist/assets/abap-Bw6f2wDG.js +1 -0
  32. package/client/dist/assets/activity-BEIp_Y1A.js +1 -0
  33. package/client/dist/assets/activity-BdrPln96.js +1 -0
  34. package/client/dist/assets/activity-CpkRS8Sx.js +1 -0
  35. package/client/dist/assets/activity-DKCpESPt.js +1 -0
  36. package/client/dist/assets/activity-DOUVEjJi.js +1 -0
  37. package/client/dist/assets/activity-DRwkql_y.js +1 -0
  38. package/client/dist/assets/activity-DcDQ7tjw.js +1 -0
  39. package/client/dist/assets/activity-Dv6H7wEr.js +1 -0
  40. package/client/dist/assets/addon-image-3WCl5Vhd.js +1 -0
  41. package/client/dist/assets/addon-ligatures-C5OdliKs.js +2 -0
  42. package/client/dist/assets/addon-webgl-BbX6pSjl.js +44 -0
  43. package/client/dist/assets/addspec-B5yl4Loj.js +1 -0
  44. package/client/dist/assets/addspec-BEeF5-zc.js +1 -0
  45. package/client/dist/assets/addspec-D33ocMxf.js +1 -0
  46. package/client/dist/assets/addspec-DFswZ0jK.js +1 -0
  47. package/client/dist/assets/addspec-DRE-jZv7.js +1 -0
  48. package/client/dist/assets/addspec-DVZ15Jp8.js +1 -0
  49. package/client/dist/assets/addspec-Fkv91Opc.js +1 -0
  50. package/client/dist/assets/addspec-GWm4ffKl.js +1 -0
  51. package/client/dist/assets/agents-1nCDWRmP.js +1 -0
  52. package/client/dist/assets/agents-Bm9rPqnt.js +1 -0
  53. package/client/dist/assets/agents-CMxtJMLD.js +1 -0
  54. package/client/dist/assets/agents-DK-Dlc0i.js +1 -0
  55. package/client/dist/assets/agents-Q6Ldfpxx.js +1 -0
  56. package/client/dist/assets/agents-TeOSy-ax.js +1 -0
  57. package/client/dist/assets/agents-iTqjRajS.js +1 -0
  58. package/client/dist/assets/agents-s87sMGzL.js +1 -0
  59. package/client/dist/assets/agentstudio-B6Wb59E7.js +1 -0
  60. package/client/dist/assets/agentstudio-BADhZ41e.js +1 -0
  61. package/client/dist/assets/agentstudio-BSnWLR63.js +1 -0
  62. package/client/dist/assets/agentstudio-BdidyBzZ.js +1 -0
  63. package/client/dist/assets/agentstudio-CxlUllqI.js +1 -0
  64. package/client/dist/assets/agentstudio-D3I62TLJ.js +1 -0
  65. package/client/dist/assets/agentstudio-DuH9TogZ.js +1 -0
  66. package/client/dist/assets/agentstudio-Kw88_dUF.js +1 -0
  67. package/client/dist/assets/aiedit-BWxHGsYA.js +1 -0
  68. package/client/dist/assets/aiedit-D2ji6Qy0.js +1 -0
  69. package/client/dist/assets/aiedit-DAhZTvtk.js +1 -0
  70. package/client/dist/assets/aiedit-DJMny-D5.js +1 -0
  71. package/client/dist/assets/aiedit-DOcxERkU.js +1 -0
  72. package/client/dist/assets/aiedit-DvrcbwGv.js +1 -0
  73. package/client/dist/assets/aiedit-TTwzL1TS.js +1 -0
  74. package/client/dist/assets/aiedit-WBSjT_C1.js +1 -0
  75. package/client/dist/assets/analytics-BIdr0YfL.js +1 -0
  76. package/client/dist/assets/analytics-C6EzgtdE.js +1 -0
  77. package/client/dist/assets/analytics-C9Zc-rkM.js +1 -0
  78. package/client/dist/assets/analytics-CVx3YOc0.js +1 -0
  79. package/client/dist/assets/analytics-CYj0tfj7.js +1 -0
  80. package/client/dist/assets/analytics-CnY4kNG3.js +1 -0
  81. package/client/dist/assets/analytics-CrPCZRJ-.js +1 -0
  82. package/client/dist/assets/analytics-DMCto-TF.js +1 -0
  83. package/client/dist/assets/apex-Cw8_REBo.js +1 -0
  84. package/client/dist/assets/atom-one-dark-B-oHczHB.css +1 -0
  85. package/client/dist/assets/attachments-BIsSSnHJ.js +1 -0
  86. package/client/dist/assets/attachments-BW4L3l2L.js +1 -0
  87. package/client/dist/assets/attachments-Bcf6BG6V.js +1 -0
  88. package/client/dist/assets/attachments-Bke8sCU4.js +1 -0
  89. package/client/dist/assets/attachments-COcrGRFz.js +1 -0
  90. package/client/dist/assets/attachments-DYHGA2Dj.js +1 -0
  91. package/client/dist/assets/attachments-Dd92KpUH.js +1 -0
  92. package/client/dist/assets/attachments-DzdU6DV6.js +1 -0
  93. package/client/dist/assets/azcli-Cz6HAoOw.js +1 -0
  94. package/client/dist/assets/bat-CcJ-xyqL.js +1 -0
  95. package/client/dist/assets/bicep-z1WDCKYz.js +2 -0
  96. package/client/dist/assets/browser-5ErDlJoR.js +1 -0
  97. package/client/dist/assets/browser-Bc-YdlVg.js +1 -0
  98. package/client/dist/assets/browser-BlYF4OOq.js +1 -0
  99. package/client/dist/assets/browser-CT-ReZGt.js +1 -0
  100. package/client/dist/assets/browser-DGITz3fC.js +1 -0
  101. package/client/dist/assets/browser-JsAIGCEW.js +1 -0
  102. package/client/dist/assets/browser-M5-rbPlw.js +1 -0
  103. package/client/dist/assets/browser-Qya9cARy.js +1 -0
  104. package/client/dist/assets/cameligo-BRewOpfa.js +1 -0
  105. package/client/dist/assets/chat-BEGuC03z.js +1 -0
  106. package/client/dist/assets/chat-BEW60P_u.js +1 -0
  107. package/client/dist/assets/chat-BQNMD0PL.js +1 -0
  108. package/client/dist/assets/chat-BsbNGPW9.js +1 -0
  109. package/client/dist/assets/chat-CboQguCi.js +1 -0
  110. package/client/dist/assets/chat-DRCa9pOt.js +1 -0
  111. package/client/dist/assets/chat-DwUm6W9z.js +1 -0
  112. package/client/dist/assets/chat-yoXwguQu.js +1 -0
  113. package/client/dist/assets/chunk-CilyBKbf.js +1 -0
  114. package/client/dist/assets/clojure-DBjRWN6g.js +1 -0
  115. package/client/dist/assets/clsx-DnqN-uhr.js +1 -0
  116. package/client/dist/assets/code-AL1rVIMb.js +1 -0
  117. package/client/dist/assets/code-C0BKpkht.js +1 -0
  118. package/client/dist/assets/code-C0FTS3ew.js +1 -0
  119. package/client/dist/assets/code-CPcHxzxw.js +1 -0
  120. package/client/dist/assets/code-D3ryDniw.js +1 -0
  121. package/client/dist/assets/code-D3zVVQTj.js +1 -0
  122. package/client/dist/assets/code-PCmfS3dn.js +1 -0
  123. package/client/dist/assets/code-exI0G5Wd.js +1 -0
  124. package/client/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  125. package/client/dist/assets/coffee-Cfk_XHGR.js +1 -0
  126. package/client/dist/assets/commands-B772IyDa.js +1 -0
  127. package/client/dist/assets/commands-BDDp6xFG.js +1 -0
  128. package/client/dist/assets/commands-CJxCry-o.js +1 -0
  129. package/client/dist/assets/commands-CfgY-_of.js +1 -0
  130. package/client/dist/assets/commands-DLrvnPNg.js +1 -0
  131. package/client/dist/assets/commands-IXMOKBYt.js +1 -0
  132. package/client/dist/assets/commands-UD1NzmwX.js +1 -0
  133. package/client/dist/assets/commands-sqrqsxyE.js +1 -0
  134. package/client/dist/assets/common-DCr6VzJ7.js +1 -0
  135. package/client/dist/assets/common-Dard9UNH.js +1 -0
  136. package/client/dist/assets/common-DeDELLZJ.js +1 -0
  137. package/client/dist/assets/common-DltqHaAe.js +1 -0
  138. package/client/dist/assets/common-Dmm1GhdD.js +1 -0
  139. package/client/dist/assets/common-DnjcgkPH.js +1 -0
  140. package/client/dist/assets/common-GbpxfPG8.js +1 -0
  141. package/client/dist/assets/common-wA36jmj1.js +1 -0
  142. package/client/dist/assets/cpp-BVob6BaP.js +1 -0
  143. package/client/dist/assets/csharp-C4fbRuOu.js +1 -0
  144. package/client/dist/assets/csp-DthFP_vT.js +1 -0
  145. package/client/dist/assets/css-CGMH0hcW.js +3 -0
  146. package/client/dist/assets/css.worker-Wv5dxAWO.js +89 -0
  147. package/client/dist/assets/cssMode-Cc6ozl-J.js +1 -0
  148. package/client/dist/assets/cypher-Pnf68BRV.js +1 -0
  149. package/client/dist/assets/dart-PMMOtxZX.js +1 -0
  150. package/client/dist/assets/dashboard-B4ixDVk8.js +1 -0
  151. package/client/dist/assets/dashboard-BZBADHSj.js +1 -0
  152. package/client/dist/assets/dashboard-C1MfeUHs.js +1 -0
  153. package/client/dist/assets/dashboard-C7SK6xu5.js +1 -0
  154. package/client/dist/assets/dashboard-CB6Le1yN.js +1 -0
  155. package/client/dist/assets/dashboard-CoTpMOBM.js +1 -0
  156. package/client/dist/assets/dashboard-Duo4DDCW.js +1 -0
  157. package/client/dist/assets/dashboard-I19DXBxw.js +1 -0
  158. package/client/dist/assets/dist-js-BY-Fv_fg.js +1 -0
  159. package/client/dist/assets/dist-js-Bakc4uxT.js +1 -0
  160. package/client/dist/assets/dockerfile-di1nsJCc.js +1 -0
  161. package/client/dist/assets/ecl-D_WVcB5M.js +1 -0
  162. package/client/dist/assets/editor-Br_kD0ds.css +1 -0
  163. package/client/dist/assets/editor.api2-XLGzZfbc.js +872 -0
  164. package/client/dist/assets/editor.main-CfXxHimg.js +6 -0
  165. package/client/dist/assets/editor.worker-Bd9IXS8d.js +26 -0
  166. package/client/dist/assets/elixir-OAdJEMOn.js +1 -0
  167. package/client/dist/assets/explore-4mFpnrKU.js +1 -0
  168. package/client/dist/assets/explore-A8Ltoblq.js +1 -0
  169. package/client/dist/assets/explore-B9A3iN2W.js +1 -0
  170. package/client/dist/assets/explore-BV5Xxlsn.js +1 -0
  171. package/client/dist/assets/explore-BrBJvfjP.js +1 -0
  172. package/client/dist/assets/explore-C3FSE42C.js +1 -0
  173. package/client/dist/assets/explore-D2EFgt8J.js +1 -0
  174. package/client/dist/assets/explore-hFc3HFcp.js +1 -0
  175. package/client/dist/assets/flow9-D3QEZjgn.js +1 -0
  176. package/client/dist/assets/format-command-CwGuwzGA.js +1 -0
  177. package/client/dist/assets/freemarker2-DP7J1gG3.js +3 -0
  178. package/client/dist/assets/fsharp-BF0k_8N8.js +1 -0
  179. package/client/dist/assets/go-BAQO5Jsz.js +1 -0
  180. package/client/dist/assets/graphql-hdFVFkiV.js +1 -0
  181. package/client/dist/assets/handlebars-BjRlucw6.js +1 -0
  182. package/client/dist/assets/hcl-DWnl1o-X.js +1 -0
  183. package/client/dist/assets/html-OumBQJ-U.js +1 -0
  184. package/client/dist/assets/html.worker-CQP8QQsS.js +502 -0
  185. package/client/dist/assets/htmlMode-CStc3zXM.js +1 -0
  186. package/client/dist/assets/index-CimDRRi7.css +2 -0
  187. package/client/dist/assets/index-XGZaKl_u.js +142 -0
  188. package/client/dist/assets/ini-CB-6OVu3.js +1 -0
  189. package/client/dist/assets/integrations-C3p12Ms6.js +1 -0
  190. package/client/dist/assets/integrations-Cr6hH7XR.js +1 -0
  191. package/client/dist/assets/integrations-Cublz3m6.js +1 -0
  192. package/client/dist/assets/integrations-D28q1kF6.js +1 -0
  193. package/client/dist/assets/integrations-DRdbki5W.js +1 -0
  194. package/client/dist/assets/integrations-DaC4SzzL.js +1 -0
  195. package/client/dist/assets/integrations-DmQYCUvN.js +1 -0
  196. package/client/dist/assets/integrations-HIlUxXVs.js +1 -0
  197. package/client/dist/assets/java-d1CmfiHX.js +1 -0
  198. package/client/dist/assets/javascript-CMk--e7g.js +1 -0
  199. package/client/dist/assets/jobs-BE1siB0M.js +1 -0
  200. package/client/dist/assets/jobs-BHcQ_Faf.js +1 -0
  201. package/client/dist/assets/jobs-CFfc2dNX.js +1 -0
  202. package/client/dist/assets/jobs-CSi5n8X_.js +1 -0
  203. package/client/dist/assets/jobs-Dc3X86PY.js +1 -0
  204. package/client/dist/assets/jobs-De5tASex.js +1 -0
  205. package/client/dist/assets/jobs-DsoXEdo7.js +1 -0
  206. package/client/dist/assets/jobs-Wl-ApPMb.js +1 -0
  207. package/client/dist/assets/json.worker-DzV-CpCQ.js +58 -0
  208. package/client/dist/assets/jsonMode-C2h3ZcjZ.js +7 -0
  209. package/client/dist/assets/julia-Bgv08lKa.js +1 -0
  210. package/client/dist/assets/kotlin-u98kaVTf.js +1 -0
  211. package/client/dist/assets/less-CjYwpgg5.js +2 -0
  212. package/client/dist/assets/lexon-YTjaAFBB.js +1 -0
  213. package/client/dist/assets/lib-CPxTMOAq.js +7 -0
  214. package/client/dist/assets/liquid-mI3KJrBE.js +1 -0
  215. package/client/dist/assets/lspLanguageFeatures-DU09ggWi.js +4 -0
  216. package/client/dist/assets/lua-BzmkWv27.js +1 -0
  217. package/client/dist/assets/m3-CFwk9fw0.js +1 -0
  218. package/client/dist/assets/markdown-CR5iMpSZ.js +1 -0
  219. package/client/dist/assets/mdx-C41VDTR_.js +1 -0
  220. package/client/dist/assets/mips-CcEalc17.js +1 -0
  221. package/client/dist/assets/monaco.contribution-CPObAXMC.js +2 -0
  222. package/client/dist/assets/msdax-BQbkawnr.js +1 -0
  223. package/client/dist/assets/mysql-GTlaaW_P.js +1 -0
  224. package/client/dist/assets/nav-0fwkrgHt.js +1 -0
  225. package/client/dist/assets/nav-BEL3MTwK.js +1 -0
  226. package/client/dist/assets/nav-B_G-TJDW.js +1 -0
  227. package/client/dist/assets/nav-C2YXcbZS.js +1 -0
  228. package/client/dist/assets/nav-ClzOE4mA.js +1 -0
  229. package/client/dist/assets/nav-CtYwmMgu.js +1 -0
  230. package/client/dist/assets/nav-D2bOGSEg.js +1 -0
  231. package/client/dist/assets/nav-iH1V5j6o.js +1 -0
  232. package/client/dist/assets/objective-c-Byu1T5if.js +1 -0
  233. package/client/dist/assets/pascal-BrfzBfRm.js +1 -0
  234. package/client/dist/assets/pascaligo-BXXKFUeo.js +1 -0
  235. package/client/dist/assets/perl-B3OikKq-.js +1 -0
  236. package/client/dist/assets/pgsql-CTsa0Acc.js +1 -0
  237. package/client/dist/assets/php-DiQh3FUW.js +1 -0
  238. package/client/dist/assets/pla-92uH8Fzm.js +1 -0
  239. package/client/dist/assets/postiats-BbeWkKUr.js +1 -0
  240. package/client/dist/assets/powerquery-DgDMzpsm.js +1 -0
  241. package/client/dist/assets/powershell-BfdUUzaG.js +1 -0
  242. package/client/dist/assets/preload-helper-DSXbuxSR.js +1 -0
  243. package/client/dist/assets/protobuf-BojW2ftW.js +2 -0
  244. package/client/dist/assets/pug-BxqTg3IU.js +1 -0
  245. package/client/dist/assets/python-Y27rKQtk.js +1 -0
  246. package/client/dist/assets/qsharp-BX_A-MW9.js +1 -0
  247. package/client/dist/assets/r-D9BMnxvJ.js +1 -0
  248. package/client/dist/assets/razor-Cd5-q9Bp.js +1 -0
  249. package/client/dist/assets/redis-5cJqEQJJ.js +1 -0
  250. package/client/dist/assets/redshift-d8BBqiwb.js +1 -0
  251. package/client/dist/assets/restructuredtext-C8a6yIcZ.js +1 -0
  252. package/client/dist/assets/ruby-egeh-6KX.js +1 -0
  253. package/client/dist/assets/rust-a3r9IInB.js +1 -0
  254. package/client/dist/assets/sb-y8iRIDei.js +1 -0
  255. package/client/dist/assets/scala-BPDK2AmK.js +1 -0
  256. package/client/dist/assets/scheme-BIWUEoOs.js +1 -0
  257. package/client/dist/assets/scss-CA-PSzwg.js +3 -0
  258. package/client/dist/assets/settings-55oDcbSh.js +1 -0
  259. package/client/dist/assets/settings-Bd4Tq1RB.js +1 -0
  260. package/client/dist/assets/settings-CCSM-Fhn.js +1 -0
  261. package/client/dist/assets/settings-D3e_bDoW.js +1 -0
  262. package/client/dist/assets/settings-DKbTkbn7.js +1 -0
  263. package/client/dist/assets/settings-Dxpo6_w7.js +1 -0
  264. package/client/dist/assets/settings-bt84e3Aa.js +1 -0
  265. package/client/dist/assets/settings-nu68QukM.js +1 -0
  266. package/client/dist/assets/setup-BMqwfbW9.js +1 -0
  267. package/client/dist/assets/setup-Bb5LcG28.js +1 -0
  268. package/client/dist/assets/setup-BeEx2_da.js +1 -0
  269. package/client/dist/assets/setup-CCCrB53Q.js +1 -0
  270. package/client/dist/assets/setup-CJA0ATmd.js +1 -0
  271. package/client/dist/assets/setup-CeiDbZcb.js +1 -0
  272. package/client/dist/assets/setup-Cus7TApA.js +1 -0
  273. package/client/dist/assets/setup-D9qOs2Xo.js +1 -0
  274. package/client/dist/assets/shell--LiT1Bja.js +1 -0
  275. package/client/dist/assets/solidity-DdqZccZg.js +1 -0
  276. package/client/dist/assets/sophia-S6-YxNG_.js +1 -0
  277. package/client/dist/assets/sparql-BSf5kMp2.js +1 -0
  278. package/client/dist/assets/specs-BFfu3u-a.js +1 -0
  279. package/client/dist/assets/specs-B__C8-8a.js +1 -0
  280. package/client/dist/assets/specs-CZ1PsXsC.js +1 -0
  281. package/client/dist/assets/specs-D2FzlLn9.js +1 -0
  282. package/client/dist/assets/specs-DaUTrNF9.js +1 -0
  283. package/client/dist/assets/specs-Dyc5hYeE.js +1 -0
  284. package/client/dist/assets/specs-cKEh2LXt.js +1 -0
  285. package/client/dist/assets/specs-k0PyLDVt.js +1 -0
  286. package/client/dist/assets/sql-D7KgjR8G.js +1 -0
  287. package/client/dist/assets/st-BnoDa-Ml.js +1 -0
  288. package/client/dist/assets/swift-DEUHTkUX.js +1 -0
  289. package/client/dist/assets/systemverilog-Tqb_KPnW.js +1 -0
  290. package/client/dist/assets/tcl-BmBFS2qq.js +1 -0
  291. package/client/dist/assets/terminal-80yDMgMF.js +1 -0
  292. package/client/dist/assets/terminal-Bje4ziIa.js +1 -0
  293. package/client/dist/assets/terminal-C2WYcFHF.js +1 -0
  294. package/client/dist/assets/terminal-CSONJOex.js +1 -0
  295. package/client/dist/assets/terminal-DEqzGtcr.js +1 -0
  296. package/client/dist/assets/terminal-DeWzh6ys.js +1 -0
  297. package/client/dist/assets/terminal-YOlsJCQj.js +1 -0
  298. package/client/dist/assets/terminal-lkZYR4wJ.js +1 -0
  299. package/client/dist/assets/tickets-CB7N30gm.js +1 -0
  300. package/client/dist/assets/tickets-CF2PYelu.js +1 -0
  301. package/client/dist/assets/tickets-DNOANUXr.js +1 -0
  302. package/client/dist/assets/tickets-DU1aqsbr.js +1 -0
  303. package/client/dist/assets/tickets-DYvafSaY.js +1 -0
  304. package/client/dist/assets/tickets-DlpC_iTg.js +1 -0
  305. package/client/dist/assets/tickets-DucYgtdl.js +1 -0
  306. package/client/dist/assets/tickets-clefmXLv.js +1 -0
  307. package/client/dist/assets/ts.worker-METxwbDZ.js +67719 -0
  308. package/client/dist/assets/tsMode-B0y_xEci.js +11 -0
  309. package/client/dist/assets/twig-BQV8igWC.js +1 -0
  310. package/client/dist/assets/typescript-BzK0OgwW.js +1 -0
  311. package/client/dist/assets/typespec-DlFroUGY.js +1 -0
  312. package/client/dist/assets/useProjectCache-DSaiGFjV.js +1 -0
  313. package/client/dist/assets/vb-BlrJpIMX.js +1 -0
  314. package/client/dist/assets/wgsl-BWgIc6FZ.js +298 -0
  315. package/client/dist/assets/workers-rt--R2Qy.js +1 -0
  316. package/client/dist/assets/xml-eX9QXAmI.js +1 -0
  317. package/client/dist/assets/yaml-fcsNkpOt.js +1 -0
  318. package/client/dist/index.html +246 -0
  319. package/docs/README.md +54 -0
  320. package/docs/cli.md +198 -0
  321. package/docs/codex.md +210 -0
  322. package/docs/creating-specs.md +197 -0
  323. package/docs/customizing.md +197 -0
  324. package/docs/getting-started.md +140 -0
  325. package/docs/internals/README.md +25 -0
  326. package/docs/internals/adding-a-provider.md +238 -0
  327. package/docs/internals/api-reference.md +634 -0
  328. package/docs/internals/architecture.md +332 -0
  329. package/docs/internals/configuration.md +172 -0
  330. package/docs/internals/openspec-workflow.md +282 -0
  331. package/docs/internals/operations-runbook.md +198 -0
  332. package/docs/internals/profiles.md +152 -0
  333. package/docs/platforms/macos.md +130 -0
  334. package/docs/platforms/windows.md +81 -0
  335. package/docs/running-pipelines.md +240 -0
  336. package/docs/terminal.md +138 -0
  337. package/docs/tracking-cost.md +155 -0
  338. package/package.json +82 -0
  339. package/server/dist/agent-generator.js +232 -0
  340. package/server/dist/agent-refine-db.js +124 -0
  341. package/server/dist/agent-refine-manager.js +526 -0
  342. package/server/dist/ai-invocations.js +111 -0
  343. package/server/dist/attachment-manager.js +299 -0
  344. package/server/dist/auth.js +207 -0
  345. package/server/dist/binary-probe.js +35 -0
  346. package/server/dist/browser-capture-manager.js +576 -0
  347. package/server/dist/browser-capture-types.js +28 -0
  348. package/server/dist/browser-network.js +149 -0
  349. package/server/dist/browser-playwright.js +888 -0
  350. package/server/dist/build-dirs.js +44 -0
  351. package/server/dist/changes-reader.js +120 -0
  352. package/server/dist/chat-manager.js +1060 -0
  353. package/server/dist/chromium-resolver.js +311 -0
  354. package/server/dist/code-explorer-router.js +788 -0
  355. package/server/dist/codex-otel-bridge.js +235 -0
  356. package/server/dist/command-resolver.js +102 -0
  357. package/server/dist/config.js +306 -0
  358. package/server/dist/context-budget.js +113 -0
  359. package/server/dist/context-scope.js +279 -0
  360. package/server/dist/contract-refine-runner.js +521 -0
  361. package/server/dist/core-compat.js +207 -0
  362. package/server/dist/core-package.js +14 -0
  363. package/server/dist/db.js +1034 -0
  364. package/server/dist/desktop-analytics.js +156 -0
  365. package/server/dist/desktop-db.js +456 -0
  366. package/server/dist/desktop-router.js +735 -0
  367. package/server/dist/docs-router.js +207 -0
  368. package/server/dist/explore-contract-refine.js +421 -0
  369. package/server/dist/explore-cwd-manager.js +242 -0
  370. package/server/dist/explore-draft-title.js +47 -0
  371. package/server/dist/explore-smash.js +450 -0
  372. package/server/dist/feature-flags.js +17 -0
  373. package/server/dist/file-provenance.js +382 -0
  374. package/server/dist/file-summary-generator.js +221 -0
  375. package/server/dist/file-summary-manager.js +689 -0
  376. package/server/dist/hooks.js +102 -0
  377. package/server/dist/ids.js +7 -0
  378. package/server/dist/index.js +586 -0
  379. package/server/dist/metrics.js +136 -0
  380. package/server/dist/mobile/index.js +16 -0
  381. package/server/dist/mobile/mobile-admin-router.js +84 -0
  382. package/server/dist/mobile/mobile-auth.js +67 -0
  383. package/server/dist/mobile/mobile-devices.js +80 -0
  384. package/server/dist/mobile/mobile-event-bus.js +39 -0
  385. package/server/dist/mobile/mobile-gateway.js +285 -0
  386. package/server/dist/mobile/mobile-mdns.js +81 -0
  387. package/server/dist/mobile/mobile-pairing.js +179 -0
  388. package/server/dist/mobile/mobile-redact.js +53 -0
  389. package/server/dist/mobile/mobile-router.js +411 -0
  390. package/server/dist/mobile/mobile-tls.js +86 -0
  391. package/server/dist/mobile/mobile-types.js +9 -0
  392. package/server/dist/mobile/mobile-ws.js +275 -0
  393. package/server/dist/path-resolver.js +298 -0
  394. package/server/dist/plugin-manager.js +617 -0
  395. package/server/dist/plugins/claude-approval.js +179 -0
  396. package/server/dist/plugins/claude-md-mutation.js +146 -0
  397. package/server/dist/plugins/codex-mcp.js +108 -0
  398. package/server/dist/plugins/contributors.js +72 -0
  399. package/server/dist/plugins/drift.js +58 -0
  400. package/server/dist/plugins/index.js +14 -0
  401. package/server/dist/plugins/json-mutation.js +120 -0
  402. package/server/dist/plugins/manager.js +32 -0
  403. package/server/dist/plugins/ownership.js +86 -0
  404. package/server/dist/plugins/paths.js +37 -0
  405. package/server/dist/plugins/prereq-installer.js +104 -0
  406. package/server/dist/plugins/rail-integration.js +79 -0
  407. package/server/dist/plugins/serena/index.js +13 -0
  408. package/server/dist/plugins/serena/install.js +91 -0
  409. package/server/dist/plugins/serena/instructions-content.js +21 -0
  410. package/server/dist/plugins/serena/manifest.js +111 -0
  411. package/server/dist/plugins/serena/verify.js +78 -0
  412. package/server/dist/plugins-router.js +215 -0
  413. package/server/dist/pricing.js +89 -0
  414. package/server/dist/profile-manager.js +310 -0
  415. package/server/dist/profiles-router.js +759 -0
  416. package/server/dist/project-registry.js +443 -0
  417. package/server/dist/project-router.js +4016 -0
  418. package/server/dist/proposal-manager.js +291 -0
  419. package/server/dist/provider-selection.js +69 -0
  420. package/server/dist/providers/claude-adapter.js +281 -0
  421. package/server/dist/providers/codex-adapter.js +264 -0
  422. package/server/dist/providers/index.js +23 -0
  423. package/server/dist/providers/registry.js +37 -0
  424. package/server/dist/providers/types.js +22 -0
  425. package/server/dist/queue-manager.js +1511 -0
  426. package/server/dist/rails-router.js +362 -0
  427. package/server/dist/rails-store.js +116 -0
  428. package/server/dist/result-event.js +106 -0
  429. package/server/dist/schemas/profile.v1.json +151 -0
  430. package/server/dist/setup-manager.js +1165 -0
  431. package/server/dist/setup-prerequisites.js +372 -0
  432. package/server/dist/smash-runner.js +663 -0
  433. package/server/dist/spec-draft-parser.js +133 -0
  434. package/server/dist/spec-launcher-manager.js +174 -0
  435. package/server/dist/spec-models.js +32 -0
  436. package/server/dist/specrails-tech-client.js +82 -0
  437. package/server/dist/spending.js +448 -0
  438. package/server/dist/telemetry-compactor.js +180 -0
  439. package/server/dist/telemetry-export.js +317 -0
  440. package/server/dist/telemetry-receiver.js +224 -0
  441. package/server/dist/terminal-manager.js +633 -0
  442. package/server/dist/terminal-marks-store.js +117 -0
  443. package/server/dist/terminal-osc-parser.js +159 -0
  444. package/server/dist/terminal-settings.js +282 -0
  445. package/server/dist/terminal-shell-integration.js +196 -0
  446. package/server/dist/ticket-broadcast.js +47 -0
  447. package/server/dist/ticket-store.js +397 -0
  448. package/server/dist/ticket-watcher.js +117 -0
  449. package/server/dist/types.js +10 -0
  450. package/server/dist/user-mcp-config.js +117 -0
  451. package/server/dist/util/cli-prompt.js +181 -0
  452. package/server/dist/util/secure-fs.js +50 -0
  453. package/server/dist/util/win-spawn.js +43 -0
  454. package/server/dist/webhook-manager.js +89 -0
  455. package/server/dist/ws-routing.js +47 -0
@@ -0,0 +1,576 @@
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.BrowserCaptureManager = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const ids_1 = require("./ids");
10
+ const attachment_manager_1 = require("./attachment-manager");
11
+ const browser_capture_types_1 = require("./browser-capture-types");
12
+ const browser_playwright_1 = require("./browser-playwright");
13
+ const WS_OPEN = 1;
14
+ const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
15
+ /** Playwright throws this family when the page/context/browser died underneath an
16
+ * operation (server restart in dev, page crash, tab closed). */
17
+ function isTargetClosedError(err) {
18
+ const msg = err instanceof Error ? err.message : String(err);
19
+ return /target page, context or browser has been closed|target closed|has been closed/i.test(msg);
20
+ }
21
+ const MAX_SESSIONS_PER_PROJECT = 4;
22
+ const DOM_HTML_BYTE_CAP = 100_000;
23
+ const LAST_URL_KEY = 'config.browser_last_url';
24
+ /** How far back from capture time to include buffered network requests. */
25
+ const NETWORK_WINDOW_MS = 30_000;
26
+ /**
27
+ * Per-project owner of an embedded Chromium browser used by the "Add Spec from
28
+ * browser" feature. Holds ONE persistent Playwright context (cookies/login survive
29
+ * restarts) keyed to `~/.specrails/projects/<slug>/browser-profile/`, with one page
30
+ * per session. Screencast frames + control go over the dedicated `/ws/browser/:id`
31
+ * socket; region capture (screenshot + rich DOM) is a REST call that persists both
32
+ * artifacts as attachments so they ride the existing Add-Spec attachment pipeline.
33
+ *
34
+ * All Playwright contact is behind the injected `ContextLauncher`, so the class is
35
+ * fully unit-testable with a fake launcher (no real browser).
36
+ */
37
+ class BrowserCaptureManager {
38
+ projectId;
39
+ projectSlug;
40
+ db;
41
+ broadcast;
42
+ launcher;
43
+ attachments;
44
+ profileDir;
45
+ context = null;
46
+ contextPromise = null;
47
+ sessions = new Map();
48
+ disposed = false;
49
+ constructor(opts) {
50
+ this.projectId = opts.projectId;
51
+ this.projectSlug = opts.projectSlug;
52
+ this.db = opts.db;
53
+ this.broadcast = opts.broadcast;
54
+ this.launcher = opts.launcher ?? (0, browser_playwright_1.createPlaywrightLauncher)();
55
+ this.attachments = opts.attachments ?? attachment_manager_1.attachmentManager;
56
+ this.profileDir =
57
+ opts.profileDir ??
58
+ path_1.default.join(opts.homeDir ?? os_1.default.homedir(), '.specrails', 'projects', opts.projectSlug, 'browser-profile');
59
+ }
60
+ // ─── Last-URL persistence (reuses the queue_state key/value table) ───────────
61
+ getLastUrl() {
62
+ try {
63
+ const row = this.db.prepare('SELECT value FROM queue_state WHERE key = ?').get(LAST_URL_KEY);
64
+ return row?.value ?? null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ setLastUrl(url) {
71
+ try {
72
+ this.db.prepare('INSERT OR REPLACE INTO queue_state (key, value) VALUES (?, ?)').run(LAST_URL_KEY, url);
73
+ }
74
+ catch {
75
+ /* non-fatal */
76
+ }
77
+ }
78
+ // ─── Context lifecycle ──────────────────────────────────────────────────────
79
+ async ensureContext() {
80
+ if (this.disposed)
81
+ throw new browser_capture_types_1.BrowserLaunchError('manager disposed');
82
+ if (this.context)
83
+ return this.context;
84
+ if (this.contextPromise)
85
+ return this.contextPromise;
86
+ this.contextPromise = (async () => {
87
+ try {
88
+ const ctx = await this.launcher({
89
+ userDataDir: this.profileDir,
90
+ viewport: DEFAULT_VIEWPORT,
91
+ });
92
+ this.context = ctx;
93
+ return ctx;
94
+ }
95
+ catch (err) {
96
+ this.contextPromise = null;
97
+ throw new browser_capture_types_1.BrowserLaunchError('failed to launch browser', err);
98
+ }
99
+ })();
100
+ return this.contextPromise;
101
+ }
102
+ // ─── Session CRUD ───────────────────────────────────────────────────────────
103
+ toMeta(s) {
104
+ return {
105
+ id: s.id,
106
+ projectId: s.projectId,
107
+ url: s.url,
108
+ title: s.title,
109
+ viewportWidth: s.viewport.width,
110
+ viewportHeight: s.viewport.height,
111
+ createdAt: s.createdAt,
112
+ };
113
+ }
114
+ listSessions() {
115
+ return [...this.sessions.values()].filter((s) => !s.closed).map((s) => this.toMeta(s));
116
+ }
117
+ getSession(sessionId) {
118
+ const s = this.sessions.get(sessionId);
119
+ return s && !s.closed ? s : undefined;
120
+ }
121
+ async create(opts) {
122
+ const live = [...this.sessions.values()].filter((s) => !s.closed);
123
+ if (live.length >= MAX_SESSIONS_PER_PROJECT) {
124
+ throw new browser_capture_types_1.BrowserLimitExceededError(MAX_SESSIONS_PER_PROJECT);
125
+ }
126
+ const ctx = await this.ensureContext();
127
+ const page = await ctx.newPage();
128
+ const id = (0, ids_1.newId)();
129
+ const session = {
130
+ id,
131
+ projectId: this.projectId,
132
+ page,
133
+ clients: new Set(),
134
+ lastFrame: null,
135
+ url: null,
136
+ title: null,
137
+ viewport: { ...DEFAULT_VIEWPORT },
138
+ createdAt: opts?.createdAtMs ?? this.now(),
139
+ screencasting: false,
140
+ screencastDesired: false,
141
+ screencastOp: null,
142
+ closed: false,
143
+ };
144
+ this.sessions.set(id, session);
145
+ // Start capturing the page's network requests before the first navigation so
146
+ // XHR/fetch made during page load are available at capture time. Best-effort:
147
+ // a handle that doesn't support it (or a failure) just yields no network data.
148
+ try {
149
+ await page.enableNetwork?.();
150
+ }
151
+ catch { /* network capture is best-effort */ }
152
+ const target = opts?.initialUrl?.trim() || this.getLastUrl() || 'about:blank';
153
+ const result = await page.goto(target);
154
+ session.url = result.url;
155
+ session.title = result.title;
156
+ if (result.url && result.url !== 'about:blank')
157
+ this.setLastUrl(result.url);
158
+ return this.toMeta(session);
159
+ }
160
+ // ─── WS attach / detach + screencast fan-out ────────────────────────────────
161
+ async attach(sessionId, ws) {
162
+ if (this.disposed)
163
+ return null;
164
+ const s = this.getSession(sessionId);
165
+ if (!s)
166
+ return null;
167
+ s.clients.add(ws);
168
+ this.safeSend(ws, JSON.stringify({ type: 'ready', id: s.id, url: s.url, title: s.title, viewport: s.viewport }));
169
+ if (s.lastFrame)
170
+ this.safeSend(ws, s.lastFrame);
171
+ s.screencastDesired = true;
172
+ await this.applyScreencast(s);
173
+ return this.toMeta(s);
174
+ }
175
+ detach(sessionId, ws) {
176
+ const s = this.sessions.get(sessionId);
177
+ if (!s)
178
+ return;
179
+ s.clients.delete(ws);
180
+ if (s.clients.size === 0 && !s.closed) {
181
+ s.screencastDesired = false;
182
+ void this.applyScreencast(s);
183
+ }
184
+ }
185
+ /**
186
+ * Serialise screencast start/stop transitions on a per-session promise chain so
187
+ * a rapid detach→attach (stop fired async, then start) can't double-initialise
188
+ * the CDP screencast. The chain always reconciles `screencasting` to
189
+ * `screencastDesired`.
190
+ */
191
+ applyScreencast(s) {
192
+ const prev = s.screencastOp ?? Promise.resolve();
193
+ s.screencastOp = prev.then(async () => {
194
+ if (s.closed) {
195
+ if (s.screencasting) {
196
+ s.screencasting = false;
197
+ try {
198
+ await s.page.stopScreencast();
199
+ }
200
+ catch { /* ignore */ }
201
+ }
202
+ return;
203
+ }
204
+ if (s.screencastDesired && !s.screencasting) {
205
+ s.screencasting = true;
206
+ await s.page.startScreencast((frame) => {
207
+ if (s.closed)
208
+ return;
209
+ s.lastFrame = frame.data;
210
+ for (const client of s.clients) {
211
+ if (client.readyState === WS_OPEN) {
212
+ try {
213
+ client.send(frame.data);
214
+ }
215
+ catch { /* drop */ }
216
+ }
217
+ }
218
+ });
219
+ }
220
+ else if (!s.screencastDesired && s.screencasting) {
221
+ s.screencasting = false;
222
+ try {
223
+ await s.page.stopScreencast();
224
+ }
225
+ catch { /* ignore */ }
226
+ }
227
+ }).catch(() => { });
228
+ return s.screencastOp;
229
+ }
230
+ safeSend(ws, data) {
231
+ if (ws.readyState !== WS_OPEN)
232
+ return;
233
+ try {
234
+ ws.send(data);
235
+ }
236
+ catch { /* drop */ }
237
+ }
238
+ broadcastControl(s, msg) {
239
+ const data = JSON.stringify(msg);
240
+ for (const client of s.clients)
241
+ this.safeSend(client, data);
242
+ }
243
+ // ─── Interactions ───────────────────────────────────────────────────────────
244
+ async probeElement(sessionId, point) {
245
+ if (this.disposed)
246
+ return null;
247
+ const s = this.getSession(sessionId);
248
+ if (!s)
249
+ return null;
250
+ return s.page.probeElementAt(point);
251
+ }
252
+ /** Re-resolve an element by selector and step to parent/child/self (breadcrumb). */
253
+ async navigateElement(sessionId, selector, direction) {
254
+ if (this.disposed)
255
+ return null;
256
+ const s = this.getSession(sessionId);
257
+ if (!s)
258
+ return null;
259
+ return (await s.page.navigateElement?.(selector, direction)) ?? null;
260
+ }
261
+ async handleInput(sessionId, event) {
262
+ if (this.disposed)
263
+ return;
264
+ const s = this.getSession(sessionId);
265
+ if (!s)
266
+ return;
267
+ if (event.type === 'resize') {
268
+ s.viewport = {
269
+ width: Math.max(1, Math.round(event.width)),
270
+ height: Math.max(1, Math.round(event.height)),
271
+ };
272
+ }
273
+ await s.page.dispatchInput(event);
274
+ }
275
+ async navigate(sessionId, action, url) {
276
+ if (this.disposed)
277
+ return null;
278
+ const s = this.getSession(sessionId);
279
+ if (!s)
280
+ return null;
281
+ let result;
282
+ if (action === 'goto')
283
+ result = await s.page.goto(url ?? 'about:blank');
284
+ else if (action === 'back')
285
+ result = await s.page.goBack();
286
+ else if (action === 'forward')
287
+ result = await s.page.goForward();
288
+ else
289
+ result = await s.page.reload();
290
+ s.url = result.url;
291
+ s.title = result.title;
292
+ if (result.url && result.url !== 'about:blank')
293
+ this.setLastUrl(result.url);
294
+ this.broadcastControl(s, { type: 'nav', url: result.url, title: result.title });
295
+ return result;
296
+ }
297
+ // ─── Capture: screenshot + rich DOM → attachments ───────────────────────────
298
+ async capture(sessionId, rect, pendingSpecId, opts) {
299
+ if (this.disposed)
300
+ return null;
301
+ const s = this.getSession(sessionId);
302
+ if (!s)
303
+ return null;
304
+ const safeRect = {
305
+ x: Math.max(0, rect.x),
306
+ y: Math.max(0, rect.y),
307
+ width: Math.max(1, rect.width),
308
+ height: Math.max(1, rect.height),
309
+ };
310
+ let png;
311
+ let dom;
312
+ try {
313
+ ;
314
+ [png, dom] = await Promise.all([
315
+ s.page.screenshotClip(safeRect),
316
+ s.page.extractDom(safeRect, DOM_HTML_BYTE_CAP),
317
+ ]);
318
+ }
319
+ catch (err) {
320
+ // The page/context can vanish mid-capture (the dev server restarting,
321
+ // a page crash, the tab being closed). Treat a closed target as a gone
322
+ // session — tear it down and return null (→ 404) instead of a 500 stack.
323
+ if (isTargetClosedError(err)) {
324
+ await this.kill(sessionId);
325
+ return null;
326
+ }
327
+ throw err;
328
+ }
329
+ // Snapshot the recent network requests into the DOM payload (rides the same
330
+ // JSON attachment → spec prompt). ON unless the spec explicitly disabled it.
331
+ if (opts?.captureNetwork !== false) {
332
+ try {
333
+ const reqs = s.page.recentNetwork?.(this.now() - NETWORK_WINDOW_MS) ?? [];
334
+ if (reqs.length > 0)
335
+ dom.networkRequests = reqs;
336
+ }
337
+ catch { /* network snapshot is best-effort */ }
338
+ }
339
+ const stamp = this.now();
340
+ const screenshot = await this.attachments.upload({
341
+ slug: this.projectSlug,
342
+ ticketKey: pendingSpecId,
343
+ projectPath: null,
344
+ file: {
345
+ buffer: png,
346
+ originalname: `screen-capture-${stamp}.png`,
347
+ mimetype: 'image/png',
348
+ size: png.length,
349
+ },
350
+ });
351
+ const domJson = Buffer.from(JSON.stringify(dom, null, 2), 'utf-8');
352
+ const domAttachment = await this.attachments.upload({
353
+ slug: this.projectSlug,
354
+ ticketKey: pendingSpecId,
355
+ projectPath: null,
356
+ file: {
357
+ buffer: domJson,
358
+ originalname: `page-dom-${stamp}.json`,
359
+ mimetype: 'application/json',
360
+ size: domJson.length,
361
+ },
362
+ });
363
+ return { screenshot, domAttachment, dom, screenshotDataUrl: `data:image/png;base64,${png.toString('base64')}` };
364
+ }
365
+ // ─── Clipboard bridge ───────────────────────────────────────────────────────
366
+ /**
367
+ * Bridge the host clipboard to the embedded (headless) page, which has no
368
+ * access to the OS clipboard. `copy`/`cut` return the page's current selection
369
+ * text for the client to write to the host clipboard; `paste` inserts the
370
+ * host clipboard text (sent by the client) at the focused element.
371
+ */
372
+ async clipboard(sessionId, action, text) {
373
+ if (this.disposed)
374
+ return null;
375
+ const s = this.getSession(sessionId);
376
+ if (!s)
377
+ return null;
378
+ if (action === 'paste') {
379
+ if (text)
380
+ await s.page.insertText?.(text);
381
+ return { text: '' };
382
+ }
383
+ const sel = (await s.page.getSelectionText?.()) ?? '';
384
+ if (action === 'cut' && sel)
385
+ await s.page.deleteSelection?.();
386
+ return { text: sel };
387
+ }
388
+ // ─── Multi-breakpoint capture ───────────────────────────────────────────────
389
+ /**
390
+ * Capture the SAME selection at several viewport sizes. The element occupies a
391
+ * different rect at each breakpoint (a nav collapses on mobile), so we resolve
392
+ * a stable anchor selector once at the live viewport and re-query its box per
393
+ * breakpoint, falling back to the original rect when it can't be resolved. The
394
+ * whole sequence is driven server-side (set viewport → settle → re-resolve →
395
+ * shoot) so there is no fire-and-forget WS resize race; the live viewport is
396
+ * always restored. One canonical DOM (the first breakpoint) is stored.
397
+ */
398
+ async captureBreakpoints(sessionId, rect, anchorPoint, pendingSpecId, dims) {
399
+ if (this.disposed)
400
+ return null;
401
+ const s = this.getSession(sessionId);
402
+ if (!s)
403
+ return null;
404
+ const order = Object.keys(dims);
405
+ if (order.length === 0)
406
+ return null;
407
+ const stashed = { ...s.viewport };
408
+ let selector = null;
409
+ try {
410
+ selector = (await s.page.resolveAnchorSelector?.(anchorPoint)) ?? null;
411
+ }
412
+ catch {
413
+ selector = null;
414
+ }
415
+ const captured = {};
416
+ try {
417
+ for (const key of order) {
418
+ const d = dims[key];
419
+ await s.page.setViewport(d.width, d.height);
420
+ s.viewport = { width: d.width, height: d.height };
421
+ try {
422
+ await s.page.waitForStable?.();
423
+ }
424
+ catch { /* settle is best-effort */ }
425
+ let useRect = rect;
426
+ if (selector) {
427
+ try {
428
+ const r = await s.page.resolveAnchorRect?.(selector);
429
+ if (r && r.width > 0 && r.height > 0)
430
+ useRect = r;
431
+ }
432
+ catch { /* fall back to the original rect */ }
433
+ }
434
+ // CLAMP to the current (breakpoint) viewport: a rect resolved at a larger
435
+ // viewport — or the original-rect fallback when the element collapsed /
436
+ // is hidden at this size — can sit outside the smaller viewport, which
437
+ // makes page.screenshot throw "Clipped area is outside the image".
438
+ const cx = Math.max(0, Math.min(useRect.x, d.width - 1));
439
+ const cy = Math.max(0, Math.min(useRect.y, d.height - 1));
440
+ const safeRect = {
441
+ x: cx,
442
+ y: cy,
443
+ width: Math.max(1, Math.min(useRect.width, d.width - cx)),
444
+ height: Math.max(1, Math.min(useRect.height, d.height - cy)),
445
+ };
446
+ const [png, dom] = await Promise.all([
447
+ s.page.screenshotClip(safeRect),
448
+ s.page.extractDom(safeRect, DOM_HTML_BYTE_CAP),
449
+ ]);
450
+ captured[key] = { png, dom };
451
+ }
452
+ }
453
+ catch (err) {
454
+ if (isTargetClosedError(err)) {
455
+ await this.kill(sessionId);
456
+ return null;
457
+ }
458
+ throw err;
459
+ }
460
+ finally {
461
+ try {
462
+ await s.page.setViewport(stashed.width, stashed.height);
463
+ }
464
+ catch { /* ignore */ }
465
+ s.viewport = stashed;
466
+ }
467
+ const stamp = this.now();
468
+ const breakpoints = {};
469
+ for (const key of order) {
470
+ const { png } = captured[key];
471
+ const attachment = await this.attachments.upload({
472
+ slug: this.projectSlug,
473
+ ticketKey: pendingSpecId,
474
+ projectPath: null,
475
+ file: { buffer: png, originalname: `screen-capture-${key}-${stamp}.png`, mimetype: 'image/png', size: png.length },
476
+ });
477
+ breakpoints[key] = { attachment, dataUrl: `data:image/png;base64,${png.toString('base64')}`, viewport: dims[key] };
478
+ }
479
+ // Canonical = the first breakpoint: only its DOM is persisted (avoid tripling
480
+ // the DOM artifact / prompt cost). screenshot/dom point at it.
481
+ const canonicalKey = order[0];
482
+ const canonicalDom = captured[canonicalKey].dom;
483
+ const domJson = Buffer.from(JSON.stringify(canonicalDom, null, 2), 'utf-8');
484
+ const domAttachment = await this.attachments.upload({
485
+ slug: this.projectSlug,
486
+ ticketKey: pendingSpecId,
487
+ projectPath: null,
488
+ file: { buffer: domJson, originalname: `page-dom-${stamp}.json`, mimetype: 'application/json', size: domJson.length },
489
+ });
490
+ return {
491
+ screenshot: breakpoints[canonicalKey].attachment,
492
+ domAttachment,
493
+ dom: canonicalDom,
494
+ screenshotDataUrl: breakpoints[canonicalKey].dataUrl,
495
+ breakpoints,
496
+ };
497
+ }
498
+ // ─── Teardown ───────────────────────────────────────────────────────────────
499
+ async kill(sessionId) {
500
+ const s = this.sessions.get(sessionId);
501
+ if (!s)
502
+ return false;
503
+ this.sessions.delete(sessionId);
504
+ if (s.closed)
505
+ return false;
506
+ s.closed = true;
507
+ s.screencastDesired = false;
508
+ for (const client of s.clients) {
509
+ try {
510
+ client.close(1000, 'session_closed');
511
+ }
512
+ catch { /* ignore */ }
513
+ }
514
+ s.clients.clear();
515
+ try {
516
+ await s.page.close();
517
+ }
518
+ catch { /* ignore */ }
519
+ // NOTE: the persistent Chromium context is deliberately kept alive here even
520
+ // when no sessions remain. Closing it on last-session-kill raced with React
521
+ // StrictMode's mount→unmount→mount in dev: the throwaway first session is
522
+ // killed (sessionCount→0 → context closed) WHILE the real second session is
523
+ // still launching in that same context, breaking it. The context is closed
524
+ // on manager.shutdown() / project removal instead — one idle headless browser
525
+ // per project after use is an acceptable cost.
526
+ return true;
527
+ }
528
+ async shutdown() {
529
+ if (this.disposed)
530
+ return;
531
+ this.disposed = true;
532
+ for (const s of [...this.sessions.values()]) {
533
+ s.closed = true;
534
+ for (const client of s.clients) {
535
+ try {
536
+ client.close(1000, 'shutdown');
537
+ }
538
+ catch { /* ignore */ }
539
+ }
540
+ s.clients.clear();
541
+ try {
542
+ await s.page.close();
543
+ }
544
+ catch { /* ignore */ }
545
+ }
546
+ this.sessions.clear();
547
+ // Resolve the context even if its launch was still in flight when shutdown
548
+ // raced in — otherwise a pending contextPromise settles after we exit and
549
+ // leaks a headless Chromium that nothing will ever close.
550
+ let ctx = this.context;
551
+ if (!ctx && this.contextPromise) {
552
+ try {
553
+ ctx = await this.contextPromise;
554
+ }
555
+ catch {
556
+ ctx = null;
557
+ }
558
+ }
559
+ if (ctx) {
560
+ try {
561
+ await ctx.close();
562
+ }
563
+ catch { /* ignore */ }
564
+ }
565
+ this.context = null;
566
+ this.contextPromise = null;
567
+ }
568
+ sessionCount() {
569
+ return [...this.sessions.values()].filter((s) => !s.closed).length;
570
+ }
571
+ // Wall-clock indirection so tests can stay deterministic if needed.
572
+ now() {
573
+ return Date.now();
574
+ }
575
+ }
576
+ exports.BrowserCaptureManager = BrowserCaptureManager;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ // Shared types for the browser-capture feature ("Add Spec from browser").
3
+ //
4
+ // The manager (`browser-capture-manager.ts`) depends only on these types plus an
5
+ // injected `ContextLauncher`, so it is fully unit-testable with a fake launcher —
6
+ // no real Chromium. The real Playwright/CDP implementation lives in
7
+ // `browser-playwright.ts` and is the ONLY module that imports `playwright`.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.BrowserLaunchError = exports.BrowserLimitExceededError = void 0;
10
+ // ─── Error classes (mirror terminal-manager's typed errors) ───────────────────
11
+ class BrowserLimitExceededError extends Error {
12
+ limit;
13
+ constructor(limit) {
14
+ super('browser_session_limit_exceeded');
15
+ this.name = 'BrowserLimitExceededError';
16
+ this.limit = limit;
17
+ }
18
+ }
19
+ exports.BrowserLimitExceededError = BrowserLimitExceededError;
20
+ class BrowserLaunchError extends Error {
21
+ cause;
22
+ constructor(message, cause) {
23
+ super(message);
24
+ this.name = 'BrowserLaunchError';
25
+ this.cause = cause;
26
+ }
27
+ }
28
+ exports.BrowserLaunchError = BrowserLaunchError;