@sunshinelife83/hearth 1.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 (436) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -0
  3. package/bin/hearth-agentd.js +4 -0
  4. package/bin/hearth.js +4 -0
  5. package/bin/run-entrypoint.js +20 -0
  6. package/dist/agent-tools.js +738 -0
  7. package/dist/app-version.js +16 -0
  8. package/dist/apply-patch.js +402 -0
  9. package/dist/artifact-error.js +8 -0
  10. package/dist/artifact-tools.js +377 -0
  11. package/dist/cli-workspace.js +36 -0
  12. package/dist/cli.js +1086 -0
  13. package/dist/config-migration.js +82 -0
  14. package/dist/config-schema.js +136 -0
  15. package/dist/config.js +115 -0
  16. package/dist/context/context-tools.js +150 -0
  17. package/dist/context/repo-map.js +102 -0
  18. package/dist/dashboard/app.js +226 -0
  19. package/dist/dashboard/index.html +45 -0
  20. package/dist/dashboard/landing.html +57 -0
  21. package/dist/dashboard/styles.css +23 -0
  22. package/dist/dashboard.js +271 -0
  23. package/dist/db/client.js +29 -0
  24. package/dist/db/migrations.js +262 -0
  25. package/dist/db/schema.js +85 -0
  26. package/dist/device-tokens.js +61 -0
  27. package/dist/git-worktrees.js +134 -0
  28. package/dist/git.js +41 -0
  29. package/dist/incoming-artifacts.js +378 -0
  30. package/dist/local-agent-acp.js +768 -0
  31. package/dist/local-agent-adapters.js +20 -0
  32. package/dist/local-agent-availability.js +92 -0
  33. package/dist/local-agent-catalog.js +51 -0
  34. package/dist/local-agent-claude.js +334 -0
  35. package/dist/local-agent-client.js +519 -0
  36. package/dist/local-agent-codex.js +532 -0
  37. package/dist/local-agent-config.js +34 -0
  38. package/dist/local-agent-daemon-lifecycle.js +195 -0
  39. package/dist/local-agent-daemon-main.js +86 -0
  40. package/dist/local-agent-daemon-protocol.js +302 -0
  41. package/dist/local-agent-daemon.js +407 -0
  42. package/dist/local-agent-errors.js +378 -0
  43. package/dist/local-agent-grok.js +202 -0
  44. package/dist/local-agent-manager.js +715 -0
  45. package/dist/local-agent-opencode.js +463 -0
  46. package/dist/local-agent-path.js +24 -0
  47. package/dist/local-agent-pi-sandbox.js +472 -0
  48. package/dist/local-agent-pi.js +298 -0
  49. package/dist/local-agent-presentation.js +104 -0
  50. package/dist/local-agent-profiles.js +127 -0
  51. package/dist/local-agent-runtime-pool.js +481 -0
  52. package/dist/local-agent-runtime.js +1 -0
  53. package/dist/local-agent-store.js +193 -0
  54. package/dist/local-agent-targets.js +97 -0
  55. package/dist/logger.js +97 -0
  56. package/dist/machine-id.js +35 -0
  57. package/dist/mcp-modern-server.js +65 -0
  58. package/dist/oauth-provider.js +331 -0
  59. package/dist/oauth-store.js +144 -0
  60. package/dist/onboarding.js +35 -0
  61. package/dist/orchestration/brief.js +70 -0
  62. package/dist/orchestration/lanes.js +145 -0
  63. package/dist/orchestration/result.js +46 -0
  64. package/dist/orchestration/scope.js +61 -0
  65. package/dist/pi-tools.js +58 -0
  66. package/dist/policy/command-policy.js +104 -0
  67. package/dist/policy/env-allowlist.js +96 -0
  68. package/dist/policy/sandbox.js +68 -0
  69. package/dist/policy/workspace-profiles.js +47 -0
  70. package/dist/process-journal.js +116 -0
  71. package/dist/process-platform.js +49 -0
  72. package/dist/process-sessions.js +388 -0
  73. package/dist/rate-limit.js +77 -0
  74. package/dist/request-meta.js +9 -0
  75. package/dist/review-checkpoints.js +349 -0
  76. package/dist/roots.js +37 -0
  77. package/dist/server-shutdown.js +12 -0
  78. package/dist/server.js +980 -0
  79. package/dist/skills.js +84 -0
  80. package/dist/snapshot-manager.js +123 -0
  81. package/dist/stdio-server.js +80 -0
  82. package/dist/task-store.js +203 -0
  83. package/dist/task-tools.js +455 -0
  84. package/dist/tool-surfaces/claude.js +220 -0
  85. package/dist/tool-surfaces/codex.js +255 -0
  86. package/dist/tool-surfaces/index.js +15 -0
  87. package/dist/tool-surfaces/shared.js +228 -0
  88. package/dist/tool-surfaces/types.js +27 -0
  89. package/dist/ui/.vite/manifest.json +2544 -0
  90. package/dist/ui/assets/abap-CLvhMVsD.js +1 -0
  91. package/dist/ui/assets/actionscript-3--17pq3dv.js +1 -0
  92. package/dist/ui/assets/ada-C5qYipkI.js +1 -0
  93. package/dist/ui/assets/andromeeda-vGVdxbeo.js +1 -0
  94. package/dist/ui/assets/angular-html-DrbO3_Xe.js +1 -0
  95. package/dist/ui/assets/angular-ts-CmTVzHJx.js +1 -0
  96. package/dist/ui/assets/apache-U0d_L8uA.js +1 -0
  97. package/dist/ui/assets/apex-CGTLDQj6.js +1 -0
  98. package/dist/ui/assets/apl-BOpCpqXf.js +1 -0
  99. package/dist/ui/assets/applescript-CCn79oCD.js +1 -0
  100. package/dist/ui/assets/ara-4CJ0cIlV.js +1 -0
  101. package/dist/ui/assets/asciidoc-DE70LPWp.js +1 -0
  102. package/dist/ui/assets/asm-Cmm7eHzH.js +1 -0
  103. package/dist/ui/assets/astro-CxPeBwei.js +1 -0
  104. package/dist/ui/assets/aurora-x-CDeNXAV0.js +1 -0
  105. package/dist/ui/assets/awk-BWXHIvNe.js +1 -0
  106. package/dist/ui/assets/ayu-dark-DluEY0Gj.js +1 -0
  107. package/dist/ui/assets/ayu-light-C3h-C4tm.js +1 -0
  108. package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  109. package/dist/ui/assets/ballerina-B7ZEbQpA.js +1 -0
  110. package/dist/ui/assets/bat-Bo4NYOV-.js +1 -0
  111. package/dist/ui/assets/beancount-D-usSTwE.js +1 -0
  112. package/dist/ui/assets/berry-DKpUyyne.js +1 -0
  113. package/dist/ui/assets/bibtex-Ci_nEsc7.js +1 -0
  114. package/dist/ui/assets/bicep-CUHmPFLl.js +1 -0
  115. package/dist/ui/assets/bird2-C2hNVINV.js +1 -0
  116. package/dist/ui/assets/blade-DtsAAG4P.js +1 -0
  117. package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
  118. package/dist/ui/assets/c-D9hLjNUM.js +1 -0
  119. package/dist/ui/assets/c3-CnJL0r0V.js +1 -0
  120. package/dist/ui/assets/cadence-CQ2zXKGN.js +1 -0
  121. package/dist/ui/assets/cairo-DLTphjLi.js +1 -0
  122. package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
  123. package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
  124. package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
  125. package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
  126. package/dist/ui/assets/clarity-SemFz856.js +1 -0
  127. package/dist/ui/assets/clojure-DqKBuwfJ.js +1 -0
  128. package/dist/ui/assets/cmake-Bj61d0ZC.js +1 -0
  129. package/dist/ui/assets/cobol-C1SWlvZm.js +1 -0
  130. package/dist/ui/assets/codeowners-C8r90Shi.js +1 -0
  131. package/dist/ui/assets/codeql-oeQT6MSM.js +1 -0
  132. package/dist/ui/assets/coffee-B71R3v2U.js +1 -0
  133. package/dist/ui/assets/common-lisp-Cv5bFMCO.js +1 -0
  134. package/dist/ui/assets/coq-BrsZFFmf.js +1 -0
  135. package/dist/ui/assets/cpp--5a4c3v4.js +1 -0
  136. package/dist/ui/assets/crystal-AtqWYtE0.js +1 -0
  137. package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
  138. package/dist/ui/assets/css-CbqKSHI-.js +1 -0
  139. package/dist/ui/assets/csv-Dx-8-gkx.js +1 -0
  140. package/dist/ui/assets/cue-CE9AQfxI.js +1 -0
  141. package/dist/ui/assets/cypher-ClKdZ_lG.js +1 -0
  142. package/dist/ui/assets/d-qD-0Kul2.js +1 -0
  143. package/dist/ui/assets/dark-plus-Cs2F2srj.js +1 -0
  144. package/dist/ui/assets/dart-DkHntEIa.js +1 -0
  145. package/dist/ui/assets/dax-BkyTk9wS.js +1 -0
  146. package/dist/ui/assets/desktop-Dlh5hvp9.js +1 -0
  147. package/dist/ui/assets/diff-woXpYk--.js +1 -0
  148. package/dist/ui/assets/docker-IyjqRm3v.js +1 -0
  149. package/dist/ui/assets/dotenv-_5a1GRtc.js +1 -0
  150. package/dist/ui/assets/dracula-BHWKrbxM.js +1 -0
  151. package/dist/ui/assets/dracula-soft-5eyTD99u.js +1 -0
  152. package/dist/ui/assets/dream-maker-DW3nJb8Q.js +1 -0
  153. package/dist/ui/assets/edge-BPwY_RrJ.js +1 -0
  154. package/dist/ui/assets/elixir-DVH2yLJf.js +1 -0
  155. package/dist/ui/assets/elm-BrleA6gS.js +1 -0
  156. package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
  157. package/dist/ui/assets/erb-2SHiYe30.js +1 -0
  158. package/dist/ui/assets/erlang-Cphh6RMH.js +1 -0
  159. package/dist/ui/assets/everforest-dark-sB-x3p7T.js +1 -0
  160. package/dist/ui/assets/everforest-light-Df2xbC6M.js +1 -0
  161. package/dist/ui/assets/fennel-DQxkIbk2.js +1 -0
  162. package/dist/ui/assets/fish-BJitypiv.js +1 -0
  163. package/dist/ui/assets/fluent-C03EYrpw.js +1 -0
  164. package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  165. package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +1 -0
  166. package/dist/ui/assets/fsharp-D13ZGOAj.js +1 -0
  167. package/dist/ui/assets/gdresource-C0sCabJj.js +1 -0
  168. package/dist/ui/assets/gdscript-Cp2uCuqX.js +1 -0
  169. package/dist/ui/assets/gdshader-CBce3t8t.js +1 -0
  170. package/dist/ui/assets/genie-CV2tkWYe.js +1 -0
  171. package/dist/ui/assets/gherkin-DExj1W_8.js +1 -0
  172. package/dist/ui/assets/git-commit-BSykSTBG.js +1 -0
  173. package/dist/ui/assets/git-rebase-Bc7atMaV.js +1 -0
  174. package/dist/ui/assets/github-dark-C-LZuMrd.js +1 -0
  175. package/dist/ui/assets/github-dark-default-DXG-b-1a.js +1 -0
  176. package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  177. package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  178. package/dist/ui/assets/github-light-EUqPIrTm.js +1 -0
  179. package/dist/ui/assets/github-light-default-BXViO-2h.js +1 -0
  180. package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  181. package/dist/ui/assets/gleam-CSRkHgEL.js +1 -0
  182. package/dist/ui/assets/glimmer-js-DXeTDPtW.js +1 -0
  183. package/dist/ui/assets/glimmer-ts-CEOoXHU4.js +1 -0
  184. package/dist/ui/assets/glsl-5-43-Gsg.js +1 -0
  185. package/dist/ui/assets/gn-ilITqXS6.js +1 -0
  186. package/dist/ui/assets/gnuplot-7GGW24-e.js +1 -0
  187. package/dist/ui/assets/go-BJwz_mda.js +1 -0
  188. package/dist/ui/assets/graphql-Bk7Ah1KA.js +1 -0
  189. package/dist/ui/assets/groovy-CacY0gHj.js +1 -0
  190. package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  191. package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  192. package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  193. package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  194. package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  195. package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  196. package/dist/ui/assets/hack-DUHz4rCJ.js +1 -0
  197. package/dist/ui/assets/haml-BPCuuTPR.js +1 -0
  198. package/dist/ui/assets/handlebars-CHxmUsnU.js +1 -0
  199. package/dist/ui/assets/haskell-D8IpX4py.js +1 -0
  200. package/dist/ui/assets/haxe-OTjmBuCE.js +1 -0
  201. package/dist/ui/assets/hcl-Dh228itO.js +1 -0
  202. package/dist/ui/assets/hjson-CxZEssPk.js +1 -0
  203. package/dist/ui/assets/hlsl-Cvrh5tZx.js +1 -0
  204. package/dist/ui/assets/horizon-CE9ld1lL.js +1 -0
  205. package/dist/ui/assets/horizon-bright-Br1oVSNq.js +1 -0
  206. package/dist/ui/assets/houston-CsvMBhTu.js +1 -0
  207. package/dist/ui/assets/html-BBJqizz0.js +1 -0
  208. package/dist/ui/assets/html-derivative-ShTPCcKN.js +1 -0
  209. package/dist/ui/assets/http-VFTyziwA.js +1 -0
  210. package/dist/ui/assets/hurl-B8yxTa4r.js +1 -0
  211. package/dist/ui/assets/hxml-B0Qn7Nwc.js +1 -0
  212. package/dist/ui/assets/hy-CZbG8q4J.js +1 -0
  213. package/dist/ui/assets/imba-DsUTQ-LC.js +1 -0
  214. package/dist/ui/assets/ini-B5eOa1yu.js +1 -0
  215. package/dist/ui/assets/java-BZ5DAgJZ.js +1 -0
  216. package/dist/ui/assets/javascript-C_zuIEaC.js +1 -0
  217. package/dist/ui/assets/jinja-DWd6xfNr.js +1 -0
  218. package/dist/ui/assets/jison-BbY63d1V.js +1 -0
  219. package/dist/ui/assets/json-C4DnajZs.js +1 -0
  220. package/dist/ui/assets/json5-BR5RXkoi.js +1 -0
  221. package/dist/ui/assets/jsonc-CYpm1nAK.js +1 -0
  222. package/dist/ui/assets/jsonl-CmCQp5Yx.js +1 -0
  223. package/dist/ui/assets/jsonnet-CJTPZ8u_.js +1 -0
  224. package/dist/ui/assets/jssm-DXw9l8Rf.js +1 -0
  225. package/dist/ui/assets/jsx-C4AACieZ.js +1 -0
  226. package/dist/ui/assets/julia-sRu7EFMW.js +1 -0
  227. package/dist/ui/assets/just-DpZ9Sx1v.js +1 -0
  228. package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
  229. package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +1 -0
  230. package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +1 -0
  231. package/dist/ui/assets/kdl-CsD5j6eV.js +1 -0
  232. package/dist/ui/assets/kotlin-DhhofPvG.js +1 -0
  233. package/dist/ui/assets/kusto-BUv0MjJC.js +1 -0
  234. package/dist/ui/assets/laserwave-C_8bwKvT.js +1 -0
  235. package/dist/ui/assets/latex-B6a_uum7.js +1 -0
  236. package/dist/ui/assets/lean-CewbzKMR.js +1 -0
  237. package/dist/ui/assets/less-DVTAwKKz.js +1 -0
  238. package/dist/ui/assets/light-plus-DVQuIRkW.js +1 -0
  239. package/dist/ui/assets/liquid-B2sVpjjr.js +1 -0
  240. package/dist/ui/assets/llvm-Cm23YOpf.js +1 -0
  241. package/dist/ui/assets/log-BNLmms1o.js +1 -0
  242. package/dist/ui/assets/logo-Cluzi2Zq.js +1 -0
  243. package/dist/ui/assets/lua-DxJJll61.js +1 -0
  244. package/dist/ui/assets/luau-CNKltnaQ.js +1 -0
  245. package/dist/ui/assets/make-Dixweg8N.js +1 -0
  246. package/dist/ui/assets/markdown-BYOwaDjH.js +1 -0
  247. package/dist/ui/assets/marko-Dx8mw-Wx.js +1 -0
  248. package/dist/ui/assets/material-theme-Bm3Qr25_.js +1 -0
  249. package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +1 -0
  250. package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +1 -0
  251. package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  252. package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  253. package/dist/ui/assets/matlab-D7qyCx1q.js +1 -0
  254. package/dist/ui/assets/mdc-vNKeXEwR.js +1 -0
  255. package/dist/ui/assets/mdx-DQZ5AkYe.js +1 -0
  256. package/dist/ui/assets/mermaid-Bk4SNUv9.js +1 -0
  257. package/dist/ui/assets/min-dark-BSWPekZh.js +1 -0
  258. package/dist/ui/assets/min-light-DDpmG2fV.js +1 -0
  259. package/dist/ui/assets/mipsasm-BMqwQI7S.js +1 -0
  260. package/dist/ui/assets/mojo-BgCJLMeH.js +1 -0
  261. package/dist/ui/assets/monokai-CdkpiU2Y.js +1 -0
  262. package/dist/ui/assets/moonbit-CaWjb8XO.js +1 -0
  263. package/dist/ui/assets/move-B1IS1UjX.js +1 -0
  264. package/dist/ui/assets/narrat-_X_XdTYD.js +1 -0
  265. package/dist/ui/assets/nextflow-Bbiyy34d.js +1 -0
  266. package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +1 -0
  267. package/dist/ui/assets/nginx-DScQAGLT.js +1 -0
  268. package/dist/ui/assets/night-owl-DhmEMT88.js +1 -0
  269. package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +1 -0
  270. package/dist/ui/assets/nim-lAJch9uT.js +1 -0
  271. package/dist/ui/assets/nix-IvuFDN5E.js +1 -0
  272. package/dist/ui/assets/nord-Cb4Vim4T.js +1 -0
  273. package/dist/ui/assets/nushell-DcLAeLz5.js +1 -0
  274. package/dist/ui/assets/objective-c-D1A_Heim.js +1 -0
  275. package/dist/ui/assets/objective-cpp-BsSzOQcm.js +1 -0
  276. package/dist/ui/assets/ocaml-O90oeIOV.js +1 -0
  277. package/dist/ui/assets/odin-B1RWQWA5.js +1 -0
  278. package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +1 -0
  279. package/dist/ui/assets/one-light-D7Lr4KcI.js +1 -0
  280. package/dist/ui/assets/openscad-BUDT5pXO.js +1 -0
  281. package/dist/ui/assets/pascal-4ZHwLPI5.js +1 -0
  282. package/dist/ui/assets/perl-C_GkVo_E.js +1 -0
  283. package/dist/ui/assets/php-DziV3K6J.js +1 -0
  284. package/dist/ui/assets/pierre-dark-CpLgRqie.js +1 -0
  285. package/dist/ui/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  286. package/dist/ui/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  287. package/dist/ui/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  288. package/dist/ui/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  289. package/dist/ui/assets/pierre-light-CoaEpmwp.js +1 -0
  290. package/dist/ui/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  291. package/dist/ui/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  292. package/dist/ui/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  293. package/dist/ui/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  294. package/dist/ui/assets/pkl-ot-7Btpt.js +1 -0
  295. package/dist/ui/assets/plastic-DQwYfKfQ.js +1 -0
  296. package/dist/ui/assets/plsql-DGHpHOYJ.js +1 -0
  297. package/dist/ui/assets/po-BiJDBrnU.js +1 -0
  298. package/dist/ui/assets/poimandres-DRFjx7u4.js +1 -0
  299. package/dist/ui/assets/polar-C7UOKdEL.js +1 -0
  300. package/dist/ui/assets/postcss-BXeXVLqQ.js +1 -0
  301. package/dist/ui/assets/powerquery-DNMTfnFr.js +1 -0
  302. package/dist/ui/assets/powershell-DshXNtvi.js +1 -0
  303. package/dist/ui/assets/prisma-BsRQq5mF.js +1 -0
  304. package/dist/ui/assets/prolog-iXnhIJG7.js +1 -0
  305. package/dist/ui/assets/proto-DB4EqR-F.js +1 -0
  306. package/dist/ui/assets/pug-z75UIWVK.js +1 -0
  307. package/dist/ui/assets/puppet-CDv2pdJW.js +1 -0
  308. package/dist/ui/assets/purescript-9MfHhQsQ.js +1 -0
  309. package/dist/ui/assets/python-gzcpVVnB.js +1 -0
  310. package/dist/ui/assets/qml-Cmm4coz1.js +1 -0
  311. package/dist/ui/assets/qmldir-DCQb3MpD.js +1 -0
  312. package/dist/ui/assets/qss-Fe1Jh2GI.js +1 -0
  313. package/dist/ui/assets/r-Brd0axBE.js +1 -0
  314. package/dist/ui/assets/racket-DcIDlBhZ.js +1 -0
  315. package/dist/ui/assets/raku-B3gFvitq.js +1 -0
  316. package/dist/ui/assets/razor-DSbwmoiu.js +1 -0
  317. package/dist/ui/assets/red-CJ3rzSJv.js +1 -0
  318. package/dist/ui/assets/reg-CRGYupPL.js +1 -0
  319. package/dist/ui/assets/regexp-Bs3Uk-pY.js +1 -0
  320. package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
  321. package/dist/ui/assets/review-payload-CbwCBKB1.js +1580 -0
  322. package/dist/ui/assets/riscv-Ckw8ddFX.js +1 -0
  323. package/dist/ui/assets/ron-VUp2lXgN.js +1 -0
  324. package/dist/ui/assets/rose-pine-BthvhNj6.js +1 -0
  325. package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
  326. package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +1 -0
  327. package/dist/ui/assets/rosmsg-CAekHB0j.js +1 -0
  328. package/dist/ui/assets/rst-ChIyKpUC.js +1 -0
  329. package/dist/ui/assets/ruby-BdAmvOMO.js +1 -0
  330. package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
  331. package/dist/ui/assets/sas-DpXn8hc5.js +1 -0
  332. package/dist/ui/assets/sass-DXrisJhu.js +1 -0
  333. package/dist/ui/assets/scala-DKOlJaKm.js +1 -0
  334. package/dist/ui/assets/scheme-DQCgrYNe.js +1 -0
  335. package/dist/ui/assets/scss-atX1Uuef.js +1 -0
  336. package/dist/ui/assets/sdbl-bTVj8UrX.js +1 -0
  337. package/dist/ui/assets/shaderlab-TOUzSsQk.js +1 -0
  338. package/dist/ui/assets/shellscript-BqgcAeMK.js +1 -0
  339. package/dist/ui/assets/shellsession-DfQSMRN5.js +1 -0
  340. package/dist/ui/assets/slack-dark-DnToyrRv.js +1 -0
  341. package/dist/ui/assets/slack-ochin-B2OO5cIa.js +1 -0
  342. package/dist/ui/assets/smalltalk-B16xEiuN.js +1 -0
  343. package/dist/ui/assets/snazzy-light-4G7pJPwS.js +1 -0
  344. package/dist/ui/assets/solarized-dark-DV17i1UV.js +1 -0
  345. package/dist/ui/assets/solarized-light-DSh2HLQt.js +1 -0
  346. package/dist/ui/assets/solidity-CKzVLygQ.js +1 -0
  347. package/dist/ui/assets/soy-DzP7zZTB.js +1 -0
  348. package/dist/ui/assets/sparql-D_iOobhT.js +1 -0
  349. package/dist/ui/assets/splunk-BC2Px7Mm.js +1 -0
  350. package/dist/ui/assets/sql-gmaLk66z.js +1 -0
  351. package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
  352. package/dist/ui/assets/stata-CJ0YTMcV.js +1 -0
  353. package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
  354. package/dist/ui/assets/surrealql-LH7FciQz.js +1 -0
  355. package/dist/ui/assets/svelte-BME2PU6V.js +1 -0
  356. package/dist/ui/assets/swift-DonLKvLd.js +1 -0
  357. package/dist/ui/assets/synthwave-84-nFMaYfgc.js +1 -0
  358. package/dist/ui/assets/system-verilog-DJ5XKQeo.js +1 -0
  359. package/dist/ui/assets/systemd-BxMlprV5.js +1 -0
  360. package/dist/ui/assets/talonscript-CohzipZa.js +1 -0
  361. package/dist/ui/assets/tasl-DMoTqEGO.js +1 -0
  362. package/dist/ui/assets/tcl-CZd0xW_V.js +1 -0
  363. package/dist/ui/assets/templ-DHBNIfYW.js +1 -0
  364. package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
  365. package/dist/ui/assets/tex-C8-kPEzO.js +1 -0
  366. package/dist/ui/assets/tokyo-night-oM2G3aXe.js +1 -0
  367. package/dist/ui/assets/toml-CcmNWLt0.js +1 -0
  368. package/dist/ui/assets/ts-tags-CiewV0vR.js +1 -0
  369. package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
  370. package/dist/ui/assets/tsx-CUYPqmnq.js +1 -0
  371. package/dist/ui/assets/turtle-ByJddavk.js +1 -0
  372. package/dist/ui/assets/twig-BTkvsl0b.js +1 -0
  373. package/dist/ui/assets/typescript-DDQMaPwj.js +1 -0
  374. package/dist/ui/assets/typespec-B88KGewJ.js +1 -0
  375. package/dist/ui/assets/typst-DI99ib-x.js +1 -0
  376. package/dist/ui/assets/v-DETTlOr0.js +1 -0
  377. package/dist/ui/assets/vala-zf12oZj6.js +1 -0
  378. package/dist/ui/assets/vb-Djn5o6TS.js +1 -0
  379. package/dist/ui/assets/verilog-CiiDBU1e.js +1 -0
  380. package/dist/ui/assets/vesper-D5bVUKB1.js +1 -0
  381. package/dist/ui/assets/vhdl-BroJfC0k.js +1 -0
  382. package/dist/ui/assets/viml-DvXPmvsu.js +1 -0
  383. package/dist/ui/assets/vitesse-black-fwtXNY1n.js +1 -0
  384. package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +1 -0
  385. package/dist/ui/assets/vitesse-light-VbXTXTou.js +1 -0
  386. package/dist/ui/assets/vue-BH5SEWRH.js +1 -0
  387. package/dist/ui/assets/vue-html-BT7yeYez.js +1 -0
  388. package/dist/ui/assets/vue-vine-Cs5IN2rs.js +1 -0
  389. package/dist/ui/assets/vyper-CgoNMtux.js +1 -0
  390. package/dist/ui/assets/wasm-BnjxR4X6.js +1 -0
  391. package/dist/ui/assets/wasm-ByWQv1Qj.js +1 -0
  392. package/dist/ui/assets/wenyan-C8pVoKbM.js +1 -0
  393. package/dist/ui/assets/wgsl-BsKzXJz4.js +1 -0
  394. package/dist/ui/assets/wikitext-ClFFjSW2.js +1 -0
  395. package/dist/ui/assets/wit-DdvCle-K.js +1 -0
  396. package/dist/ui/assets/wolfram-DLL8P-h_.js +1 -0
  397. package/dist/ui/assets/workspace-app-B_BnSEHO.css +1 -0
  398. package/dist/ui/assets/workspace-app-DSFpnrIC.js +115 -0
  399. package/dist/ui/assets/xml-D3_1BEcO.js +1 -0
  400. package/dist/ui/assets/xsl-BBOCDn4Y.js +1 -0
  401. package/dist/ui/assets/yaml-lThBDT45.js +1 -0
  402. package/dist/ui/assets/zenscript-BnlCZFoB.js +1 -0
  403. package/dist/ui/assets/zig-CMLA9XwU.js +1 -0
  404. package/dist/ui/workspace-app.html +15 -0
  405. package/dist/user-config.js +228 -0
  406. package/dist/verification.js +74 -0
  407. package/dist/workspace-store.js +133 -0
  408. package/dist/workspaces.js +433 -0
  409. package/docs/adr/devspace-x-implementation-adrs.md +159 -0
  410. package/docs/agent-profile-schema.md +203 -0
  411. package/docs/artifact-exchange.md +44 -0
  412. package/docs/assets/devspace-logo-light.png +0 -0
  413. package/docs/assets/devspace-screenshot.png +0 -0
  414. package/docs/assets/v11-review-ui-after.png +0 -0
  415. package/docs/assets/v11-review-ui-before.png +0 -0
  416. package/docs/chatgpt-coding-workflow.md +215 -0
  417. package/docs/configuration.md +217 -0
  418. package/docs/gotchas.md +294 -0
  419. package/docs/local-agent-daemon.md +101 -0
  420. package/docs/security.md +197 -0
  421. package/docs/setup.md +174 -0
  422. package/examples/agents/claude-implementer.md +30 -0
  423. package/examples/agents/codex-explorer.md +27 -0
  424. package/examples/agents/codex-qa-tester.md +29 -0
  425. package/examples/agents/codex-worker.md +25 -0
  426. package/examples/agents/copilot-reviewer.md +25 -0
  427. package/examples/agents/cursor-agent-worker.md +26 -0
  428. package/examples/agents/grok-builder.md +10 -0
  429. package/examples/agents/opencode-explorer.md +27 -0
  430. package/examples/agents/pi-reviewer.md +27 -0
  431. package/package.json +102 -0
  432. package/schema/v1/hearth.schema.json +529 -0
  433. package/scripts/copy-dashboard.mjs +9 -0
  434. package/scripts/fix-node-pty-permissions.mjs +22 -0
  435. package/scripts/generate-config-schema.ts +9 -0
  436. package/skills/subagents/SKILL.md +57 -0
