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,735 @@
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.createDesktopRouter = createDesktopRouter;
7
+ const express_1 = require("express");
8
+ const crypto_1 = require("crypto");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const net_1 = __importDefault(require("net"));
12
+ const desktop_db_1 = require("./desktop-db");
13
+ const webhook_manager_1 = require("./webhook-manager");
14
+ const specrails_tech_client_1 = require("./specrails-tech-client");
15
+ const core_compat_1 = require("./core-compat");
16
+ const providers_1 = require("./providers");
17
+ const desktop_analytics_1 = require("./desktop-analytics");
18
+ const setup_prerequisites_1 = require("./setup-prerequisites");
19
+ const path_resolver_1 = require("./path-resolver");
20
+ const terminal_settings_1 = require("./terminal-settings");
21
+ function slugify(name) {
22
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
23
+ }
24
+ // Emergency rollback for the codex provider: SPECRAILS_CODEX_BETA=0 forces
25
+ // codex back to "unavailable" without redeploying. The pre-rebrand
26
+ // SPECRAILS_HUB_CODEX_BETA name is read as a legacy fallback when the new
27
+ // var is unset (legacy fallback — do not remove while old installs exist).
28
+ function isCodexBetaDisabled() {
29
+ const v = process.env.SPECRAILS_CODEX_BETA ?? process.env.SPECRAILS_HUB_CODEX_BETA;
30
+ return v === '0';
31
+ }
32
+ // Theme allow-list. Mirror of THEME_IDS in `client/src/lib/themes.ts` —
33
+ // kept duplicated to avoid pulling client code into the server bundle.
34
+ const THEME_ID_ALLOWLIST = new Set(['dracula', 'aurora-light', 'obsidian-dark', 'matrix', 'specrails']);
35
+ // Language allow-list. Mirror of LANGUAGE_IDS in `client/src/lib/i18n.ts` —
36
+ // kept duplicated to avoid pulling client code into the server bundle.
37
+ const LANGUAGE_ID_ALLOWLIST = new Set(['en', 'es', 'fr', 'de', 'pt', 'it', 'zh', 'ja']);
38
+ // LOW-04: Deny registration of system-critical directory paths.
39
+ const DENIED_PATH_PREFIXES = [
40
+ '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64',
41
+ '/sys', '/proc', '/dev', '/boot', '/run',
42
+ ];
43
+ function isPathSafe(resolvedPath) {
44
+ const normalized = resolvedPath.endsWith('/') ? resolvedPath : resolvedPath + '/';
45
+ return !DENIED_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix + '/') || normalized === prefix + '/');
46
+ }
47
+ function deriveProjectName(projectPath) {
48
+ return path_1.default.basename(projectPath);
49
+ }
50
+ function hasCommandFiles(dir) {
51
+ try {
52
+ return fs_1.default.readdirSync(dir).some((f) => f.endsWith('.md'));
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ function hasSpecrails(projectPath) {
59
+ return hasCommandFiles(path_1.default.join(projectPath, '.claude', 'commands', 'sr'))
60
+ || hasCommandFiles(path_1.default.join(projectPath, '.claude', 'commands', 'specrails'));
61
+ }
62
+ function canonicalizePath(resolvedPath) {
63
+ try {
64
+ return fs_1.default.realpathSync(resolvedPath);
65
+ }
66
+ catch {
67
+ return resolvedPath;
68
+ }
69
+ }
70
+ function isLoopbackHost(hostname) {
71
+ const host = hostname.toLowerCase();
72
+ return host === 'localhost' || host === '127.0.0.1' || host === '::1';
73
+ }
74
+ function isPrivateIp(hostname) {
75
+ const ipVersion = net_1.default.isIP(hostname);
76
+ if (ipVersion === 0)
77
+ return false;
78
+ if (ipVersion === 6) {
79
+ const host = hostname.toLowerCase();
80
+ return host === '::1' || host.startsWith('fc') || host.startsWith('fd') || host.startsWith('fe80:');
81
+ }
82
+ const parts = hostname.split('.').map((p) => Number.parseInt(p, 10));
83
+ const [a, b] = parts;
84
+ return a === 0 ||
85
+ a === 10 ||
86
+ a === 127 ||
87
+ (a === 169 && b === 254) ||
88
+ (a === 172 && b >= 16 && b <= 31) ||
89
+ (a === 192 && b === 168);
90
+ }
91
+ function validateHttpUrl(raw, opts) {
92
+ let parsed;
93
+ try {
94
+ parsed = new URL(raw);
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
100
+ return null;
101
+ if (opts.requireHttps && parsed.protocol !== 'https:') {
102
+ if (!opts.allowLoopback || !isLoopbackHost(parsed.hostname))
103
+ return null;
104
+ }
105
+ if (!opts.allowLoopback && (isLoopbackHost(parsed.hostname) || isPrivateIp(parsed.hostname)))
106
+ return null;
107
+ return parsed.toString().replace(/\/$/, '');
108
+ }
109
+ function publicWebhook(row) {
110
+ if (!row)
111
+ return row;
112
+ const { secret: _secret, ...rest } = row;
113
+ return { ...rest, hasSecret: row.secret.length > 0 };
114
+ }
115
+ function createDesktopRouter(registry, broadcast) {
116
+ const router = (0, express_1.Router)();
117
+ // GET /api/projects — list all registered projects
118
+ router.get('/projects', (_req, res) => {
119
+ const projects = (0, desktop_db_1.listProjects)(registry.desktopDb);
120
+ // Detect projects that are currently in the setup wizard so the client
121
+ // can restore the wizard after a page refresh.
122
+ const setupProjectIds = [];
123
+ for (const p of projects) {
124
+ const ctx = registry.getContext(p.id);
125
+ if (!ctx)
126
+ continue;
127
+ const installing = ctx.setupManager.isInstalling(p.id);
128
+ const settingUp = ctx.setupManager.isSettingUp(p.id);
129
+ const hasSession = !!(0, desktop_db_1.getProjectSetupSession)(registry.desktopDb, p.id);
130
+ const specrailsInstalled = hasSpecrails(p.path);
131
+ if (installing || settingUp || (hasSession && !specrailsInstalled)) {
132
+ setupProjectIds.push(p.id);
133
+ }
134
+ }
135
+ res.json({ projects, setupProjectIds });
136
+ });
137
+ // GET /api/available-providers — which AI CLIs are installed, plus supported install tiers
138
+ //
139
+ // Codex (OpenAI) is supported as a first-class provider as of Stage C of
140
+ // the multi-provider work. The `SPECRAILS_CODEX_BETA=0` env var is honoured
141
+ // as an emergency rollback (forces codex back to "unavailable" in the UI
142
+ // without redeploying) — unset or `1` reports the real detection.
143
+ router.get('/available-providers', (_req, res) => {
144
+ const providers = (0, core_compat_1.detectAvailableCLIs)();
145
+ // tiers: quick install is always available (app-driven config); full requires an AI CLI
146
+ const tiers = ['quick'];
147
+ if (providers.claude || providers.codex)
148
+ tiers.push('full');
149
+ const codexBetaOff = isCodexBetaDisabled();
150
+ res.json({
151
+ claude: providers.claude,
152
+ codex: codexBetaOff ? false : providers.codex,
153
+ tiers,
154
+ });
155
+ });
156
+ router.get('/setup-prerequisites', (req, res) => {
157
+ const status = (0, setup_prerequisites_1.getSetupPrerequisitesStatus)();
158
+ if (req.query.diagnostic === '1') {
159
+ const diag = (0, path_resolver_1.getPathDiagnostic)();
160
+ const whichResults = {};
161
+ for (const item of status.prerequisites) {
162
+ whichResults[item.command] = item.resolvedPath ?? null;
163
+ }
164
+ res.json({
165
+ ...status,
166
+ diagnostic: {
167
+ pathSegments: diag.pathSegments,
168
+ pathSources: diag.pathSources,
169
+ loginShellStatus: diag.loginShellStatus,
170
+ whichResults,
171
+ nodeEnv: process.env.NODE_ENV ?? null,
172
+ platform: status.platform,
173
+ },
174
+ });
175
+ return;
176
+ }
177
+ res.json(status);
178
+ });
179
+ // POST /api/projects — register a new project by path
180
+ router.post('/projects', (req, res) => {
181
+ const { path: projectPath, name, provider, providers: providersRaw } = req.body ?? {};
182
+ if (!projectPath || typeof projectPath !== 'string') {
183
+ res.status(400).json({ error: 'path is required' });
184
+ return;
185
+ }
186
+ // Normalise to a providers list. New multi-provider clients send
187
+ // `providers: ['claude','codex']`; legacy clients send a single
188
+ // `provider`; omitting both defaults to ['claude']. The first entry is the
189
+ // primary/default provider.
190
+ let providers;
191
+ if (Array.isArray(providersRaw) && providersRaw.length > 0) {
192
+ providers = providersRaw;
193
+ }
194
+ else if (typeof provider === 'string') {
195
+ providers = [provider];
196
+ }
197
+ else {
198
+ providers = ['claude'];
199
+ }
200
+ // De-duplicate while preserving order (primary stays first).
201
+ providers = providers.filter((p, i) => providers.indexOf(p) === i);
202
+ // Provider validation walks the registry — `claude` and `codex` are
203
+ // both accepted as of Stage C; future providers register one adapter
204
+ // file and become acceptable here without further changes.
205
+ for (const p of providers) {
206
+ if (!(0, providers_1.hasAdapter)(p)) {
207
+ res.status(400).json({
208
+ error: `provider must be one of: ${[...(0, providers_1.listAdapters)().map((a) => a.id)].join(', ')}`,
209
+ });
210
+ return;
211
+ }
212
+ }
213
+ // Beta-gate parity: if codex beta is forced off via env, refuse codex
214
+ // selections too (consistency with /available-providers).
215
+ if (providers.includes('codex') && isCodexBetaDisabled()) {
216
+ res.status(400).json({
217
+ error: 'Codex provider is currently disabled (SPECRAILS_CODEX_BETA=0). Unset or set to 1 to enable.',
218
+ });
219
+ return;
220
+ }
221
+ const resolvedPath = path_1.default.resolve(projectPath);
222
+ // Validate path exists
223
+ if (!fs_1.default.existsSync(resolvedPath)) {
224
+ res.status(400).json({ error: `Path does not exist: ${resolvedPath}` });
225
+ return;
226
+ }
227
+ const canonicalPath = canonicalizePath(resolvedPath);
228
+ // LOW-04: Reject registration of system-critical directories
229
+ if (!isPathSafe(canonicalPath)) {
230
+ res.status(400).json({ error: 'Registering system directories is not allowed' });
231
+ return;
232
+ }
233
+ const derivedName = (name && typeof name === 'string' && name.trim())
234
+ ? name.trim()
235
+ : deriveProjectName(canonicalPath);
236
+ const slug = slugify(derivedName);
237
+ const id = (0, crypto_1.randomUUID)();
238
+ const specrailsInstalled = hasSpecrails(canonicalPath);
239
+ try {
240
+ const ctx = registry.addProject({
241
+ id,
242
+ slug,
243
+ name: derivedName,
244
+ path: canonicalPath,
245
+ provider: providers[0],
246
+ providers: providers,
247
+ });
248
+ broadcast({
249
+ type: 'desktop.project_added',
250
+ project: ctx.project,
251
+ timestamp: new Date().toISOString(),
252
+ });
253
+ res.status(201).json({ project: ctx.project, has_specrails: specrailsInstalled });
254
+ }
255
+ catch (err) {
256
+ const message = err.message ?? '';
257
+ // SQLite UNIQUE constraint violation means path or slug already registered
258
+ if (message.includes('UNIQUE')) {
259
+ res.status(409).json({ error: 'A project with this path is already registered' });
260
+ }
261
+ else {
262
+ console.error('[desktop] add project error:', err);
263
+ res.status(500).json({ error: 'Failed to register project' });
264
+ }
265
+ }
266
+ });
267
+ // DELETE /api/projects/:id — unregister a project
268
+ router.delete('/projects/:id', (req, res) => {
269
+ const { id } = req.params;
270
+ const ctx = registry.getContext(id);
271
+ if (!ctx) {
272
+ res.status(404).json({ error: 'Project not found' });
273
+ return;
274
+ }
275
+ registry.removeProject(id);
276
+ broadcast({
277
+ type: 'desktop.project_removed',
278
+ projectId: id,
279
+ timestamp: new Date().toISOString(),
280
+ });
281
+ res.json({ ok: true });
282
+ });
283
+ // GET /api/state — app-level state summary
284
+ router.get('/state', (_req, res) => {
285
+ const projects = (0, desktop_db_1.listProjects)(registry.desktopDb);
286
+ const todayStats = (0, desktop_analytics_1.getDesktopTodayStats)(registry);
287
+ res.json({
288
+ projects,
289
+ projectCount: projects.length,
290
+ ...todayStats,
291
+ });
292
+ });
293
+ // GET /api/analytics?period= — cross-project aggregated analytics
294
+ router.get('/analytics', (req, res) => {
295
+ const period = req.query.period ?? '7d';
296
+ const from = req.query.from;
297
+ const to = req.query.to;
298
+ const opts = { period, from, to };
299
+ const result = (0, desktop_analytics_1.getDesktopAnalytics)(registry, opts);
300
+ res.json(result);
301
+ });
302
+ // GET /api/recent-jobs?limit= — last N jobs across all projects
303
+ router.get('/recent-jobs', (req, res) => {
304
+ const limit = Math.min(Math.max(parseInt(req.query.limit ?? '10', 10) || 10, 1), 50);
305
+ const jobs = (0, desktop_analytics_1.getDesktopRecentJobs)(registry, limit);
306
+ res.json({ jobs });
307
+ });
308
+ // GET /api/resolve?path=<cwd> — resolve a project from a filesystem path
309
+ router.get('/resolve', (req, res) => {
310
+ const queryPath = req.query.path;
311
+ if (!queryPath) {
312
+ res.status(400).json({ error: 'path query parameter is required' });
313
+ return;
314
+ }
315
+ const resolvedPath = canonicalizePath(path_1.default.resolve(queryPath));
316
+ const ctx = registry.getContextByPath(resolvedPath);
317
+ if (!ctx) {
318
+ res.status(404).json({ error: 'No project registered for this path' });
319
+ return;
320
+ }
321
+ registry.touchProject(ctx.project.id);
322
+ res.json({ project: ctx.project });
323
+ });
324
+ // GET /api/settings — get app-level settings
325
+ router.get('/settings', (_req, res) => {
326
+ const port = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'port') ?? '4200';
327
+ const specrailsTechUrl = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'specrails_tech_url') ??
328
+ process.env.SPECRAILS_TECH_URL ??
329
+ 'http://localhost:3000';
330
+ const costAlertThresholdRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd');
331
+ const costAlertThresholdUsd = costAlertThresholdRaw != null ? parseFloat(costAlertThresholdRaw) : null;
332
+ res.json({ port: parseInt(port, 10), specrailsTechUrl, costAlertThresholdUsd });
333
+ });
334
+ // PUT /api/settings — update app-level settings
335
+ router.put('/settings', (req, res) => {
336
+ const { port, specrailsTechUrl, costAlertThresholdUsd } = req.body ?? {};
337
+ if (port !== undefined) {
338
+ const n = Number(port);
339
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
340
+ res.status(400).json({ error: 'port must be an integer between 1 and 65535' });
341
+ return;
342
+ }
343
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'port', String(n));
344
+ }
345
+ if (specrailsTechUrl !== undefined && typeof specrailsTechUrl === 'string') {
346
+ const normalized = validateHttpUrl(specrailsTechUrl.trim(), {
347
+ allowLoopback: true,
348
+ requireHttps: false,
349
+ });
350
+ if (!normalized) {
351
+ res.status(400).json({ error: 'specrailsTechUrl must be a valid http(s) URL' });
352
+ return;
353
+ }
354
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'specrails_tech_url', normalized);
355
+ }
356
+ if (costAlertThresholdUsd !== undefined) {
357
+ if (costAlertThresholdUsd === null) {
358
+ registry.desktopDb.prepare('DELETE FROM desktop_settings WHERE key = ?').run('cost_alert_threshold_usd');
359
+ }
360
+ else if (typeof costAlertThresholdUsd === 'number' && costAlertThresholdUsd > 0) {
361
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd', String(costAlertThresholdUsd));
362
+ }
363
+ }
364
+ res.json({ ok: true });
365
+ });
366
+ // ─── Budget routes ────────────────────────────────────────────────────────────
367
+ // GET /api/budget — get app-level budget status
368
+ router.get('/budget', (_req, res) => {
369
+ const desktopDailyBudgetRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'desktop_daily_budget_usd');
370
+ const desktopDailyBudgetUsd = desktopDailyBudgetRaw != null ? parseFloat(desktopDailyBudgetRaw) : null;
371
+ const costAlertRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd');
372
+ const costAlertThresholdUsd = costAlertRaw != null ? parseFloat(costAlertRaw) : null;
373
+ const { costToday } = (0, desktop_analytics_1.getDesktopTodayStats)(registry);
374
+ const budgetUtilizationPct = desktopDailyBudgetUsd != null && desktopDailyBudgetUsd > 0
375
+ ? (costToday / desktopDailyBudgetUsd) * 100
376
+ : null;
377
+ res.json({ desktopDailyBudgetUsd, costAlertThresholdUsd, costToday, budgetUtilizationPct });
378
+ });
379
+ // PATCH /api/budget — update app-level budget settings
380
+ router.patch('/budget', (req, res) => {
381
+ const { desktopDailyBudgetUsd, costAlertThresholdUsd } = req.body ?? {};
382
+ if (desktopDailyBudgetUsd !== undefined) {
383
+ if (desktopDailyBudgetUsd === null) {
384
+ registry.desktopDb.prepare('DELETE FROM desktop_settings WHERE key = ?').run('desktop_daily_budget_usd');
385
+ }
386
+ else if (typeof desktopDailyBudgetUsd === 'number' && desktopDailyBudgetUsd > 0) {
387
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'desktop_daily_budget_usd', String(desktopDailyBudgetUsd));
388
+ }
389
+ }
390
+ if (costAlertThresholdUsd !== undefined) {
391
+ if (costAlertThresholdUsd === null) {
392
+ registry.desktopDb.prepare('DELETE FROM desktop_settings WHERE key = ?').run('cost_alert_threshold_usd');
393
+ }
394
+ else if (typeof costAlertThresholdUsd === 'number' && costAlertThresholdUsd > 0) {
395
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'cost_alert_threshold_usd', String(costAlertThresholdUsd));
396
+ }
397
+ }
398
+ res.json({ ok: true });
399
+ });
400
+ // ─── Agent routes ────────────────────────────────────────────────────────────
401
+ // GET /api/agents — list all registered agents
402
+ router.get('/agents', (_req, res) => {
403
+ res.json({ agents: (0, desktop_db_1.listAgents)(registry.desktopDb) });
404
+ });
405
+ // GET /api/agents/:id — get agent by ID
406
+ router.get('/agents/:id', (req, res) => {
407
+ const agent = (0, desktop_db_1.getAgent)(registry.desktopDb, req.params.id);
408
+ if (!agent) {
409
+ res.status(404).json({ error: 'Agent not found' });
410
+ return;
411
+ }
412
+ res.json({ agent });
413
+ });
414
+ // POST /api/agents — register a new agent
415
+ router.post('/agents', (req, res) => {
416
+ const { slug, name, role, config } = req.body ?? {};
417
+ if (!slug || typeof slug !== 'string') {
418
+ res.status(400).json({ error: 'slug is required' });
419
+ return;
420
+ }
421
+ if (!name || typeof name !== 'string') {
422
+ res.status(400).json({ error: 'name is required' });
423
+ return;
424
+ }
425
+ const id = (0, crypto_1.randomUUID)();
426
+ try {
427
+ const agent = (0, desktop_db_1.addAgent)(registry.desktopDb, { id, slug, name, role, config });
428
+ res.status(201).json({ agent });
429
+ }
430
+ catch (err) {
431
+ const message = err.message ?? '';
432
+ if (message.includes('UNIQUE')) {
433
+ res.status(409).json({ error: 'An agent with this slug already exists' });
434
+ }
435
+ else {
436
+ console.error('[desktop] add agent error:', err);
437
+ res.status(500).json({ error: 'Failed to register agent' });
438
+ }
439
+ }
440
+ });
441
+ // PATCH /api/agents/:id — update agent fields
442
+ router.patch('/agents/:id', (req, res) => {
443
+ const agent = (0, desktop_db_1.getAgent)(registry.desktopDb, req.params.id);
444
+ if (!agent) {
445
+ res.status(404).json({ error: 'Agent not found' });
446
+ return;
447
+ }
448
+ const { name, role, status, current_job_id, last_heartbeat_at, config } = req.body ?? {};
449
+ const updates = {};
450
+ if (name !== undefined)
451
+ updates.name = name;
452
+ if (role !== undefined)
453
+ updates.role = role;
454
+ if (status !== undefined)
455
+ updates.status = status;
456
+ if (current_job_id !== undefined)
457
+ updates.current_job_id = current_job_id;
458
+ if (last_heartbeat_at !== undefined)
459
+ updates.last_heartbeat_at = last_heartbeat_at;
460
+ if (config !== undefined)
461
+ updates.config = config;
462
+ const updated = (0, desktop_db_1.updateAgent)(registry.desktopDb, req.params.id, updates);
463
+ res.json({ agent: updated });
464
+ });
465
+ // GET /api/core-compat — compatibility status between the app and specrails-core
466
+ router.get('/core-compat', async (_req, res) => {
467
+ const result = await (0, core_compat_1.checkCoreCompat)();
468
+ res.json(result);
469
+ });
470
+ // GET /api/cli-status — detected AI CLI provider and version
471
+ router.get('/cli-status', (_req, res) => {
472
+ res.json((0, core_compat_1.getCLIStatus)());
473
+ });
474
+ // ─── specrails-tech proxy routes ────────────────────────────────────────────
475
+ function getSpecrailsTechClient() {
476
+ const url = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'specrails_tech_url') ??
477
+ process.env.SPECRAILS_TECH_URL ??
478
+ 'http://localhost:3000';
479
+ return (0, specrails_tech_client_1.createSpecrailsTechClient)(url);
480
+ }
481
+ // GET /api/specrails-tech/status — health + connected flag
482
+ router.get('/specrails-tech/status', async (_req, res) => {
483
+ const client = getSpecrailsTechClient();
484
+ const result = await client.health();
485
+ if (!result.connected) {
486
+ res.json({ connected: false, error: result.error });
487
+ return;
488
+ }
489
+ res.json({ connected: true, data: result.data });
490
+ });
491
+ // GET /api/specrails-tech/agents — list agents
492
+ router.get('/specrails-tech/agents', async (_req, res) => {
493
+ const client = getSpecrailsTechClient();
494
+ const result = await client.listAgents();
495
+ if (!result.connected) {
496
+ res.json({ connected: false, error: result.error, data: [] });
497
+ return;
498
+ }
499
+ res.json({ connected: true, data: result.data });
500
+ });
501
+ // GET /api/specrails-tech/agents/:slug — agent detail
502
+ router.get('/specrails-tech/agents/:slug', async (req, res) => {
503
+ const client = getSpecrailsTechClient();
504
+ const result = await client.getAgent(req.params.slug);
505
+ if (!result.connected) {
506
+ res.status(503).json({ connected: false, error: result.error });
507
+ return;
508
+ }
509
+ res.json({ connected: true, data: result.data });
510
+ });
511
+ // GET /api/specrails-tech/docs — list docs
512
+ router.get('/specrails-tech/docs', async (_req, res) => {
513
+ const client = getSpecrailsTechClient();
514
+ const result = await client.listDocs();
515
+ if (!result.connected) {
516
+ res.json({ connected: false, error: result.error, data: [] });
517
+ return;
518
+ }
519
+ res.json({ connected: true, data: result.data });
520
+ });
521
+ // GET /api/specrails-tech/docs/:page — doc page detail
522
+ router.get('/specrails-tech/docs/:page', async (req, res) => {
523
+ const client = getSpecrailsTechClient();
524
+ const result = await client.getDoc(req.params.page);
525
+ if (!result.connected) {
526
+ res.status(503).json({ connected: false, error: result.error });
527
+ return;
528
+ }
529
+ res.json({ connected: true, data: result.data });
530
+ });
531
+ // ─── Webhook routes ──────────────────────────────────────────────────────────
532
+ const webhookManager = new webhook_manager_1.WebhookManager(registry.desktopDb);
533
+ // GET /api/webhooks — list all webhooks
534
+ router.get('/webhooks', (_req, res) => {
535
+ res.json({ webhooks: (0, desktop_db_1.listWebhooks)(registry.desktopDb).map(publicWebhook) });
536
+ });
537
+ // POST /api/webhooks — create a webhook
538
+ router.post('/webhooks', (req, res) => {
539
+ const { url, secret, events, projectId } = req.body ?? {};
540
+ if (!url || typeof url !== 'string') {
541
+ res.status(400).json({ error: 'url is required' });
542
+ return;
543
+ }
544
+ const validEvents = ['job.completed', 'job.failed', 'job.canceled', 'daily_budget_exceeded', 'desktop_daily_budget_exceeded'];
545
+ const parsedEvents = Array.isArray(events)
546
+ ? events.filter((e) => validEvents.includes(e))
547
+ : ['job.completed', 'job.failed', 'job.canceled'];
548
+ if (parsedEvents.length === 0) {
549
+ res.status(400).json({ error: 'at least one valid event is required' });
550
+ return;
551
+ }
552
+ if (projectId != null) {
553
+ const ctx = registry.getContext(projectId);
554
+ if (!ctx) {
555
+ res.status(400).json({ error: 'project not found' });
556
+ return;
557
+ }
558
+ }
559
+ const normalizedUrl = validateHttpUrl(url.trim(), {
560
+ allowLoopback: process.env.SPECRAILS_ALLOW_LOCAL_WEBHOOKS === '1',
561
+ requireHttps: true,
562
+ });
563
+ if (!normalizedUrl) {
564
+ res.status(400).json({ error: 'webhook url must be https and must not target localhost/private IPs' });
565
+ return;
566
+ }
567
+ const webhook = (0, desktop_db_1.addWebhook)(registry.desktopDb, {
568
+ id: (0, crypto_1.randomUUID)(),
569
+ projectId: projectId ?? null,
570
+ url: normalizedUrl,
571
+ secret: typeof secret === 'string' ? secret.trim() : '',
572
+ events: parsedEvents,
573
+ });
574
+ res.status(201).json({ webhook: publicWebhook(webhook) });
575
+ });
576
+ // PATCH /api/webhooks/:id — update a webhook
577
+ router.patch('/webhooks/:id', (req, res) => {
578
+ const existing = (0, desktop_db_1.getWebhook)(registry.desktopDb, req.params.id);
579
+ if (!existing) {
580
+ res.status(404).json({ error: 'Webhook not found' });
581
+ return;
582
+ }
583
+ const { url, secret, events, enabled } = req.body ?? {};
584
+ const validEvents = ['job.completed', 'job.failed', 'job.canceled', 'daily_budget_exceeded', 'desktop_daily_budget_exceeded'];
585
+ const parsedEvents = Array.isArray(events)
586
+ ? events.filter((e) => validEvents.includes(e))
587
+ : undefined;
588
+ let normalizedUrl;
589
+ if (typeof url === 'string') {
590
+ const candidate = validateHttpUrl(url.trim(), {
591
+ allowLoopback: process.env.SPECRAILS_ALLOW_LOCAL_WEBHOOKS === '1',
592
+ requireHttps: true,
593
+ });
594
+ if (!candidate) {
595
+ res.status(400).json({ error: 'webhook url must be https and must not target localhost/private IPs' });
596
+ return;
597
+ }
598
+ normalizedUrl = candidate;
599
+ }
600
+ const updated = (0, desktop_db_1.updateWebhook)(registry.desktopDb, req.params.id, {
601
+ url: normalizedUrl,
602
+ secret: typeof secret === 'string' ? secret.trim() : undefined,
603
+ events: parsedEvents,
604
+ enabled: typeof enabled === 'boolean' ? enabled : undefined,
605
+ });
606
+ res.json({ webhook: publicWebhook(updated) });
607
+ });
608
+ // DELETE /api/webhooks/:id — delete a webhook
609
+ router.delete('/webhooks/:id', (req, res) => {
610
+ const existing = (0, desktop_db_1.getWebhook)(registry.desktopDb, req.params.id);
611
+ if (!existing) {
612
+ res.status(404).json({ error: 'Webhook not found' });
613
+ return;
614
+ }
615
+ (0, desktop_db_1.removeWebhook)(registry.desktopDb, req.params.id);
616
+ res.json({ ok: true });
617
+ });
618
+ // POST /api/webhooks/:id/test — send a test ping
619
+ router.post('/webhooks/:id/test', (req, res) => {
620
+ const webhook = (0, desktop_db_1.getWebhook)(registry.desktopDb, req.params.id);
621
+ if (!webhook) {
622
+ res.status(404).json({ error: 'Webhook not found' });
623
+ return;
624
+ }
625
+ webhookManager.deliverTest(webhook);
626
+ res.json({ ok: true, message: 'Test ping queued' });
627
+ });
628
+ // GET /api/terminal-settings — Desktop-wide terminal defaults
629
+ router.get('/terminal-settings', (_req, res) => {
630
+ res.json((0, terminal_settings_1.getDesktopTerminalSettings)(registry.desktopDb));
631
+ });
632
+ // PATCH /api/terminal-settings — partial update of Desktop-wide defaults
633
+ router.patch('/terminal-settings', (req, res) => {
634
+ if (!req.body || typeof req.body !== 'object' || Array.isArray(req.body)) {
635
+ res.status(400).json({ error: 'invalid body' });
636
+ return;
637
+ }
638
+ try {
639
+ const updated = (0, terminal_settings_1.patchDesktopTerminalSettings)(registry.desktopDb, req.body);
640
+ res.json(updated);
641
+ }
642
+ catch (err) {
643
+ if (err instanceof terminal_settings_1.TerminalSettingsValidationError) {
644
+ res.status(400).json({ error: 'validation_failed', field: err.field, message: err.message });
645
+ return;
646
+ }
647
+ throw err;
648
+ }
649
+ });
650
+ // ─── Theme (app-wide UI theme) ────────────────────────────────────────────
651
+ // Allow-list synchronized with `client/src/lib/themes.ts THEME_IDS`.
652
+ // Persisted under desktop_settings key `ui_theme`. Default seeded by migration 8.
653
+ router.get('/theme', (_req, res) => {
654
+ const stored = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'ui_theme');
655
+ const theme = stored && THEME_ID_ALLOWLIST.has(stored) ? stored : 'specrails';
656
+ res.json({ theme });
657
+ });
658
+ router.patch('/theme', (req, res) => {
659
+ const next = req.body?.theme;
660
+ if (typeof next !== 'string' || !THEME_ID_ALLOWLIST.has(next)) {
661
+ res.status(400).json({
662
+ error: 'invalid_theme',
663
+ message: `theme must be one of: ${[...THEME_ID_ALLOWLIST].join(', ')}`,
664
+ });
665
+ return;
666
+ }
667
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'ui_theme', next);
668
+ res.json({ theme: next });
669
+ });
670
+ // ─── Language (app-wide UI language) ──────────────────────────────────────
671
+ // Allow-list synchronized with `client/src/lib/i18n.ts LANGUAGE_IDS`.
672
+ // Persisted under desktop_settings key `ui_language`. No default is seeded:
673
+ // `language: null` means "user never chose" and the client keeps following
674
+ // the OS/browser language until an explicit choice is PATCHed.
675
+ router.get('/language', (_req, res) => {
676
+ const stored = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'ui_language');
677
+ const language = stored && LANGUAGE_ID_ALLOWLIST.has(stored) ? stored : null;
678
+ res.json({ language });
679
+ });
680
+ router.patch('/language', (req, res) => {
681
+ const next = req.body?.language;
682
+ if (typeof next !== 'string' || !LANGUAGE_ID_ALLOWLIST.has(next)) {
683
+ res.status(400).json({
684
+ error: 'invalid_language',
685
+ message: `language must be one of: ${[...LANGUAGE_ID_ALLOWLIST].join(', ')}`,
686
+ });
687
+ return;
688
+ }
689
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'ui_language', next);
690
+ res.json({ language: next });
691
+ });
692
+ // ─── Code Explorer settings (summary language + monthly budget) ───────────
693
+ router.get('/code-explorer-settings', (_req, res) => {
694
+ const langRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'summary_language');
695
+ const language = langRaw === 'es' ? 'es' : 'en';
696
+ const budgetRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'summary_monthly_budget_usd');
697
+ const parsed = budgetRaw !== undefined ? Number(budgetRaw) : NaN;
698
+ const monthlyBudgetUsd = Number.isFinite(parsed) && parsed >= 0 ? parsed : 5.0;
699
+ res.json({ language, monthlyBudgetUsd });
700
+ });
701
+ router.patch('/code-explorer-settings', (req, res) => {
702
+ const body = (req.body ?? {});
703
+ if (body.language !== undefined) {
704
+ if (body.language !== 'en' && body.language !== 'es') {
705
+ res.status(400).json({
706
+ error: 'invalid_language',
707
+ message: "language must be one of: 'en', 'es'",
708
+ });
709
+ return;
710
+ }
711
+ }
712
+ if (body.monthlyBudgetUsd !== undefined) {
713
+ if (typeof body.monthlyBudgetUsd !== 'number' || !Number.isFinite(body.monthlyBudgetUsd) || body.monthlyBudgetUsd < 0) {
714
+ res.status(400).json({
715
+ error: 'invalid_monthly_budget_usd',
716
+ message: 'monthlyBudgetUsd must be a non-negative number',
717
+ });
718
+ return;
719
+ }
720
+ }
721
+ if (body.language !== undefined) {
722
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'summary_language', body.language);
723
+ }
724
+ if (body.monthlyBudgetUsd !== undefined) {
725
+ (0, desktop_db_1.setDesktopSetting)(registry.desktopDb, 'summary_monthly_budget_usd', String(body.monthlyBudgetUsd));
726
+ }
727
+ const langRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'summary_language');
728
+ const language = langRaw === 'es' ? 'es' : 'en';
729
+ const budgetRaw = (0, desktop_db_1.getDesktopSetting)(registry.desktopDb, 'summary_monthly_budget_usd');
730
+ const parsed = budgetRaw !== undefined ? Number(budgetRaw) : NaN;
731
+ const monthlyBudgetUsd = Number.isFinite(parsed) && parsed >= 0 ? parsed : 5.0;
732
+ res.json({ language, monthlyBudgetUsd });
733
+ });
734
+ return router;
735
+ }