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,586 @@
1
+ "use strict";
2
+ // Note: the pkg-binary native-addon hijacks (better_sqlite3.node, node-pty, pty.node)
3
+ // used to live here but had to move to an esbuild `banner.js` in scripts/build-sidecar.mjs
4
+ // so they run BEFORE esbuild's top-of-bundle `require('node-pty')` statement.
5
+ // See the banner in that script for the actual patches.
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const http_1 = __importDefault(require("http"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const express_1 = __importDefault(require("express"));
15
+ const ws_1 = require("ws");
16
+ const project_registry_1 = require("./project-registry");
17
+ const desktop_router_1 = require("./desktop-router");
18
+ const project_router_1 = require("./project-router");
19
+ const docs_router_1 = require("./docs-router");
20
+ const auth_1 = require("./auth");
21
+ const ws_routing_1 = require("./ws-routing");
22
+ const terminal_manager_1 = require("./terminal-manager");
23
+ const terminal_shell_integration_1 = require("./terminal-shell-integration");
24
+ const feature_flags_1 = require("./feature-flags");
25
+ const mobile_1 = require("./mobile");
26
+ const telemetry_receiver_1 = require("./telemetry-receiver");
27
+ const telemetry_compactor_1 = require("./telemetry-compactor");
28
+ const path_resolver_1 = require("./path-resolver");
29
+ // Side-effect import: registers every bundled ProviderAdapter (claude, codex,
30
+ // future providers) so `getAdapter`/`hasAdapter`/`listAdapters` are populated
31
+ // before any manager constructs a project context. See
32
+ // openspec/changes/add-multi-provider-support/specs/multi-provider-architecture/spec.md.
33
+ require("./providers");
34
+ const inheritedPathBeforeResolve = (process.env.PATH ?? '').split(process.platform === 'win32' ? ';' : ':').filter(Boolean).length;
35
+ (0, path_resolver_1.resolveStartupPath)();
36
+ const TERMINAL_PANEL_ENABLED = process.env.SPECRAILS_TERMINAL_PANEL !== 'false';
37
+ const BROWSER_CAPTURE_ENABLED = (0, feature_flags_1.isBrowserCaptureEnabled)();
38
+ // Read package.json version once at startup
39
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
40
+ const PKG_VERSION = (() => {
41
+ try {
42
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
43
+ return require('../package.json').version ?? '0.0.0';
44
+ }
45
+ catch {
46
+ return '0.0.0';
47
+ }
48
+ })();
49
+ // ─── Desktop app watchdog ─────────────────────────────────────────────────────
50
+ // When running as a Tauri sidecar, the parent Tauri process passes its PID via
51
+ // --parent-pid=<pid>. We poll every 3 seconds and exit if the parent is gone,
52
+ // preventing orphaned server processes after an app crash.
53
+ const parentPidArg = process.argv.find((a) => a.startsWith('--parent-pid='));
54
+ if (parentPidArg) {
55
+ const parentPid = parseInt(parentPidArg.split('=')[1], 10);
56
+ if (!isNaN(parentPid)) {
57
+ // Poll at 1s (not 3s): on a self-update relaunch the Tauri host exits and the
58
+ // freshly-launched instance needs port 4200 freed fast. The old 3s latency
59
+ // raced the new instance's startup port check and produced "port already in
60
+ // use". Faster detection + a graceful exit shrinks that window.
61
+ const watchdog = setInterval(() => {
62
+ try {
63
+ // signal 0 = existence check only, does not actually send a signal
64
+ process.kill(parentPid, 0);
65
+ }
66
+ catch {
67
+ // Parent process is gone — shut down GRACEFULLY (tree-kill child rails,
68
+ // PTYs, remove the PID file, release the port) instead of a bare
69
+ // process.exit(0) that orphans children and leaks the PID file.
70
+ clearInterval(watchdog);
71
+ void shutdown();
72
+ }
73
+ }, 1000);
74
+ }
75
+ }
76
+ // ─── Parse CLI args ───────────────────────────────────────────────────────────
77
+ let port = 4200;
78
+ for (let i = 2; i < process.argv.length; i++) {
79
+ if (process.argv[i] === '--port' && process.argv[i + 1]) {
80
+ port = parseInt(process.argv[++i], 10);
81
+ }
82
+ }
83
+ // ─── PID file management ──────────────────────────────────────────────────────
84
+ const PID_DIR = path_1.default.join(os_1.default.homedir(), '.specrails');
85
+ const PID_FILE = path_1.default.join(PID_DIR, 'manager.pid');
86
+ function writePidFile() {
87
+ try {
88
+ fs_1.default.mkdirSync(PID_DIR, { recursive: true });
89
+ fs_1.default.writeFileSync(PID_FILE, String(process.pid), 'utf-8');
90
+ }
91
+ catch {
92
+ // Non-fatal
93
+ }
94
+ }
95
+ function removePidFile() {
96
+ try {
97
+ fs_1.default.unlinkSync(PID_FILE);
98
+ }
99
+ catch {
100
+ // Non-fatal
101
+ }
102
+ }
103
+ // ─── Express + WebSocket setup ────────────────────────────────────────────────
104
+ const app = (0, express_1.default)();
105
+ // Host-header validation (H-08) — first barrier, anti DNS-rebinding.
106
+ // Implementation in auth.ts (unit-tested there); index.ts is coverage-excluded.
107
+ app.use(auth_1.hostValidationMiddleware);
108
+ // ─── CORS — allow only localhost origins (CRIT-02) ────────────────────────────
109
+ // Tauri's desktop WebView exposes two different origin formats:
110
+ // - macOS / Linux: tauri://localhost
111
+ // - Windows WebView2: http://tauri.localhost (virtual-host mapping on the
112
+ // custom scheme; shows up as a regular http origin from the fetch layer)
113
+ const ALLOWED_ORIGIN_PATTERN = /^(https?:\/\/(localhost|127\.0\.0\.1|tauri\.localhost)(:\d+)?|tauri:\/\/localhost)$/;
114
+ function isAllowedBrowserOrigin(origin) {
115
+ return origin === undefined || ALLOWED_ORIGIN_PATTERN.test(origin);
116
+ }
117
+ function corsMiddleware(req, res, next) {
118
+ const origin = req.headers['origin'];
119
+ if (origin) {
120
+ if (ALLOWED_ORIGIN_PATTERN.test(origin)) {
121
+ res.setHeader('Access-Control-Allow-Origin', origin);
122
+ res.setHeader('Vary', 'Origin');
123
+ }
124
+ else {
125
+ // Non-localhost origin — reject with 403
126
+ res.status(403).json({ error: 'Forbidden: cross-origin requests not allowed' });
127
+ return;
128
+ }
129
+ }
130
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
131
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Desktop-Token');
132
+ res.setHeader('Access-Control-Max-Age', '600');
133
+ if (req.method === 'OPTIONS') {
134
+ res.sendStatus(204);
135
+ return;
136
+ }
137
+ next();
138
+ }
139
+ app.use(corsMiddleware);
140
+ // ─── Body size limit (MED-02) ─────────────────────────────────────────────────
141
+ app.use(express_1.default.json({ limit: '1mb' }));
142
+ const server = http_1.default.createServer(app);
143
+ const wsServerOptions = {
144
+ noServer: true,
145
+ // Cap inbound frame size (H-10). Shared by the main, terminal and browser WS
146
+ // servers. Terminal/browser input frames (keystrokes, control JSON, pastes)
147
+ // are tiny; 1 MB tolerates a large bracketed paste while bounding the memory
148
+ // a single malicious frame can force the sidecar to buffer.
149
+ maxPayload: 1024 * 1024,
150
+ handleProtocols: (protocols) => {
151
+ if (protocols.has('specrails-desktop'))
152
+ return 'specrails-desktop';
153
+ // Backward compatibility for clients that only offer the auth carrier.
154
+ for (const protocol of protocols) {
155
+ if (protocol.startsWith('desktop-token.'))
156
+ return protocol;
157
+ }
158
+ return false;
159
+ },
160
+ };
161
+ const wss = new ws_1.WebSocketServer(wsServerOptions);
162
+ const terminalWss = new ws_1.WebSocketServer(wsServerOptions);
163
+ const browserWss = new ws_1.WebSocketServer(wsServerOptions);
164
+ const clients = new Map();
165
+ // 8 MB: a healthy client drains far faster than we produce; crossing this means
166
+ // the socket is stalled. Dropping an event is safe — clients reconcile via the
167
+ // REST polling paths — and far better than an unbounded memory leak.
168
+ const WS_BACKPRESSURE_LIMIT_BYTES = 8 * 1024 * 1024;
169
+ const TERMINAL_WS_RE = /^\/ws\/terminal\/([0-9a-f-]+)$/i;
170
+ const BROWSER_WS_RE = /^\/ws\/browser\/([0-9a-f-]+)$/i;
171
+ function rejectUpgrade(socket, status, reason) {
172
+ socket.write(`HTTP/1.1 ${status} ${reason}\r\nConnection: close\r\n\r\n`);
173
+ socket.destroy();
174
+ }
175
+ function authorizeUpgrade(request) {
176
+ const origin = request.headers.origin;
177
+ if (!isAllowedBrowserOrigin(origin))
178
+ return 'forbidden';
179
+ const provided = (0, auth_1.tokenFromUpgradeRequest)(request);
180
+ if (!provided || !(0, auth_1.safeEqual)(provided, (0, auth_1.loadOrGenerateToken)()))
181
+ return 'unauthorized';
182
+ return 'ok';
183
+ }
184
+ function broadcast(msg) {
185
+ const data = JSON.stringify(msg);
186
+ // Project-scoped messages carry a projectId; app-level messages do not.
187
+ const msgProjectId = msg.projectId;
188
+ for (const [client, state] of clients) {
189
+ if (client.readyState !== ws_1.WebSocket.OPEN)
190
+ continue;
191
+ // H-09: route project-scoped messages only to matching/undeclared subscribers.
192
+ if (!(0, ws_routing_1.shouldDeliverToSubscriber)(msgProjectId, state.subscribedProjectId))
193
+ continue;
194
+ // H-10: drop for a back-pressured client instead of growing its buffer.
195
+ if (client.bufferedAmount > WS_BACKPRESSURE_LIMIT_BYTES)
196
+ continue;
197
+ client.send(data);
198
+ }
199
+ // Fan a copy to the Mobile Gateway's in-process bus (no-op when no phone is
200
+ // attached). publish() swallows mobile-side errors so the main loop is safe.
201
+ (0, mobile_1.getMobileEventBus)().publish(msg);
202
+ }
203
+ // ─── Health endpoint state (populated by the Super-mode bootstrap below) ─────
204
+ let _getProjectCount = () => 0;
205
+ /** Captured by the Super-mode bootstrap block so graceful shutdown can tear down every
206
+ * project's spawners (rail/chat children) instead of orphaning them. */
207
+ let _registry = null;
208
+ /** The mobile companion gateway (off by default); torn down on shutdown. */
209
+ let _mobileGateway = null;
210
+ server.on('upgrade', (request, socket, head) => {
211
+ const urlStr = request.url ?? '/';
212
+ const auth = authorizeUpgrade(request);
213
+ if (auth === 'forbidden')
214
+ return rejectUpgrade(socket, 403, 'Forbidden');
215
+ if (auth === 'unauthorized')
216
+ return rejectUpgrade(socket, 401, 'Unauthorized');
217
+ // Terminal PTY WebSocket endpoint: /ws/terminal/:id?projectId=...
218
+ const pathOnly = urlStr.split('?')[0];
219
+ const termMatch = pathOnly.match(TERMINAL_WS_RE);
220
+ if (termMatch) {
221
+ if (!TERMINAL_PANEL_ENABLED)
222
+ return rejectUpgrade(socket, 404, 'Not Found');
223
+ let parsed;
224
+ try {
225
+ parsed = new URL(urlStr, 'http://localhost');
226
+ }
227
+ catch {
228
+ return rejectUpgrade(socket, 400, 'Bad Request');
229
+ }
230
+ const projectId = parsed.searchParams.get('projectId');
231
+ const sessionId = termMatch[1];
232
+ const tm = (0, terminal_manager_1.getTerminalManager)();
233
+ const session = tm.getUnsafe(sessionId);
234
+ if (!session) {
235
+ // The session may have died right after POST /terminals returned (e.g. a
236
+ // shell that failed to acquire a controlling tty). If we have a tombstone,
237
+ // upgrade just long enough to tell the client WHY, then close — otherwise
238
+ // the client sees a bare 404 and a silent dead terminal.
239
+ if (projectId) {
240
+ const tomb = tm.getTombstone(sessionId, projectId);
241
+ if (tomb) {
242
+ terminalWss.handleUpgrade(request, socket, head, (ws) => {
243
+ try {
244
+ ws.send(JSON.stringify({ type: 'exit', code: tomb.code, signal: tomb.signal, early: tomb.early }));
245
+ }
246
+ catch { /* ignore */ }
247
+ try {
248
+ ws.close(1000, tomb.early ? 'pty_exit_early' : 'pty_exit');
249
+ }
250
+ catch { /* ignore */ }
251
+ });
252
+ return;
253
+ }
254
+ }
255
+ return rejectUpgrade(socket, 404, 'Not Found');
256
+ }
257
+ if (!projectId || session.projectId !== projectId)
258
+ return rejectUpgrade(socket, 403, 'Forbidden');
259
+ terminalWss.handleUpgrade(request, socket, head, (ws) => {
260
+ const meta = tm.attach(sessionId, ws);
261
+ if (!meta) {
262
+ // Lost the race: the pty exited between the getUnsafe check and attach.
263
+ const tomb = tm.getTombstone(sessionId, projectId);
264
+ if (tomb) {
265
+ try {
266
+ ws.send(JSON.stringify({ type: 'exit', code: tomb.code, signal: tomb.signal, early: tomb.early }));
267
+ }
268
+ catch { /* ignore */ }
269
+ }
270
+ try {
271
+ ws.close(1000, tomb?.early ? 'pty_exit_early' : 'pty_exit');
272
+ }
273
+ catch { /* ignore */ }
274
+ return;
275
+ }
276
+ ws.on('message', (data, isBinary) => {
277
+ if (isBinary) {
278
+ tm.write(sessionId, data);
279
+ return;
280
+ }
281
+ try {
282
+ const txt = data.toString('utf8');
283
+ const msg = JSON.parse(txt);
284
+ if (msg?.type === 'resize' && typeof msg.cols === 'number' && typeof msg.rows === 'number') {
285
+ tm.resize(sessionId, msg.cols, msg.rows);
286
+ }
287
+ else if (msg?.type === 'write' && typeof msg.data === 'string') {
288
+ tm.write(sessionId, msg.data);
289
+ }
290
+ }
291
+ catch { /* ignore malformed control */ }
292
+ });
293
+ ws.on('close', () => tm.detach(sessionId, ws));
294
+ ws.on('error', () => tm.detach(sessionId, ws));
295
+ });
296
+ return;
297
+ }
298
+ // Embedded-browser screencast WebSocket: /ws/browser/:id?projectId=...
299
+ // Frames stream server→client as binary; client→server are JSON control
300
+ // messages (input/navigate). Kept off the shared /ws so high-rate screencast
301
+ // throughput can't starve the project event stream (mirrors the terminal WS).
302
+ const browserMatch = pathOnly.match(BROWSER_WS_RE);
303
+ if (browserMatch) {
304
+ if (!BROWSER_CAPTURE_ENABLED)
305
+ return rejectUpgrade(socket, 404, 'Not Found');
306
+ let parsed;
307
+ try {
308
+ parsed = new URL(urlStr, 'http://localhost');
309
+ }
310
+ catch {
311
+ return rejectUpgrade(socket, 400, 'Bad Request');
312
+ }
313
+ const projectId = parsed.searchParams.get('projectId');
314
+ const sessionId = browserMatch[1];
315
+ if (!projectId)
316
+ return rejectUpgrade(socket, 400, 'Bad Request');
317
+ const mgr = _registry?.getContext(projectId)?.browserCaptureManager;
318
+ const session = mgr?.getSession(sessionId);
319
+ if (!mgr || !session)
320
+ return rejectUpgrade(socket, 404, 'Not Found');
321
+ browserWss.handleUpgrade(request, socket, head, (ws) => {
322
+ const client = ws;
323
+ void mgr.attach(sessionId, client).then((meta) => {
324
+ if (!meta) {
325
+ // Session died between the upgrade check and attach — tell the client
326
+ // explicitly instead of leaving a silent, frame-less socket open.
327
+ try {
328
+ ws.close(1000, 'session_closed');
329
+ }
330
+ catch { /* ignore */ }
331
+ }
332
+ });
333
+ ws.on('message', (data, isBinary) => {
334
+ if (isBinary)
335
+ return; // browser inbound is JSON control only
336
+ try {
337
+ const msg = JSON.parse(data.toString('utf8'));
338
+ if (msg.type === 'input' && msg.event) {
339
+ void mgr.handleInput(sessionId, msg.event);
340
+ }
341
+ else if (msg.type === 'navigate') {
342
+ void mgr.navigate(sessionId, msg.action ?? 'goto', msg.url);
343
+ }
344
+ else if (msg.type === 'probe' && Number.isFinite(msg.x) && Number.isFinite(msg.y) && msg.x >= 0 && msg.y >= 0) {
345
+ // Hover-to-select: resolve the element under the cursor and reply with
346
+ // its rect so the client can draw a highlight box.
347
+ void mgr.probeElement(sessionId, { x: msg.x, y: msg.y }).then((probe) => {
348
+ try {
349
+ ws.send(JSON.stringify({ type: 'hover', rect: probe?.rect ?? null, selector: probe?.selector ?? null, path: probe?.path ?? null }));
350
+ }
351
+ catch { /* drop */ }
352
+ });
353
+ }
354
+ }
355
+ catch { /* ignore malformed control */ }
356
+ });
357
+ ws.on('close', () => mgr.detach(sessionId, client));
358
+ ws.on('error', () => mgr.detach(sessionId, client));
359
+ });
360
+ return;
361
+ }
362
+ // Default: main event WebSocket.
363
+ wss.handleUpgrade(request, socket, head, (ws) => {
364
+ wss.emit('connection', ws, request);
365
+ });
366
+ });
367
+ // ─── Docs portal (available in all modes) ────────────────────────────────────
368
+ // Loopback-only (H-04): docs are unauthenticated by design (no token needed to
369
+ // read them), so a loopback guard is the only thing standing between them and
370
+ // the network the day the bind changes.
371
+ app.use('/api/docs', auth_1.requireLoopback, (0, docs_router_1.createDocsRouter)());
372
+ // ─── Auth — protect all /api/* except /api/health and /api/token ─────────────
373
+ // (CRIT-01) Token is served publicly so the local client can bootstrap itself.
374
+ app.get('/api/health', (_req, res) => {
375
+ res.json({
376
+ status: 'ok',
377
+ version: PKG_VERSION,
378
+ uptime: Math.floor(process.uptime()),
379
+ projects: _getProjectCount(),
380
+ mode: 'super',
381
+ });
382
+ });
383
+ // Loopback-only (H-03): this is the most sensitive endpoint — it hands out the
384
+ // master token, which grants terminals/spawn/fs/admin. It must stay public (no
385
+ // token) so the local client can bootstrap. Two complementary guards protect it:
386
+ // `requireLoopback` rejects a non-local PEER (matters if the bind ever changes
387
+ // from 127.0.0.1), and the Host-header guard above rejects DNS-rebinding (where
388
+ // the peer IS loopback — the victim's own browser — but the Host is the
389
+ // attacker's domain). Neither alone is sufficient; together they close both.
390
+ app.get('/api/token', auth_1.requireLoopback, (_req, res) => {
391
+ res.json({ token: (0, auth_1.loadOrGenerateToken)() });
392
+ });
393
+ app.use('/api', auth_1.requireAuth);
394
+ // ─── WebSocket rate limiting helper (LOW-03) ──────────────────────────────────
395
+ const WS_MAX_MESSAGES_PER_MINUTE = 120;
396
+ const WS_MAX_MESSAGE_BYTES = 65_536; // 64 KB
397
+ function applyWsRateLimiting(ws) {
398
+ let messageCount = 0;
399
+ const resetTimer = setInterval(() => { messageCount = 0; }, 60_000);
400
+ ws.on('message', (data) => {
401
+ const size = typeof data === 'string' ? Buffer.byteLength(data) : data.byteLength;
402
+ if (size > WS_MAX_MESSAGE_BYTES) {
403
+ ws.close(1009, 'Message too large');
404
+ return;
405
+ }
406
+ messageCount++;
407
+ if (messageCount > WS_MAX_MESSAGES_PER_MINUTE) {
408
+ ws.close(1008, 'Rate limit exceeded');
409
+ }
410
+ });
411
+ ws.on('close', () => {
412
+ clearInterval(resetTimer);
413
+ });
414
+ }
415
+ // ─── Super-mode bootstrap ─────────────────────────────────────────────────────
416
+ {
417
+ const registry = new project_registry_1.ProjectRegistry(broadcast, undefined, port);
418
+ registry.loadAll();
419
+ _registry = registry;
420
+ _getProjectCount = () => registry.listContexts().length;
421
+ // OTLP/JSON receiver — INTENTIONALLY UNAUTHENTICATED (H-01/H-02). The spawned
422
+ // claude/codex CLIs post telemetry here with no auth header (queue-manager sets
423
+ // OTEL_EXPORTER_OTLP_ENDPOINT but no OTEL_EXPORTER_OTLP_HEADERS), so it cannot
424
+ // be put behind requireAuth. It is also NOT covered by `app.use('/api', ...)`
425
+ // — that middleware is path-scoped to /api, and /otlp is a sibling path (the
426
+ // old comment here claiming otherwise was wrong). It is protected instead by
427
+ // `requireLoopback` (children always connect via 127.0.0.1) + the loopback bind.
428
+ app.use('/otlp', auth_1.requireLoopback, (0, telemetry_receiver_1.createTelemetryRouter)(registry));
429
+ // Run telemetry compaction at startup after registry is hydrated
430
+ (0, telemetry_compactor_1.runCompactionForAll)(registry).catch((err) => {
431
+ console.error('[telemetry-compactor] startup compaction error:', err);
432
+ });
433
+ // ─── Mobile companion gateway (off by default; boot if previously enabled) ──
434
+ // Second HTTPS+WSS listener in THIS process; the main server stays loopback.
435
+ // The control plane is desktop-only: loopback + (via /api below) requireAuth.
436
+ // Mounted before the /api desktop router so its sub-path is unambiguous.
437
+ const mobileGateway = new mobile_1.MobileGateway({ desktopDb: registry.desktopDb, desktopPort: port, broadcast });
438
+ _mobileGateway = mobileGateway;
439
+ app.use('/api/mobile', auth_1.requireLoopback, (0, mobile_1.createMobileAdminRouter)({
440
+ gateway: mobileGateway,
441
+ desktopDb: registry.desktopDb,
442
+ broadcast,
443
+ }));
444
+ if (mobileGateway.isEnabledSetting()) {
445
+ mobileGateway.start().catch((err) => console.error('[mobile-gateway] boot start failed:', err));
446
+ }
447
+ // App-level routes. CRITICAL mount order: the desktop router is mounted at
448
+ // '/api' BEFORE the project router below so its exact routes (e.g.
449
+ // GET /api/projects, DELETE /api/projects/:id) are handled here, while
450
+ // everything else under /api/projects/:projectId/* falls through to the
451
+ // project router.
452
+ app.use('/api', (0, desktop_router_1.createDesktopRouter)(registry, broadcast));
453
+ // Per-project routes under /api/projects/:projectId/*
454
+ app.use('/api/projects', (0, project_router_1.createProjectRouter)(registry));
455
+ // Return 410 Gone for the old per-project hook endpoint in Super mode
456
+ app.post('/hooks/events', (_req, res) => {
457
+ res.status(410).json({
458
+ error: 'In Super mode, use /api/projects/:projectId/hooks/events',
459
+ });
460
+ });
461
+ wss.on('connection', (ws) => {
462
+ const state = { subscribedProjectId: null };
463
+ clients.set(ws, state);
464
+ applyWsRateLimiting(ws);
465
+ // H-09: honor an optional `{ type: 'subscribe', projectId }` control frame so
466
+ // a connection can scope itself to one project's events. Anything else on the
467
+ // inbound channel is ignored (the main event WS is otherwise server→client).
468
+ ws.on('message', (data) => {
469
+ const txt = typeof data === 'string' ? data : data.toString('utf8');
470
+ const frame = (0, ws_routing_1.parseSubscribeFrame)(txt);
471
+ if (frame.subscribe)
472
+ state.subscribedProjectId = frame.projectId;
473
+ });
474
+ // Send app state init
475
+ const projects = registry.listContexts().map((ctx) => ctx.project);
476
+ ws.send(JSON.stringify({
477
+ type: 'desktop.projects',
478
+ projects,
479
+ timestamp: new Date().toISOString(),
480
+ }));
481
+ ws.on('close', () => {
482
+ clients.delete(ws);
483
+ });
484
+ });
485
+ }
486
+ // ─── Global async error handler ────────────────────────────────────────────────
487
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
488
+ app.use((err, _req, res, _next) => {
489
+ console.error('[unhandled error]', err);
490
+ if (!res.headersSent) {
491
+ res.status(500).json({ error: 'Internal server error' });
492
+ }
493
+ });
494
+ // ─── Serve built React client (production) ────────────────────────────────────
495
+ const clientDist = path_1.default.resolve(__dirname, '../../client/dist');
496
+ if (fs_1.default.existsSync(clientDist)) {
497
+ app.use(express_1.default.static(clientDist));
498
+ app.get(/^(?!\/api|\/hooks).*/, (_req, res) => {
499
+ res.sendFile(path_1.default.join(clientDist, 'index.html'));
500
+ });
501
+ }
502
+ // ─── Start server ─────────────────────────────────────────────────────────────
503
+ server.on('error', (err) => {
504
+ if (err.code === 'EADDRINUSE') {
505
+ console.error(`[error] Port ${port} is already in use. Is another manager instance running?`);
506
+ console.error(`[error] Try stopping it first: specrails-desktop stop`);
507
+ process.exit(1);
508
+ }
509
+ throw err;
510
+ });
511
+ server.listen(port, '127.0.0.1', () => {
512
+ console.log(`specrails web manager running on http://127.0.0.1:${port}`);
513
+ writePidFile();
514
+ // Sweep stale shell-integration shim directories left behind by previous runs.
515
+ try {
516
+ const removed = (0, terminal_shell_integration_1.cleanupStaleShimDirs)();
517
+ if (removed > 0)
518
+ console.log(`[terminal-shell-integration] cleaned ${removed} stale shim dirs`);
519
+ }
520
+ catch { /* best effort */ }
521
+ void (0, path_resolver_1.augmentPathFromLoginShell)().then(() => {
522
+ const diag = (0, path_resolver_1.getPathDiagnostic)();
523
+ const augmented = diag.pathSegments.length - inheritedPathBeforeResolve;
524
+ const source = process.stdin.isTTY ? 'terminal' : 'gui';
525
+ console.log(`[path-resolver] inherited=${inheritedPathBeforeResolve} augmented=${Math.max(0, augmented)} loginShell=${diag.loginShellStatus} source=${source}`);
526
+ });
527
+ });
528
+ // ─── Clean shutdown ───────────────────────────────────────────────────────────
529
+ let shuttingDown = false;
530
+ async function shutdown() {
531
+ // Idempotent: the watchdog, SIGTERM and SIGINT can all race into here.
532
+ if (shuttingDown)
533
+ return;
534
+ shuttingDown = true;
535
+ removePidFile();
536
+ try {
537
+ _registry?.shutdown();
538
+ }
539
+ catch { /* ignore */ }
540
+ try {
541
+ await (0, terminal_manager_1.getTerminalManager)().shutdown();
542
+ }
543
+ catch { /* ignore */ }
544
+ try {
545
+ await _mobileGateway?.stop();
546
+ }
547
+ catch { /* ignore */ }
548
+ try {
549
+ wss.close();
550
+ }
551
+ catch { /* ignore */ }
552
+ try {
553
+ terminalWss.close();
554
+ }
555
+ catch { /* ignore */ }
556
+ try {
557
+ browserWss.close();
558
+ }
559
+ catch { /* ignore */ }
560
+ // Force-close lingering keep-alive / WebSocket sockets so server.close()'s
561
+ // callback actually fires. The persistent /ws client connection and terminal
562
+ // sockets would otherwise hold the server open, stalling the port release that
563
+ // a relaunching desktop instance is waiting on. (Node 18.2+.)
564
+ try {
565
+ ;
566
+ server.closeAllConnections?.();
567
+ }
568
+ catch { /* ignore */ }
569
+ // Hard-exit fallback in case server.close() still hangs.
570
+ const forceExit = setTimeout(() => process.exit(0), 3000);
571
+ forceExit.unref?.();
572
+ server.close(() => {
573
+ clearTimeout(forceExit);
574
+ process.exit(0);
575
+ });
576
+ }
577
+ process.on('SIGTERM', () => { void shutdown(); });
578
+ process.on('SIGINT', () => { void shutdown(); });
579
+ // Last-resort PID-file cleanup for paths that bypass shutdown() (hard crash,
580
+ // uncaught exception). 'exit' handlers must be synchronous.
581
+ process.on('exit', () => {
582
+ try {
583
+ fs_1.default.unlinkSync(PID_FILE);
584
+ }
585
+ catch { /* best effort */ }
586
+ });