@@ -0,0 +1,29 @@
1
+ import { chmodSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import Database from "better-sqlite3";
4
+ import { drizzle } from "drizzle-orm/better-sqlite3";
5
+ import * as schema from "./schema.js";
6
+ import { migrateDatabase } from "./migrations.js";
7
+ export function databasePath(stateDir) {
8
+ return join(stateDir, "hearth.sqlite");
9
+ }
10
+ export function openDatabase(stateDir) {
11
+ mkdirSync(stateDir, { recursive: true, mode: 0o700 });
12
+ chmodSync(stateDir, 0o700);
13
+ const path = databasePath(stateDir);
14
+ const sqlite = new Database(path);
15
+ chmodSync(path, 0o600);
16
+ sqlite.pragma("journal_mode = WAL");
17
+ sqlite.pragma("synchronous = NORMAL");
18
+ sqlite.pragma("busy_timeout = 5000");
19
+ sqlite.pragma("foreign_keys = ON");
20
+ migrateDatabase(sqlite);
21
+ return {
22
+ sqlite,
23
+ db: createDrizzleDatabase(sqlite),
24
+ close: () => sqlite.close(),
25
+ };
26
+ }
27
+ function createDrizzleDatabase(sqlite) {
28
+ return drizzle(sqlite, { schema });
29
+ }
@@ -0,0 +1,262 @@
1
+ const migrations = [
2
+ {
3
+ version: 1,
4
+ name: "workspace-state",
5
+ up: migrateWorkspaceState,
6
+ },
7
+ {
8
+ version: 2,
9
+ name: "oauth-state",
10
+ up: migrateOAuthState,
11
+ },
12
+ {
13
+ version: 3,
14
+ name: "local-agent-sessions",
15
+ up: migrateLocalAgentSessions,
16
+ },
17
+ {
18
+ version: 4,
19
+ name: "workspace-conversation-bindings",
20
+ up: migrateWorkspaceConversationBindings,
21
+ },
22
+ {
23
+ version: 5,
24
+ name: "local-agent-structured-errors",
25
+ up: migrateLocalAgentStructuredErrors,
26
+ },
27
+ {
28
+ version: 6,
29
+ name: "local-agent-effort-rename",
30
+ up: migrateLocalAgentEffortRename,
31
+ },
32
+ {
33
+ version: 7,
34
+ name: "local-agent-lifecycle-output",
35
+ up: migrateLocalAgentLifecycleOutput,
36
+ },
37
+ {
38
+ version: 8,
39
+ name: "device-tokens",
40
+ up: migrateDeviceTokens,
41
+ },
42
+ {
43
+ version: 9,
44
+ name: "task-runtime",
45
+ up: migrateTaskRuntime,
46
+ },
47
+ ];
48
+ export function migrateDatabase(sqlite) {
49
+ const migrate = sqlite.transaction(() => {
50
+ sqlite.exec(`
51
+ create table if not exists hearth_schema_migrations (
52
+ version integer primary key,
53
+ name text not null,
54
+ applied_at text not null
55
+ );
56
+ `);
57
+ const applied = new Set(sqlite.prepare("select version from hearth_schema_migrations").all().map((row) => row.version));
58
+ const recordMigration = sqlite.prepare("insert into hearth_schema_migrations (version, name, applied_at) values (?, ?, ?)");
59
+ for (const migration of migrations) {
60
+ if (applied.has(migration.version))
61
+ continue;
62
+ migration.up(sqlite);
63
+ recordMigration.run(migration.version, migration.name, new Date().toISOString());
64
+ }
65
+ });
66
+ migrate.immediate();
67
+ }
68
+ function migrateWorkspaceState(sqlite) {
69
+ sqlite.exec(`
70
+ create table if not exists workspace_sessions (
71
+ id text primary key,
72
+ root text not null,
73
+ status text not null default 'active',
74
+ mode text not null default 'checkout',
75
+ source_root text,
76
+ base_ref text,
77
+ base_sha text,
78
+ managed text not null default 'false',
79
+ created_at text not null,
80
+ last_used_at text not null
81
+ );
82
+
83
+ create index if not exists workspace_sessions_root_idx
84
+ on workspace_sessions(root, last_used_at desc);
85
+
86
+ create index if not exists workspace_sessions_status_idx
87
+ on workspace_sessions(status, last_used_at desc);
88
+
89
+ create table if not exists loaded_agent_files (
90
+ workspace_session_id text not null,
91
+ path text not null,
92
+ content_hash text not null,
93
+ content text not null,
94
+ loaded_at text not null,
95
+ last_seen_at text not null,
96
+ primary key (workspace_session_id, path),
97
+ foreign key (workspace_session_id)
98
+ references workspace_sessions(id)
99
+ on delete cascade
100
+ );
101
+
102
+ create index if not exists loaded_agent_files_path_idx
103
+ on loaded_agent_files(path);
104
+ `);
105
+ addColumnIfMissing(sqlite, "workspace_sessions", "mode", "text not null default 'checkout'");
106
+ addColumnIfMissing(sqlite, "workspace_sessions", "source_root", "text");
107
+ addColumnIfMissing(sqlite, "workspace_sessions", "base_ref", "text");
108
+ addColumnIfMissing(sqlite, "workspace_sessions", "base_sha", "text");
109
+ addColumnIfMissing(sqlite, "workspace_sessions", "managed", "text not null default 'false'");
110
+ }
111
+ function migrateOAuthState(sqlite) {
112
+ sqlite.exec(`
113
+ create table if not exists oauth_clients (
114
+ client_id text primary key,
115
+ client_json text not null,
116
+ issued_at integer not null
117
+ );
118
+
119
+ create index if not exists oauth_clients_issued_at_idx
120
+ on oauth_clients(issued_at desc);
121
+
122
+ create table if not exists oauth_access_tokens (
123
+ token_hash text primary key,
124
+ client_id text not null,
125
+ scopes_json text not null,
126
+ expires_at integer not null,
127
+ resource text,
128
+ foreign key (client_id) references oauth_clients(client_id) on delete cascade
129
+ );
130
+
131
+ create index if not exists oauth_access_tokens_client_id_idx
132
+ on oauth_access_tokens(client_id);
133
+
134
+ create index if not exists oauth_access_tokens_expires_at_idx
135
+ on oauth_access_tokens(expires_at);
136
+
137
+ create table if not exists oauth_refresh_tokens (
138
+ token_hash text primary key,
139
+ client_id text not null,
140
+ scopes_json text not null,
141
+ expires_at integer not null,
142
+ resource text,
143
+ foreign key (client_id) references oauth_clients(client_id) on delete cascade
144
+ );
145
+
146
+ create index if not exists oauth_refresh_tokens_client_id_idx
147
+ on oauth_refresh_tokens(client_id);
148
+
149
+ create index if not exists oauth_refresh_tokens_expires_at_idx
150
+ on oauth_refresh_tokens(expires_at);
151
+ `);
152
+ }
153
+ function migrateLocalAgentSessions(sqlite) {
154
+ sqlite.exec(`
155
+ create table if not exists local_agent_sessions (
156
+ id text primary key,
157
+ workspace_id text,
158
+ workspace_root text not null,
159
+ profile_name text not null,
160
+ provider text not null,
161
+ model text,
162
+ effort text,
163
+ provider_session_id text,
164
+ status text not null,
165
+ latest_response text,
166
+ error text,
167
+ created_at text not null,
168
+ updated_at text not null
169
+ );
170
+
171
+ create index if not exists local_agent_sessions_workspace_id_idx
172
+ on local_agent_sessions(workspace_id, updated_at desc);
173
+
174
+ create index if not exists local_agent_sessions_workspace_root_idx
175
+ on local_agent_sessions(workspace_root, updated_at desc);
176
+
177
+ create index if not exists local_agent_sessions_provider_session_id_idx
178
+ on local_agent_sessions(provider_session_id);
179
+ `);
180
+ addColumnIfMissing(sqlite, "local_agent_sessions", "effort", "text");
181
+ }
182
+ function migrateWorkspaceConversationBindings(sqlite) {
183
+ sqlite.exec(`
184
+ create table if not exists workspace_conversation_bindings (
185
+ conversation_scope_id text not null,
186
+ target_key text not null,
187
+ workspace_session_id text not null,
188
+ created_at text not null,
189
+ last_used_at text not null,
190
+ primary key (conversation_scope_id, target_key),
191
+ foreign key (workspace_session_id)
192
+ references workspace_sessions(id)
193
+ on delete cascade
194
+ );
195
+
196
+ create index if not exists workspace_conversation_bindings_workspace_idx
197
+ on workspace_conversation_bindings(workspace_session_id);
198
+ `);
199
+ }
200
+ function migrateLocalAgentStructuredErrors(sqlite) {
201
+ addColumnIfMissing(sqlite, "local_agent_sessions", "error_code", "text");
202
+ addColumnIfMissing(sqlite, "local_agent_sessions", "error_retryable", "text");
203
+ }
204
+ function migrateLocalAgentEffortRename(sqlite) {
205
+ const columns = sqlite.prepare("pragma table_info(local_agent_sessions)").all();
206
+ const names = new Set(columns.map((column) => column.name));
207
+ if (names.has("effort")) {
208
+ if (names.has("thinking")) {
209
+ sqlite.exec(`
210
+ update local_agent_sessions
211
+ set effort = thinking
212
+ where effort is null and thinking is not null
213
+ `);
214
+ }
215
+ return;
216
+ }
217
+ if (!names.has("thinking")) {
218
+ addColumnIfMissing(sqlite, "local_agent_sessions", "effort", "text");
219
+ return;
220
+ }
221
+ sqlite.exec("alter table local_agent_sessions rename column thinking to effort");
222
+ }
223
+ function migrateLocalAgentLifecycleOutput(sqlite) {
224
+ addColumnIfMissing(sqlite, "local_agent_sessions", "latest_output", "text");
225
+ }
226
+ function migrateDeviceTokens(sqlite) {
227
+ sqlite.exec(`
228
+ create table if not exists device_tokens (
229
+ token_hash text primary key,
230
+ name text not null,
231
+ created_at text not null,
232
+ last_used_at text
233
+ );
234
+ `);
235
+ }
236
+ function migrateTaskRuntime(sqlite) {
237
+ sqlite.exec(`
238
+ create table if not exists tasks (
239
+ id text primary key,
240
+ workspace_id text,
241
+ workspace_root text not null,
242
+ goal text not null,
243
+ status text not null,
244
+ completion_state text,
245
+ mode text not null,
246
+ plan text,
247
+ evidence text,
248
+ error text,
249
+ tool_calls integer not null default 0,
250
+ created_at text not null,
251
+ updated_at text not null
252
+ );
253
+ create index if not exists tasks_workspace_idx on tasks(workspace_id, updated_at desc);
254
+ create index if not exists tasks_status_idx on tasks(status, updated_at desc);
255
+ `);
256
+ }
257
+ function addColumnIfMissing(sqlite, table, column, definition) {
258
+ const columns = sqlite.prepare(`pragma table_info(${table})`).all();
259
+ if (columns.some((existingColumn) => existingColumn.name === column))
260
+ return;
261
+ sqlite.exec(`alter table ${table} add column ${column} ${definition}`);
262
+ }
@@ -0,0 +1,85 @@
1
+ import { index, integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
2
+ export const workspaceSessions = sqliteTable("workspace_sessions", {
3
+ id: text("id").primaryKey(),
4
+ root: text("root").notNull(),
5
+ status: text("status").notNull().default("active"),
6
+ mode: text("mode").notNull().default("checkout"),
7
+ sourceRoot: text("source_root"),
8
+ baseRef: text("base_ref"),
9
+ baseSha: text("base_sha"),
10
+ managed: text("managed").notNull().default("false"),
11
+ createdAt: text("created_at").notNull(),
12
+ lastUsedAt: text("last_used_at").notNull(),
13
+ }, (table) => [
14
+ index("workspace_sessions_root_idx").on(table.root, table.lastUsedAt),
15
+ index("workspace_sessions_status_idx").on(table.status, table.lastUsedAt),
16
+ ]);
17
+ export const loadedAgentFiles = sqliteTable("loaded_agent_files", {
18
+ workspaceSessionId: text("workspace_session_id")
19
+ .notNull()
20
+ .references(() => workspaceSessions.id, { onDelete: "cascade" }),
21
+ path: text("path").notNull(),
22
+ contentHash: text("content_hash").notNull(),
23
+ content: text("content").notNull(),
24
+ loadedAt: text("loaded_at").notNull(),
25
+ lastSeenAt: text("last_seen_at").notNull(),
26
+ }, (table) => [
27
+ primaryKey({ columns: [table.workspaceSessionId, table.path] }),
28
+ index("loaded_agent_files_path_idx").on(table.path),
29
+ ]);
30
+ export const workspaceConversationBindings = sqliteTable("workspace_conversation_bindings", {
31
+ conversationScopeId: text("conversation_scope_id").notNull(),
32
+ targetKey: text("target_key").notNull(),
33
+ workspaceSessionId: text("workspace_session_id")
34
+ .notNull()
35
+ .references(() => workspaceSessions.id, { onDelete: "cascade" }),
36
+ createdAt: text("created_at").notNull(),
37
+ lastUsedAt: text("last_used_at").notNull(),
38
+ }, (table) => [
39
+ primaryKey({ columns: [table.conversationScopeId, table.targetKey] }),
40
+ index("workspace_conversation_bindings_workspace_idx").on(table.workspaceSessionId),
41
+ ]);
42
+ export const oauthClients = sqliteTable("oauth_clients", {
43
+ clientId: text("client_id").primaryKey(),
44
+ clientJson: text("client_json").notNull(),
45
+ issuedAt: integer("issued_at").notNull(),
46
+ });
47
+ export const oauthAccessTokens = sqliteTable("oauth_access_tokens", {
48
+ tokenHash: text("token_hash").primaryKey(),
49
+ clientId: text("client_id")
50
+ .notNull()
51
+ .references(() => oauthClients.clientId, { onDelete: "cascade" }),
52
+ scopesJson: text("scopes_json").notNull(),
53
+ expiresAt: integer("expires_at").notNull(),
54
+ resource: text("resource"),
55
+ });
56
+ export const oauthRefreshTokens = sqliteTable("oauth_refresh_tokens", {
57
+ tokenHash: text("token_hash").primaryKey(),
58
+ clientId: text("client_id")
59
+ .notNull()
60
+ .references(() => oauthClients.clientId, { onDelete: "cascade" }),
61
+ scopesJson: text("scopes_json").notNull(),
62
+ expiresAt: integer("expires_at").notNull(),
63
+ resource: text("resource"),
64
+ });
65
+ export const localAgentSessions = sqliteTable("local_agent_sessions", {
66
+ id: text("id").primaryKey(),
67
+ workspaceId: text("workspace_id"),
68
+ workspaceRoot: text("workspace_root").notNull(),
69
+ profileName: text("profile_name").notNull(),
70
+ provider: text("provider").notNull(),
71
+ model: text("model"),
72
+ effort: text("effort"),
73
+ providerSessionId: text("provider_session_id"),
74
+ status: text("status").notNull(),
75
+ latestResponse: text("latest_response"),
76
+ error: text("error"),
77
+ errorCode: text("error_code"),
78
+ errorRetryable: text("error_retryable"),
79
+ createdAt: text("created_at").notNull(),
80
+ updatedAt: text("updated_at").notNull(),
81
+ }, (table) => [
82
+ index("local_agent_sessions_workspace_id_idx").on(table.workspaceId, table.updatedAt),
83
+ index("local_agent_sessions_workspace_root_idx").on(table.workspaceRoot, table.updatedAt),
84
+ index("local_agent_sessions_provider_session_id_idx").on(table.providerSessionId),
85
+ ]);
@@ -0,0 +1,61 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createHash } from "node:crypto";
3
+ import { openDatabase } from "./db/client.js";
4
+ const DEVICE_TOKEN_PREFIX = "dvst_";
5
+ export class DeviceTokenStore {
6
+ database;
7
+ constructor(stateDir) {
8
+ this.database = openDatabase(stateDir);
9
+ }
10
+ /** Returns the plaintext token exactly once; only the hash is persisted. */
11
+ create(name) {
12
+ const token = `${DEVICE_TOKEN_PREFIX}${randomBytes(32).toString("base64url")}`;
13
+ const now = new Date().toISOString();
14
+ this.database.sqlite
15
+ .prepare(`insert into device_tokens (token_hash, name, created_at) values (?, ?, ?)
16
+ on conflict(token_hash) do nothing`)
17
+ .run(hashToken(token), name, now);
18
+ return { token, record: { name, createdAt: now } };
19
+ }
20
+ /** Verifies a presented token, refreshing last_used_at. */
21
+ verify(token) {
22
+ if (!token.startsWith(DEVICE_TOKEN_PREFIX))
23
+ return undefined;
24
+ const hash = hashToken(token);
25
+ const row = this.database.sqlite
26
+ .prepare("select name, created_at, last_used_at from device_tokens where token_hash = ?")
27
+ .get(hash);
28
+ if (!row)
29
+ return undefined;
30
+ this.database.sqlite
31
+ .prepare("update device_tokens set last_used_at = ? where token_hash = ?")
32
+ .run(new Date().toISOString(), hash);
33
+ return {
34
+ name: row.name,
35
+ createdAt: row.created_at,
36
+ ...(row.last_used_at ? { lastUsedAt: row.last_used_at } : {}),
37
+ };
38
+ }
39
+ list() {
40
+ const rows = this.database.sqlite
41
+ .prepare("select name, created_at, last_used_at from device_tokens order by created_at desc")
42
+ .all();
43
+ return rows.map((row) => ({
44
+ name: row.name,
45
+ createdAt: row.created_at,
46
+ ...(row.last_used_at ? { lastUsedAt: row.last_used_at } : {}),
47
+ }));
48
+ }
49
+ revoke(name) {
50
+ const result = this.database.sqlite
51
+ .prepare("delete from device_tokens where name = ?")
52
+ .run(name);
53
+ return Number(result.changes) > 0;
54
+ }
55
+ close() {
56
+ this.database.close();
57
+ }
58
+ }
59
+ function hashToken(token) {
60
+ return createHash("sha256").update(token).digest("base64url");
61
+ }
@@ -0,0 +1,134 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { mkdir, realpath, rm, stat } from "node:fs/promises";
5
+ import { basename, join, relative, resolve } from "node:path";
6
+ import { assertAllowedPath, isPathInsideRoot } from "./roots.js";
7
+ const execFileAsync = promisify(execFile);
8
+ export class GitWorktreeError extends Error {
9
+ code;
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.code = code;
13
+ this.name = "GitWorktreeError";
14
+ }
15
+ }
16
+ export async function createManagedWorktree(input) {
17
+ const sourcePath = assertAllowedPath(input.sourcePath, input.config.allowedRoots);
18
+ try {
19
+ const sourceStats = await stat(sourcePath);
20
+ if (!sourceStats.isDirectory()) {
21
+ throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path is not a directory: ${input.sourcePath}`);
22
+ }
23
+ }
24
+ catch (error) {
25
+ if (error instanceof GitWorktreeError)
26
+ throw error;
27
+ throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because the source path does not exist: ${input.sourcePath}`);
28
+ }
29
+ const sourceRoot = await resolveGitRoot(sourcePath, input.config.allowedRoots);
30
+ const baseRef = input.baseRef ?? "HEAD";
31
+ const baseSha = await resolveBaseCommit(sourceRoot, baseRef);
32
+ const dirtySource = (await git(["status", "--porcelain=v1"], sourceRoot)).trim().length > 0;
33
+ const worktreePath = managedWorktreePath({
34
+ worktreeRoot: input.config.worktreeRoot,
35
+ repoRoot: sourceRoot,
36
+ });
37
+ await mkdir(input.config.worktreeRoot, { recursive: true });
38
+ assertAllowedPath(worktreePath, [input.config.worktreeRoot]);
39
+ try {
40
+ await git(["worktree", "add", "--detach", worktreePath, baseSha], sourceRoot);
41
+ }
42
+ catch (error) {
43
+ await rm(worktreePath, { recursive: true, force: true });
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ throw new GitWorktreeError("GIT_WORKTREE_CREATE_FAILED", `Git failed to create the managed worktree. ${message}`);
46
+ }
47
+ return {
48
+ sourceRoot,
49
+ path: worktreePath,
50
+ baseRef,
51
+ baseSha,
52
+ dirtySource,
53
+ detached: true,
54
+ managed: true,
55
+ };
56
+ }
57
+ async function resolveGitRoot(path, allowedRoots) {
58
+ try {
59
+ const output = await git(["rev-parse", "--show-toplevel"], path);
60
+ return await assertGitRootAllowed(output.trim(), allowedRoots);
61
+ }
62
+ catch (error) {
63
+ if (isGitUnavailable(error)) {
64
+ throw new GitWorktreeError("GIT_NOT_AVAILABLE", "Cannot open workspace in worktree mode because Git is not available on this machine.");
65
+ }
66
+ throw new GitWorktreeError("GIT_REPOSITORY_NOT_FOUND", `Cannot open workspace in worktree mode because this path is not inside a Git repository: ${path}. Use mode=\"checkout\" to work directly in this directory, or initialize Git and create an initial commit first.`);
67
+ }
68
+ }
69
+ async function assertGitRootAllowed(gitRoot, allowedRoots) {
70
+ try {
71
+ return assertAllowedPath(gitRoot, allowedRoots);
72
+ }
73
+ catch {
74
+ const canonicalGitRoot = await realpath(gitRoot);
75
+ for (const allowedRoot of allowedRoots) {
76
+ const canonicalAllowedRoot = await realpath(allowedRoot).catch(() => undefined);
77
+ if (!canonicalAllowedRoot || !isPathInsideRoot(canonicalGitRoot, canonicalAllowedRoot)) {
78
+ continue;
79
+ }
80
+ const logicalGitRoot = resolve(allowedRoot, relative(canonicalAllowedRoot, canonicalGitRoot));
81
+ return assertAllowedPath(logicalGitRoot, allowedRoots);
82
+ }
83
+ return assertAllowedPath(canonicalGitRoot, allowedRoots);
84
+ }
85
+ }
86
+ async function resolveBaseCommit(sourceRoot, baseRef) {
87
+ try {
88
+ return (await git(["rev-parse", "--verify", `${baseRef}^{commit}`], sourceRoot)).trim();
89
+ }
90
+ catch (error) {
91
+ if (baseRef === "HEAD") {
92
+ throw new GitWorktreeError("GIT_REPOSITORY_HAS_NO_COMMITS", "Cannot open workspace in worktree mode because the repository has no commits yet. Create an initial commit first, or use mode=\"checkout\".");
93
+ }
94
+ throw new GitWorktreeError("GIT_INVALID_BASE_REF", `Cannot open workspace in worktree mode because baseRef ${JSON.stringify(baseRef)} does not resolve to a commit.`);
95
+ }
96
+ }
97
+ function managedWorktreePath(input) {
98
+ const repoName = sanitizePathSegment(basename(input.repoRoot)) || "repo";
99
+ const worktreeId = randomBytes(4).toString("hex");
100
+ return join(input.worktreeRoot, `${repoName}-${worktreeId}`);
101
+ }
102
+ function sanitizePathSegment(value) {
103
+ return value
104
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
105
+ .replace(/^-+|-+$/g, "")
106
+ .slice(0, 80);
107
+ }
108
+ async function git(args, cwd) {
109
+ try {
110
+ const { stdout } = await execFileAsync("git", args, {
111
+ cwd,
112
+ maxBuffer: 10 * 1024 * 1024,
113
+ });
114
+ return stdout;
115
+ }
116
+ catch (error) {
117
+ if (isGitUnavailable(error))
118
+ throw error;
119
+ const stderr = typeof error === "object" && error && "stderr" in error
120
+ ? String(error.stderr ?? "").trim()
121
+ : "";
122
+ const stdout = typeof error === "object" && error && "stdout" in error
123
+ ? String(error.stdout ?? "").trim()
124
+ : "";
125
+ const details = stderr || stdout || (error instanceof Error ? error.message : String(error));
126
+ throw new Error(details);
127
+ }
128
+ }
129
+ function isGitUnavailable(error) {
130
+ return Boolean(typeof error === "object" &&
131
+ error &&
132
+ "code" in error &&
133
+ error.code === "ENOENT");
134
+ }
package/dist/git.js ADDED
@@ -0,0 +1,41 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ const execFileAsync = promisify(execFile);
5
+ export async function git(cwd, args, options = {}) {
6
+ const { stdout, stderr } = await execFileAsync("git", args, {
7
+ cwd,
8
+ env: options.env ? { ...process.env, ...options.env } : process.env,
9
+ maxBuffer: options.maxBuffer ?? 10 * 1024 * 1024,
10
+ });
11
+ return { stdout, stderr };
12
+ }
13
+ export async function getGitEligibility(cwd) {
14
+ try {
15
+ await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
16
+ }
17
+ catch {
18
+ return {
19
+ ok: false,
20
+ reason: "not_git",
21
+ message: "workspace is not inside a git repository",
22
+ };
23
+ }
24
+ const gitRoot = (await git(cwd, ["rev-parse", "--show-toplevel"])).stdout.trim();
25
+ try {
26
+ await git(gitRoot, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
27
+ }
28
+ catch {
29
+ return {
30
+ ok: false,
31
+ gitRoot,
32
+ reason: "no_head",
33
+ message: "repository has no HEAD commit",
34
+ };
35
+ }
36
+ return { ok: true, gitRoot };
37
+ }
38
+ export function safeWorkspaceRefSegment(workspaceId) {
39
+ const safe = workspaceId.replace(/[^A-Za-z0-9._-]/g, "-");
40
+ return safe.length > 0 ? safe : createHash("sha256").update(workspaceId).digest("hex").slice(0, 16);
41
+ }