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,117 @@
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.TicketWatcher = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const chokidar_1 = require("chokidar");
10
+ const TICKET_FILE = '.specrails/local-tickets.json';
11
+ const DEBOUNCE_MS = 150;
12
+ /**
13
+ * Watches `.specrails/local-tickets.json` for external changes and broadcasts
14
+ * ticket_updated via WebSocket. One instance per project context.
15
+ */
16
+ class TicketWatcher {
17
+ _watcher = null;
18
+ _debounceTimer = null;
19
+ _projectPath;
20
+ _projectId;
21
+ _broadcast;
22
+ _lastRevision = null;
23
+ _closed = false;
24
+ constructor(projectPath, projectId, broadcast) {
25
+ this._projectPath = projectPath;
26
+ this._projectId = projectId;
27
+ this._broadcast = broadcast;
28
+ }
29
+ /**
30
+ * Start watching the ticket file. Safe to call if file doesn't exist yet —
31
+ * chokidar will detect when it's created.
32
+ */
33
+ start() {
34
+ if (this._closed)
35
+ return;
36
+ const filePath = path_1.default.join(this._projectPath, TICKET_FILE);
37
+ // Seed initial revision so we can detect external changes
38
+ this._lastRevision = this._readRevision(filePath);
39
+ this._watcher = new chokidar_1.FSWatcher({
40
+ persistent: false, // don't keep the process alive
41
+ ignoreInitial: true,
42
+ // awaitWriteFinish helps with rapid writes from CLI agents
43
+ awaitWriteFinish: {
44
+ stabilityThreshold: 80,
45
+ pollInterval: 50,
46
+ },
47
+ });
48
+ this._watcher.add(filePath);
49
+ this._watcher.on('change', () => this._onFileChange(filePath));
50
+ this._watcher.on('add', () => this._onFileChange(filePath));
51
+ this._watcher.on('error', (err) => {
52
+ const msg = err instanceof Error ? err.message : String(err);
53
+ console.error(`[ticket-watcher] error for project ${this._projectId}:`, msg);
54
+ });
55
+ }
56
+ /**
57
+ * Stop watching and clean up. Safe to call multiple times.
58
+ */
59
+ async close() {
60
+ this._closed = true;
61
+ if (this._debounceTimer) {
62
+ clearTimeout(this._debounceTimer);
63
+ this._debounceTimer = null;
64
+ }
65
+ if (this._watcher) {
66
+ await this._watcher.close();
67
+ this._watcher = null;
68
+ }
69
+ }
70
+ /**
71
+ * Notify the watcher that the app itself just wrote to the file,
72
+ * so the next file-change event should be skipped (avoids echo).
73
+ * Call this from ticket API mutation handlers.
74
+ */
75
+ notifyDesktopWrite(newRevision) {
76
+ this._lastRevision = newRevision;
77
+ }
78
+ _onFileChange(filePath) {
79
+ if (this._debounceTimer) {
80
+ clearTimeout(this._debounceTimer);
81
+ }
82
+ this._debounceTimer = setTimeout(() => {
83
+ this._debounceTimer = null;
84
+ this._handleChange(filePath);
85
+ }, DEBOUNCE_MS);
86
+ }
87
+ _handleChange(filePath) {
88
+ const revision = this._readRevision(filePath);
89
+ if (revision === null)
90
+ return; // file unreadable or malformed
91
+ // Skip if revision hasn't changed (app's own write)
92
+ if (this._lastRevision !== null && revision === this._lastRevision)
93
+ return;
94
+ this._lastRevision = revision;
95
+ // Broadcast a generic ticket_updated with the full ticket set so the
96
+ // client can diff. We use a synthetic ticket with id 0 to signal
97
+ // "full refresh" — this is simpler than diffing individual tickets
98
+ // and the client will refetch via API anyway.
99
+ this._broadcast({
100
+ type: 'ticket_updated',
101
+ projectId: this._projectId,
102
+ ticket: { id: 0 },
103
+ timestamp: new Date().toISOString(),
104
+ });
105
+ }
106
+ _readRevision(filePath) {
107
+ try {
108
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
109
+ const data = JSON.parse(raw);
110
+ return typeof data.revision === 'number' ? data.revision : null;
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ }
117
+ exports.TicketWatcher = TicketWatcher;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VALID_PRIORITIES = exports.PRIORITY_WEIGHT = void 0;
4
+ exports.PRIORITY_WEIGHT = {
5
+ low: 0,
6
+ normal: 1,
7
+ high: 2,
8
+ critical: 3,
9
+ };
10
+ exports.VALID_PRIORITIES = new Set(['low', 'normal', 'high', 'critical']);
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ // User-approved MCP injection for Explore Spec turns.
3
+ //
4
+ // When a conversation's ContextScope has `userMcp: true`, the app makes the
5
+ // user's OWN already-approved MCP servers available to the spawned CLI — the
6
+ // ones they registered locally with `claude mcp add` / `codex mcp add`, NOT the
7
+ // project-committed `.mcp.json` (that is the separate `mcp` toggle).
8
+ //
9
+ // Provider behaviour:
10
+ // - claude: read `~/.claude.json` and collect the user-scope (top-level
11
+ // `mcpServers`) and project-local-scope (`projects[<projectPath>].mcpServers`)
12
+ // server definitions, merge them (local wins on key conflict, mirroring
13
+ // claude's own scope precedence), write a `{ "mcpServers": {…} }` file and
14
+ // return `['--mcp-config', <file>]`. The CLI loads `--mcp-config` ADDITIVELY
15
+ // on top of whatever else it discovers (no `--strict-mcp-config`), and the
16
+ // existing `--dangerously-skip-permissions` flag means the tools are
17
+ // callable without an approval prompt. Verified e2e against claude 2.1.169.
18
+ // - codex: returns `[]`. codex chat turns spawn with `env: process.env` and no
19
+ // `CODEX_HOME` override, so codex already reads the user's global
20
+ // `~/.codex/config.toml` MCP servers natively — no injection needed.
21
+ //
22
+ // The spawn cwd is intentionally left unchanged (the Explore latency win of the
23
+ // app-managed `explore-cwd/` is preserved); `--mcp-config` carries an absolute
24
+ // path so cwd is irrelevant.
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.readUserClaudeMcpServers = readUserClaudeMcpServers;
30
+ exports.writeUserMcpConfig = writeUserMcpConfig;
31
+ exports.buildUserMcpArgs = buildUserMcpArgs;
32
+ const node_fs_1 = __importDefault(require("node:fs"));
33
+ const node_path_1 = __importDefault(require("node:path"));
34
+ const node_os_1 = __importDefault(require("node:os"));
35
+ /**
36
+ * Read the user's approved claude MCP servers from `~/.claude.json`:
37
+ * user-scope (top-level `mcpServers`) merged with the project's local-scope
38
+ * (`projects[projectPath].mcpServers`). Local scope overrides user scope on a
39
+ * key conflict, matching claude's documented precedence (local > user).
40
+ *
41
+ * Returns `{}` when the file is missing, unparseable, or has no servers. Never
42
+ * throws — a malformed user config must not break the chat turn.
43
+ *
44
+ * @param projectPath absolute path of the project (the local-scope key)
45
+ * @param homeDir override the home directory (tests)
46
+ */
47
+ function readUserClaudeMcpServers(projectPath, homeDir) {
48
+ const home = homeDir ?? node_os_1.default.homedir();
49
+ const configPath = node_path_1.default.join(home, '.claude.json');
50
+ let raw;
51
+ try {
52
+ raw = node_fs_1.default.readFileSync(configPath, 'utf-8');
53
+ }
54
+ catch {
55
+ return {};
56
+ }
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ }
61
+ catch {
62
+ return {};
63
+ }
64
+ const userScope = parsed && typeof parsed.mcpServers === 'object' && parsed.mcpServers
65
+ ? parsed.mcpServers
66
+ : {};
67
+ const projectEntry = parsed && typeof parsed.projects === 'object' && parsed.projects
68
+ ? parsed.projects[projectPath]
69
+ : undefined;
70
+ const localScope = projectEntry && typeof projectEntry.mcpServers === 'object' && projectEntry.mcpServers
71
+ ? projectEntry.mcpServers
72
+ : {};
73
+ return { ...userScope, ...localScope };
74
+ }
75
+ /**
76
+ * Write the merged server map to a `{ "mcpServers": {…} }` file under the
77
+ * project's data directory and return its absolute path. chmod 600 because
78
+ * server `env` blocks can carry secrets (same trust domain as `~/.claude.json`).
79
+ *
80
+ * @param baseDir override `~/.specrails/projects` (tests)
81
+ */
82
+ function writeUserMcpConfig(servers, slug, baseDir) {
83
+ // Defence-in-depth: slug is always DB-sourced and slugified upstream
84
+ // (`/^[a-z0-9-]+/`), but guard the filesystem write directly so a future
85
+ // caller passing untrusted input cannot escape the projects directory.
86
+ // buildUserMcpArgs() swallows the throw and falls back to no injection.
87
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
88
+ throw new Error(`unsafe project slug for user-mcp config: ${JSON.stringify(slug)}`);
89
+ }
90
+ const base = baseDir ?? node_path_1.default.join(node_os_1.default.homedir(), '.specrails', 'projects');
91
+ const dir = node_path_1.default.join(base, slug);
92
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
93
+ const file = node_path_1.default.join(dir, 'user-mcp.json');
94
+ node_fs_1.default.writeFileSync(file, JSON.stringify({ mcpServers: servers }, null, 2), { mode: 0o600 });
95
+ return file;
96
+ }
97
+ /**
98
+ * Build the extra CLI args that load the user's approved MCP servers, or `[]`
99
+ * when there is nothing to inject (non-claude provider, or no user/local
100
+ * servers configured). Never throws.
101
+ */
102
+ function buildUserMcpArgs(input) {
103
+ // codex reads ~/.codex natively; only claude needs explicit injection.
104
+ if (input.adapterId !== 'claude')
105
+ return [];
106
+ try {
107
+ const servers = readUserClaudeMcpServers(input.projectPath, input.homeDir);
108
+ if (Object.keys(servers).length === 0)
109
+ return [];
110
+ const file = writeUserMcpConfig(servers, input.slug, input.baseDir);
111
+ return ['--mcp-config', file];
112
+ }
113
+ catch (err) {
114
+ console.error('[user-mcp-config] failed to build --mcp-config args:', err);
115
+ return [];
116
+ }
117
+ }
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ // Centralized claude/codex spawn wrapper.
3
+ //
4
+ // Why this exists:
5
+ //
6
+ // On Windows, cross-spawn invokes claude.cmd / codex.cmd through
7
+ // `cmd.exe /d /s /c "..."`. cmd.exe does NOT preserve newlines
8
+ // inside argv values: any `\n` in `--system-prompt`,
9
+ // `--append-system-prompt`, `-p`, or codex's positional prompt
10
+ // truncates the arg there and the rest of the command line gets
11
+ // reparsed as orphan tokens. Visible symptoms include
12
+ // "Input must be provided either through stdin or as a prompt
13
+ // argument when using --print" and assistant messages that look
14
+ // like "your message got cut off — you wrote 'are' but I'm not
15
+ // sure what you were asking".
16
+ //
17
+ // On POSIX argv passes through cleanly, so we keep that path.
18
+ //
19
+ // The helpers below detect multi-line argv values on Windows,
20
+ // reroute them through child stdin (claude reads stdin when
21
+ // `-p`/`--print` has no positional argument; codex `exec -` does the
22
+ // equivalent), and call spawnCli. POSIX is unchanged byte-for-byte.
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.transformClaudeArgsForWindows = transformClaudeArgsForWindows;
25
+ exports.transformCodexArgsForWindows = transformCodexArgsForWindows;
26
+ exports.ensureStdinPipe = ensureStdinPipe;
27
+ exports.spawnClaude = spawnClaude;
28
+ exports.spawnCodex = spawnCodex;
29
+ exports.spawnAiCli = spawnAiCli;
30
+ const win_spawn_1 = require("./win-spawn");
31
+ const isWin = process.platform === 'win32';
32
+ const CLAUDE_PROMPT_FLAGS = new Set([
33
+ '--system-prompt',
34
+ '--append-system-prompt',
35
+ '-p',
36
+ '--print',
37
+ ]);
38
+ function transformClaudeArgsForWindows(args) {
39
+ const collected = [];
40
+ const out = [];
41
+ for (let i = 0; i < args.length; i++) {
42
+ const a = args[i];
43
+ if (CLAUDE_PROMPT_FLAGS.has(a) && i + 1 < args.length) {
44
+ collected.push(args[i + 1]);
45
+ i++; // skip the value
46
+ continue;
47
+ }
48
+ out.push(a);
49
+ }
50
+ if (collected.length === 0) {
51
+ return { args: out, stdinPayload: null };
52
+ }
53
+ // Re-add `-p` so claude knows to read stdin (--print mode).
54
+ out.push('-p');
55
+ return { args: out, stdinPayload: collected.join('\n\n---\n\n') };
56
+ }
57
+ // Codex `exec` flags we currently use that take a value (rest are
58
+ // boolean). Update if we ever pass new value-bearing flags.
59
+ const CODEX_EXEC_VALUE_FLAGS = new Set(['--model', '--sandbox', '-c']);
60
+ function transformCodexArgsForWindows(args) {
61
+ // Expected shapes:
62
+ // exec [...flags] <prompt> [...flags]
63
+ // exec resume [...flags] <sessionId> <prompt> [...flags]
64
+ if (args.length === 0 || args[0] !== 'exec') {
65
+ return { args, stdinPayload: null };
66
+ }
67
+ const out = ['exec'];
68
+ const isResume = args[1] === 'resume';
69
+ if (isResume)
70
+ out.push('resume');
71
+ let stdin = null;
72
+ let promptReplacedIdx = -1;
73
+ let positionalCount = 0;
74
+ let i = isResume ? 2 : 1;
75
+ while (i < args.length) {
76
+ const a = args[i];
77
+ if (a.startsWith('-') && a !== '-') {
78
+ out.push(a);
79
+ if (CODEX_EXEC_VALUE_FLAGS.has(a) && i + 1 < args.length) {
80
+ out.push(args[i + 1]);
81
+ i += 2;
82
+ continue;
83
+ }
84
+ i += 1;
85
+ continue;
86
+ }
87
+ positionalCount += 1;
88
+ const isPrompt = isResume ? positionalCount === 2 : positionalCount === 1;
89
+ if (isPrompt && stdin === null) {
90
+ stdin = a;
91
+ promptReplacedIdx = out.length;
92
+ out.push('-');
93
+ }
94
+ else {
95
+ out.push(a);
96
+ }
97
+ i += 1;
98
+ }
99
+ if (stdin === null || !stdin.includes('\n')) {
100
+ // Single-line prompts pass through cmd.exe fine — keep argv to
101
+ // dodge any codex versions that don't recognise `-` as stdin.
102
+ if (stdin !== null && promptReplacedIdx >= 0) {
103
+ out[promptReplacedIdx] = stdin;
104
+ }
105
+ return { args: out, stdinPayload: null };
106
+ }
107
+ return { args: out, stdinPayload: stdin };
108
+ }
109
+ function ensureStdinPipe(stdio) {
110
+ const fallback = ['pipe', 'pipe', 'pipe'];
111
+ if (stdio === undefined)
112
+ return fallback;
113
+ if (typeof stdio === 'string') {
114
+ // 'pipe' | 'inherit' | 'ignore' | 'overlapped'
115
+ return ['pipe', stdio, stdio];
116
+ }
117
+ if (Array.isArray(stdio)) {
118
+ return [
119
+ stdio[0] === 'ignore' ? 'pipe' : (stdio[0] ?? 'pipe'),
120
+ stdio[1] ?? 'pipe',
121
+ stdio[2] ?? 'pipe',
122
+ ];
123
+ }
124
+ return fallback;
125
+ }
126
+ /**
127
+ * Spawn `claude` with arg-rewrite on Windows so multi-line prompts
128
+ * survive. POSIX call is identical to `spawnCli('claude', args, options)`.
129
+ */
130
+ function spawnClaude(args, options = {}) {
131
+ if (!isWin) {
132
+ return (0, win_spawn_1.spawnCli)('claude', args, options);
133
+ }
134
+ /* c8 ignore start -- Windows-only branch; coverage runs on Linux/macOS */
135
+ const { args: winArgs, stdinPayload } = transformClaudeArgsForWindows(args);
136
+ if (stdinPayload === null) {
137
+ return (0, win_spawn_1.spawnCli)('claude', winArgs, options);
138
+ }
139
+ const child = (0, win_spawn_1.spawnCli)('claude', winArgs, {
140
+ ...options,
141
+ stdio: ensureStdinPipe(options.stdio),
142
+ });
143
+ if (child.stdin)
144
+ child.stdin.end(stdinPayload);
145
+ return child;
146
+ /* c8 ignore stop */
147
+ }
148
+ /**
149
+ * Spawn `codex` with arg-rewrite on Windows so multi-line prompts
150
+ * survive. POSIX call is identical to `spawnCli('codex', args, options)`.
151
+ */
152
+ function spawnCodex(args, options = {}) {
153
+ if (!isWin) {
154
+ return (0, win_spawn_1.spawnCli)('codex', args, options);
155
+ }
156
+ /* c8 ignore start -- Windows-only branch; coverage runs on Linux/macOS */
157
+ const { args: winArgs, stdinPayload } = transformCodexArgsForWindows(args);
158
+ if (stdinPayload === null) {
159
+ return (0, win_spawn_1.spawnCli)('codex', winArgs, options);
160
+ }
161
+ const child = (0, win_spawn_1.spawnCli)('codex', winArgs, {
162
+ ...options,
163
+ stdio: ensureStdinPipe(options.stdio),
164
+ });
165
+ if (child.stdin)
166
+ child.stdin.end(stdinPayload);
167
+ return child;
168
+ /* c8 ignore stop */
169
+ }
170
+ /**
171
+ * Convenience: dispatch on binary name. Use when callsite picks the
172
+ * binary dynamically (claude vs codex). Anything else routes through
173
+ * the underlying spawnCli unchanged.
174
+ */
175
+ function spawnAiCli(binary, args, options = {}) {
176
+ if (binary === 'claude')
177
+ return spawnClaude(args, options);
178
+ if (binary === 'codex')
179
+ return spawnCodex(args, options);
180
+ return (0, win_spawn_1.spawnCli)(binary, args, options);
181
+ }
@@ -0,0 +1,50 @@
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.secureDir = secureDir;
7
+ exports.secureDbFile = secureDbFile;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ /**
10
+ * Best-effort filesystem hardening for the ~/.specrails data stores (H-13).
11
+ *
12
+ * The auth token is deliberately persisted 0600, but the SQLite databases were
13
+ * created with the default umask (typically dir 0755 / file 0644), leaving
14
+ * webhook HMAC secrets, chat transcripts, and verbatim terminal command history
15
+ * world-readable on a multi-user machine. These helpers restrict the data dir to
16
+ * owner-only and the db files (plus their WAL sidecars) to owner read/write.
17
+ *
18
+ * POSIX permission bits are a no-op on Windows (NTFS uses ACLs and the per-user
19
+ * profile already isolates the home dir), so both helpers early-return there —
20
+ * we never pretend a chmod that did nothing succeeded.
21
+ */
22
+ /** Restrict a directory to owner-only (0700). No-op on Windows / on error. */
23
+ function secureDir(dir) {
24
+ if (process.platform === 'win32')
25
+ return;
26
+ try {
27
+ fs_1.default.chmodSync(dir, 0o700);
28
+ }
29
+ catch {
30
+ // Best-effort: a chmod failure must never crash startup.
31
+ }
32
+ }
33
+ /**
34
+ * Restrict a SQLite db file and its `-wal`/`-shm` sidecars to 0600.
35
+ * No-op on Windows, for `:memory:`, and on error (sidecars may not exist yet).
36
+ */
37
+ function secureDbFile(dbPath) {
38
+ if (process.platform === 'win32')
39
+ return;
40
+ if (dbPath === ':memory:')
41
+ return;
42
+ for (const file of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
43
+ try {
44
+ fs_1.default.chmodSync(file, 0o600);
45
+ }
46
+ catch {
47
+ // Sidecar may not exist yet, or fs may reject — best-effort.
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ // Cross-platform spawn wrapper.
3
+ //
4
+ // Two Windows-specific problems forced this helper:
5
+ //
6
+ // 1) `claude` (and similar npm-installed binaries) is shipped as a
7
+ // `.cmd` shim. `spawn('claude', ..., { shell: false })` fails
8
+ // with ENOENT because Node looks for an exact `claude` file
9
+ // without extension expansion.
10
+ //
11
+ // 2) Setting `shell: true` makes Windows resolve the shim, but
12
+ // cmd.exe then re-parses the concatenated command line and
13
+ // truncates any arg containing `\n` (e.g. claude's
14
+ // `--system-prompt "You are a...\n..."`).
15
+ //
16
+ // Since Node 20.12 / CVE-2024-27980 the obvious middle ground —
17
+ // `spawn('claude.cmd', ..., { shell: false })` — also fails, this
18
+ // time with EINVAL: Node refuses to spawn .cmd/.bat without a
19
+ // shell. `cross-spawn` is the de-facto fix: on Windows it
20
+ // internally launches `cmd.exe /d /s /c` with quoted-then-escaped
21
+ // args so newlines and shell metacharacters survive intact, and on
22
+ // POSIX it falls through to the native `child_process.spawn`.
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.spawnCli = spawnCli;
28
+ exports.resolveWindowsBinary = resolveWindowsBinary;
29
+ const child_process_1 = require("child_process");
30
+ const cross_spawn_1 = __importDefault(require("cross-spawn"));
31
+ function spawnCli(binary, args, options = {}) {
32
+ /* c8 ignore next 3 -- Windows-only branch; coverage runs on Linux/macOS */
33
+ if (process.platform === 'win32') {
34
+ return (0, cross_spawn_1.default)(binary, args, options);
35
+ }
36
+ return (0, child_process_1.spawn)(binary, args, options);
37
+ }
38
+ // Back-compat for callsites that only need the resolved binary
39
+ // (e.g. logging). Kept as a no-op identity on POSIX; on Windows
40
+ // `where`-based resolution lives inside cross-spawn now.
41
+ function resolveWindowsBinary(name) {
42
+ return name;
43
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebhookManager = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const desktop_db_1 = require("./desktop-db");
6
+ const WEBHOOK_TIMEOUT_MS = 10_000;
7
+ const MAX_ATTEMPTS = 3;
8
+ function delay(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ // ─── WebhookManager ───────────────────────────────────────────────────────────
12
+ class WebhookManager {
13
+ _desktopDb;
14
+ constructor(desktopDb) {
15
+ this._desktopDb = desktopDb;
16
+ }
17
+ /**
18
+ * Send a test ping to a single webhook (used by the test endpoint).
19
+ */
20
+ deliverTest(webhook) {
21
+ const payload = {
22
+ event: 'job.completed',
23
+ timestamp: new Date().toISOString(),
24
+ projectId: webhook.project_id ?? '*',
25
+ data: { test: true, message: 'specrails-desktop webhook test ping' },
26
+ };
27
+ setImmediate(() => {
28
+ this._deliverWithRetry(webhook, payload).catch(() => { });
29
+ });
30
+ }
31
+ /**
32
+ * Deliver an event to all matching webhooks for a project.
33
+ * Non-blocking: fires and forgets with retry logic.
34
+ */
35
+ deliver(projectId, event, data) {
36
+ const webhooks = (0, desktop_db_1.listWebhooksForProject)(this._desktopDb, projectId);
37
+ const matching = webhooks.filter((w) => {
38
+ try {
39
+ const events = JSON.parse(w.events);
40
+ return events.includes(event);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ });
46
+ for (const webhook of matching) {
47
+ const payload = {
48
+ event,
49
+ timestamp: new Date().toISOString(),
50
+ projectId,
51
+ data,
52
+ };
53
+ // Fire-and-forget with retry; errors are swallowed after exhausting attempts
54
+ setImmediate(() => {
55
+ this._deliverWithRetry(webhook, payload).catch(() => { });
56
+ });
57
+ }
58
+ }
59
+ async _deliverWithRetry(webhook, payload, attempt = 1) {
60
+ const body = JSON.stringify(payload);
61
+ const headers = {
62
+ 'Content-Type': 'application/json',
63
+ 'User-Agent': 'specrails-desktop',
64
+ };
65
+ if (webhook.secret) {
66
+ const sig = (0, crypto_1.createHmac)('sha256', webhook.secret).update(body).digest('hex');
67
+ headers['X-Specrails-Signature'] = `sha256=${sig}`;
68
+ }
69
+ try {
70
+ const res = await fetch(webhook.url, {
71
+ method: 'POST',
72
+ headers,
73
+ body,
74
+ signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS),
75
+ });
76
+ if (!res.ok && attempt < MAX_ATTEMPTS) {
77
+ await delay(1000 * attempt);
78
+ return this._deliverWithRetry(webhook, payload, attempt + 1);
79
+ }
80
+ }
81
+ catch {
82
+ if (attempt < MAX_ATTEMPTS) {
83
+ await delay(1000 * attempt);
84
+ return this._deliverWithRetry(webhook, payload, attempt + 1);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ exports.WebhookManager = WebhookManager;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // Pure, testable WebSocket routing decisions (H-09). Kept out of index.ts (which
3
+ // is excluded from coverage and not unit-tested) so the project-isolation logic
4
+ // that the Mobile Gateway will depend on is covered and verifiable.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shouldDeliverToSubscriber = shouldDeliverToSubscriber;
7
+ exports.parseSubscribeFrame = parseSubscribeFrame;
8
+ /**
9
+ * Decide whether a broadcast message should be delivered to a given connection.
10
+ *
11
+ * - App-level messages (no projectId) go to everyone.
12
+ * - A connection that has NOT declared a subscription (`subscribedProjectId`
13
+ * null) receives everything — back-compat with the current web client, whose
14
+ * own client-side filter remains as a redundant second layer.
15
+ * - A connection subscribed to project P receives only P's project-scoped
16
+ * messages (plus all app-level ones).
17
+ *
18
+ * The Mobile Gateway turns this into a hard authorization boundary by always
19
+ * subscribing each device connection to exactly the project(s) it may see.
20
+ */
21
+ function shouldDeliverToSubscriber(msgProjectId, subscribedProjectId) {
22
+ if (msgProjectId === undefined)
23
+ return true;
24
+ if (subscribedProjectId === null)
25
+ return true;
26
+ return subscribedProjectId === msgProjectId;
27
+ }
28
+ /**
29
+ * Parse an inbound WS control frame and return the projectId to subscribe to.
30
+ *
31
+ * Returns `{ subscribe: true, projectId }` for a well-formed
32
+ * `{ type: 'subscribe', projectId: <string|null> }` frame (a non-string
33
+ * projectId clears the subscription → null), or `{ subscribe: false }` for
34
+ * anything else / malformed input. Never throws.
35
+ */
36
+ function parseSubscribeFrame(raw) {
37
+ try {
38
+ const parsed = JSON.parse(raw);
39
+ if (parsed?.type === 'subscribe') {
40
+ return { subscribe: true, projectId: typeof parsed.projectId === 'string' ? parsed.projectId : null };
41
+ }
42
+ }
43
+ catch {
44
+ // Malformed control frame — ignore.
45
+ }
46
+ return { subscribe: false, projectId: null };
47
+ }