@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,82 @@
1
+ import * as z from "zod/v4";
2
+ import { HEARTH_CONFIG_VERSION, hearthConfigSchema, } from "./config-schema.js";
3
+ import { storedSubagentsConfigSchema } from "./local-agent-config.js";
4
+ import { LOCAL_AGENT_PROVIDERS } from "./local-agent-profiles.js";
5
+ const legacyConfigSchema = z.object({
6
+ host: z.string().optional(),
7
+ port: z.number().optional(),
8
+ allowedRoots: z.array(z.string()).optional(),
9
+ publicBaseUrl: z.string().nullable().optional(),
10
+ allowedHosts: z.array(z.string()).optional(),
11
+ stateDir: z.string().optional(),
12
+ worktreeRoot: z.string().optional(),
13
+ artifactsEnabled: z.boolean().optional(),
14
+ artifactMaxFileBytes: z.number().optional(),
15
+ agentDir: z.string().optional(),
16
+ subagents: storedSubagentsConfigSchema.optional(),
17
+ tools: z.object({
18
+ mode: z.enum(["claude", "codex"]).optional(),
19
+ }).strict().optional(),
20
+ ui: z.object({
21
+ enabled: z.boolean().optional(),
22
+ }).strict().optional(),
23
+ }).passthrough();
24
+ const LEGACY_CONFIG_KEYS = new Set([
25
+ "host",
26
+ "port",
27
+ "allowedRoots",
28
+ "publicBaseUrl",
29
+ "allowedHosts",
30
+ "stateDir",
31
+ "worktreeRoot",
32
+ "artifactsEnabled",
33
+ "artifactMaxFileBytes",
34
+ "agentDir",
35
+ "subagents",
36
+ "tools",
37
+ "ui",
38
+ ]);
39
+ export function migrateLegacyConfig(value) {
40
+ const legacy = legacyConfigSchema.parse(value);
41
+ const unsupportedKeys = Object.keys(legacy).filter((key) => !LEGACY_CONFIG_KEYS.has(key));
42
+ if (unsupportedKeys.length > 0) {
43
+ throw new Error(`Unsupported legacy configuration keys: ${unsupportedKeys.sort().join(", ")}`);
44
+ }
45
+ return hearthConfigSchema.parse({
46
+ configVersion: HEARTH_CONFIG_VERSION,
47
+ server: definedEntries({
48
+ host: legacy.host,
49
+ port: legacy.port,
50
+ publicBaseUrl: legacy.publicBaseUrl,
51
+ allowedHosts: legacy.allowedHosts,
52
+ }),
53
+ workspaces: definedEntries({
54
+ allowedRoots: legacy.allowedRoots,
55
+ worktreeRoot: legacy.worktreeRoot,
56
+ }),
57
+ storage: definedEntries({ stateDir: legacy.stateDir }),
58
+ tools: definedEntries({ mode: legacy.tools?.mode }),
59
+ ui: definedEntries({ enabled: legacy.ui?.enabled }),
60
+ artifacts: definedEntries({
61
+ enabled: legacy.artifactsEnabled,
62
+ maxFileBytes: legacy.artifactMaxFileBytes,
63
+ }),
64
+ skills: definedEntries({ agentDir: legacy.agentDir }),
65
+ subagents: migrateLegacySubagents(legacy.subagents),
66
+ });
67
+ }
68
+ function definedEntries(value) {
69
+ return Object.fromEntries(Object.entries(value).filter((entry) => entry[1] !== undefined));
70
+ }
71
+ function migrateLegacySubagents(value) {
72
+ if (value === undefined)
73
+ return undefined;
74
+ if (typeof value !== "boolean")
75
+ return value;
76
+ return {
77
+ enabled: value,
78
+ providers: value
79
+ ? LOCAL_AGENT_PROVIDERS.map((id) => ({ id, enabled: true }))
80
+ : [],
81
+ };
82
+ }
@@ -0,0 +1,136 @@
1
+ import * as z from "zod/v4";
2
+ import { subagentsConfigSchema } from "./local-agent-config.js";
3
+ import { fleetConfigSchema } from "./orchestration/lanes.js";
4
+ export const HEARTH_CONFIG_VERSION = 1;
5
+ export const HEARTH_CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/Waishnav/hearth/main/schema/v1/hearth.schema.json";
6
+ const serverConfigSchema = z.object({
7
+ host: z.string().trim().min(1).default("127.0.0.1"),
8
+ port: z.number().int().min(1).max(65_535).default(7176),
9
+ publicBaseUrl: z.string().url().nullable().default(null),
10
+ allowedHosts: z.array(z.string().trim().min(1)).default([]),
11
+ trustProxy: z.boolean().default(false),
12
+ // "derived": OAuth issuer/base URLs follow publicBaseUrl (today's behavior,
13
+ // required for remote clients like ChatGPT). "local": the issuer is pinned
14
+ // to the local bind address so changing the tunnel URL does not invalidate
15
+ // OAuth clients; only meaningful for local/LAN-only deployments.
16
+ issuerMode: z.enum(["derived", "local"]).default("derived"),
17
+ }).strict().prefault({});
18
+ const tlsConfigSchema = z.object({
19
+ // Native TLS for relay-free direct exposure: when both are set, `serve`
20
+ // terminates HTTPS itself (e.g. behind your own domain + port forward).
21
+ // Leave null when running behind localhost or a tunnel that terminates TLS.
22
+ certFile: z.string().trim().min(1).nullable().default(null),
23
+ keyFile: z.string().trim().min(1).nullable().default(null),
24
+ // Directory served (read-only) at /.well-known/acme-challenge/ so certbot
25
+ // webroot mode can provision certificates while the server runs.
26
+ acmeDir: z.string().trim().min(1).nullable().default(null),
27
+ }).strict().prefault({});
28
+ const workspaceProfileSchema = z.object({
29
+ path: z.string().trim().min(1).describe("Absolute path (or ~-prefixed) this profile applies to; longest prefix wins."),
30
+ mode: z.enum(["readonly", "supervised", "autonomous"]).optional(),
31
+ sandbox: z.enum(["auto", "none"]).optional(),
32
+ sandboxNetwork: z.enum(["allow", "deny"]).optional(),
33
+ requireSandboxForAutonomous: z.boolean().optional(),
34
+ commandAllow: z.array(z.string().trim().min(1)).default([]).describe("Regex allowlist; when non-empty only matching commands run."),
35
+ commandDeny: z.array(z.string().trim().min(1)).default([]).describe("Regex denylist; matching commands are always rejected."),
36
+ agentsAllowed: z.boolean().optional().describe("Whether agent tools may run for this workspace. Defaults to true."),
37
+ }).strict();
38
+ const workspacesConfigSchema = z.object({
39
+ allowedRoots: z.array(z.string().trim().min(1)).default([]),
40
+ worktreeRoot: z.string().trim().min(1).default("~/.hearth/worktrees"),
41
+ profiles: z.array(workspaceProfileSchema).default([]),
42
+ }).strict().prefault({});
43
+ const storageConfigSchema = z.object({
44
+ stateDir: z.string().trim().min(1).default("~/.local/share/hearth"),
45
+ }).strict().prefault({});
46
+ const toolsConfigSchema = z.object({
47
+ mode: z.enum(["claude", "codex"]).default("codex"),
48
+ }).strict().prefault({});
49
+ const uiConfigSchema = z.object({
50
+ enabled: z.boolean().default(true),
51
+ }).strict().prefault({});
52
+ const artifactsConfigSchema = z.object({
53
+ enabled: z.boolean().default(false),
54
+ maxFileBytes: z.number().int().positive().default(100 * 1024 * 1024),
55
+ }).strict().prefault({});
56
+ const skillsConfigSchema = z.object({
57
+ enabled: z.boolean().default(true),
58
+ paths: z.array(z.string().trim().min(1)).default([]),
59
+ agentDir: z.string().trim().min(1).default("~/.codex"),
60
+ }).strict().prefault({});
61
+ const loggingConfigSchema = z.object({
62
+ level: z.enum(["silent", "error", "warn", "info", "debug"]).default("info"),
63
+ format: z.enum(["json", "pretty"]).default("json"),
64
+ requests: z.boolean().default(true),
65
+ assets: z.boolean().default(false),
66
+ toolCalls: z.boolean().default(true),
67
+ shellCommands: z.boolean().default(false),
68
+ }).strict().prefault({});
69
+ const executionConfigSchema = z.object({
70
+ // Policy mode for model-invoked shell tools:
71
+ // readonly — tier-0 inspection commands only
72
+ // supervised — tier 0/1 allowed; tier 2 requires an explicit
73
+ // user-approval claim (approvedByUser) from the model
74
+ // autonomous — tier 0/1/2 allowed without approval claims
75
+ // tier 3 commands are blocked in every mode.
76
+ mode: z.enum(["readonly", "supervised", "autonomous"]).default("supervised"),
77
+ // Pass the full parent environment to model-invoked commands. Insecure
78
+ // escape hatch; the default is a conservative allowlist.
79
+ envAllowAll: z.boolean().default(false),
80
+ // Additional environment variable names passed to model-invoked commands.
81
+ envAllowlist: z.array(z.string().trim().min(1)).default([]),
82
+ // OS sandbox for autonomous-mode shell commands and verification gates:
83
+ // "auto" uses bubblewrap (Linux) or seatbelt (macOS) when available.
84
+ sandbox: z.enum(["auto", "none"]).default("auto"),
85
+ sandboxNetwork: z.enum(["allow", "deny"]).default("allow"),
86
+ // When true, autonomous-mode tier-2/3 commands are denied unless a sandbox
87
+ // adapter is actually available (tier 3 is always denied regardless).
88
+ // Default true (fail closed): on machines without bubblewrap/seatbelt
89
+ // (notably Windows, where no equivalent exists), autonomous tier-2 is
90
+ // denied rather than silently unsandboxed. Opt out explicitly per
91
+ // workspace or globally when you accept unsandboxed autonomy.
92
+ requireSandboxForAutonomous: z.boolean().default(true),
93
+ }).strict().prefault({});
94
+ const oauthConfigSchema = z.object({
95
+ accessTokenTtlSeconds: z.number().int().positive().default(60 * 60),
96
+ refreshTokenTtlSeconds: z.number().int().positive().default(30 * 24 * 60 * 60),
97
+ scopes: z.array(z.string().trim().min(1)).min(1).default(["hearth"]),
98
+ allowedRedirectHosts: z.array(z.string().trim().min(1)).min(1).default([
99
+ "chatgpt.com",
100
+ "claude.ai",
101
+ "anthropic.com",
102
+ "localhost",
103
+ "127.0.0.1",
104
+ ]),
105
+ }).strict().prefault({});
106
+ export const hearthConfigSchema = z.object({
107
+ $schema: z.string().url().default(HEARTH_CONFIG_SCHEMA_URL),
108
+ configVersion: z.literal(HEARTH_CONFIG_VERSION),
109
+ server: serverConfigSchema,
110
+ workspaces: workspacesConfigSchema,
111
+ storage: storageConfigSchema,
112
+ tools: toolsConfigSchema,
113
+ ui: uiConfigSchema,
114
+ artifacts: artifactsConfigSchema,
115
+ skills: skillsConfigSchema,
116
+ subagents: subagentsConfigSchema.default({ enabled: false, providers: [] }),
117
+ fleet: fleetConfigSchema.default({ lanes: {} }),
118
+ execution: executionConfigSchema,
119
+ logging: loggingConfigSchema,
120
+ tls: tlsConfigSchema.default({ certFile: null, keyFile: null, acmeDir: null }),
121
+ oauth: oauthConfigSchema,
122
+ }).strict();
123
+ export function defaultHearthConfig() {
124
+ return hearthConfigSchema.parse({ configVersion: HEARTH_CONFIG_VERSION });
125
+ }
126
+ export function hearthConfigJsonSchema() {
127
+ return {
128
+ $id: HEARTH_CONFIG_SCHEMA_URL,
129
+ title: "Hearth configuration",
130
+ description: "Versioned configuration for a local Hearth MCP server.",
131
+ ...z.toJSONSchema(hearthConfigSchema, {
132
+ target: "draft-2020-12",
133
+ io: "input",
134
+ }),
135
+ };
136
+ }
package/dist/config.js ADDED
@@ -0,0 +1,115 @@
1
+ import { resolve } from "node:path";
2
+ import { expandHomePath } from "./roots.js";
3
+ import { hearthAgentsDir, hearthSkillsDir, loadHearthFiles } from "./user-config.js";
4
+ export function loadConfig(env = process.env) {
5
+ const files = loadHearthFiles(env);
6
+ const stored = files.config;
7
+ const host = stored.server.host;
8
+ const port = stored.server.port;
9
+ const publicBaseUrl = parsePublicBaseUrl(stored.server.publicBaseUrl ?? localPublicBaseUrl(host, port));
10
+ const derivedAllowedHosts = [
11
+ "localhost",
12
+ "127.0.0.1",
13
+ "::1",
14
+ host,
15
+ new URL(publicBaseUrl).hostname,
16
+ ...stored.server.allowedHosts,
17
+ ];
18
+ return {
19
+ configDir: files.dir,
20
+ host,
21
+ port,
22
+ oauth: {
23
+ ownerToken: parseRequiredSecret(env.HEARTH_OAUTH_OWNER_TOKEN ?? files.auth.ownerToken),
24
+ accessTokenTtlSeconds: stored.oauth.accessTokenTtlSeconds,
25
+ refreshTokenTtlSeconds: stored.oauth.refreshTokenTtlSeconds,
26
+ scopes: stored.oauth.scopes,
27
+ allowedRedirectHosts: stored.oauth.allowedRedirectHosts,
28
+ trustProxy: stored.server.trustProxy,
29
+ },
30
+ allowedRoots: normalizePaths(stored.workspaces.allowedRoots, [process.cwd()]),
31
+ workspaceProfiles: stored.workspaces.profiles.map((profile) => ({
32
+ path: normalizePath(profile.path),
33
+ ...(profile.mode !== undefined ? { mode: profile.mode } : {}),
34
+ ...(profile.sandbox !== undefined ? { sandbox: profile.sandbox } : {}),
35
+ ...(profile.sandboxNetwork !== undefined ? { sandboxNetwork: profile.sandboxNetwork } : {}),
36
+ ...(profile.requireSandboxForAutonomous !== undefined ? { requireSandboxForAutonomous: profile.requireSandboxForAutonomous } : {}),
37
+ commandAllow: profile.commandAllow,
38
+ commandDeny: profile.commandDeny,
39
+ ...(profile.agentsAllowed !== undefined ? { agentsAllowed: profile.agentsAllowed } : {}),
40
+ })),
41
+ allowedHosts: normalizeAllowedHosts(derivedAllowedHosts),
42
+ publicBaseUrl,
43
+ oauthIssuerUrl: stored.server.issuerMode === "local"
44
+ ? localPublicBaseUrl(host, port)
45
+ : publicBaseUrl,
46
+ toolMode: stored.tools.mode,
47
+ uiEnabled: stored.ui.enabled,
48
+ stateDir: normalizePath(stored.storage.stateDir),
49
+ worktreeRoot: normalizePath(stored.workspaces.worktreeRoot),
50
+ artifactsEnabled: stored.artifacts.enabled,
51
+ artifactMaxFileBytes: stored.artifacts.maxFileBytes,
52
+ skillsEnabled: stored.skills.enabled,
53
+ skillPaths: stored.skills.paths,
54
+ hearthSkillsDir: hearthSkillsDir(env),
55
+ hearthAgentsDir: hearthAgentsDir(env),
56
+ subagents: stored.subagents,
57
+ fleet: stored.fleet,
58
+ execution: {
59
+ mode: stored.execution.mode,
60
+ envAllowAll: stored.execution.envAllowAll,
61
+ envAllowlist: stored.execution.envAllowlist,
62
+ sandbox: stored.execution.sandbox,
63
+ sandboxNetwork: stored.execution.sandboxNetwork,
64
+ requireSandboxForAutonomous: stored.execution.requireSandboxForAutonomous,
65
+ },
66
+ agentDir: normalizePath(stored.skills.agentDir),
67
+ logging: {
68
+ ...stored.logging,
69
+ trustProxy: stored.server.trustProxy,
70
+ },
71
+ tls: {
72
+ certFile: stored.tls.certFile,
73
+ keyFile: stored.tls.keyFile,
74
+ acmeDir: stored.tls.acmeDir,
75
+ },
76
+ };
77
+ }
78
+ function normalizePaths(paths, fallback = []) {
79
+ return (paths.length > 0 ? paths : fallback).map(normalizePath);
80
+ }
81
+ function normalizePath(path) {
82
+ return resolve(expandHomePath(path));
83
+ }
84
+ function normalizeAllowedHosts(hosts) {
85
+ if (hosts.includes("*"))
86
+ return ["*"];
87
+ return Array.from(new Set(hosts.map((host) => host.trim()).filter(Boolean)));
88
+ }
89
+ function parseRequiredSecret(value) {
90
+ const secret = value?.trim();
91
+ if (!secret) {
92
+ throw new Error("OAuth owner token is required. Run: hearth init");
93
+ }
94
+ if (secret.length < 16) {
95
+ throw new Error("OAuth owner token must be at least 16 characters long.");
96
+ }
97
+ return secret;
98
+ }
99
+ function parsePublicBaseUrl(value) {
100
+ const parsed = new URL(value);
101
+ parsed.hash = "";
102
+ parsed.search = "";
103
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "");
104
+ if (/(^|\/)mcp$/i.test(parsed.pathname)) {
105
+ throw new Error(`Invalid publicBaseUrl ${JSON.stringify(value)}: use the origin without /mcp (for example https://${parsed.host}). Fix with: hearth config set publicBaseUrl https://${parsed.host}`);
106
+ }
107
+ return parsed.toString().replace(/\/$/, "");
108
+ }
109
+ function localPublicBaseUrl(host, port) {
110
+ const publicHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
111
+ const formattedHost = publicHost.includes(":") && !publicHost.startsWith("[")
112
+ ? `[${publicHost}]`
113
+ : publicHost;
114
+ return `http://${formattedHost}:${port}`;
115
+ }
@@ -0,0 +1,150 @@
1
+ import * as z from "zod/v4";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { buildRepoMap, formatRepoMap } from "./repo-map.js";
5
+ import { logToolCall, resultOutputSchema, textBlock } from "../tool-surfaces/shared.js";
6
+ import { workspaceIdDescription } from "../tool-surfaces/types.js";
7
+ /**
8
+ * Context tools (P6, first slice): a repository overview and a bounded
9
+ * text search. Both are read-only, run locally, and hand the model curated
10
+ * repository knowledge instead of forcing it to shell its way around.
11
+ */
12
+ const execFileAsync = promisify(execFile);
13
+ let ripgrepAvailable;
14
+ function hasRipgrep() {
15
+ ripgrepAvailable ??= (() => {
16
+ try {
17
+ execFileSyncShort("rg", ["--version"]);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ })();
24
+ return ripgrepAvailable;
25
+ }
26
+ function execFileSyncShort(command, args) {
27
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
28
+ const { execFileSync } = require("node:child_process");
29
+ execFileSync(command, args, { stdio: "ignore", timeout: 5000 });
30
+ }
31
+ const MAX_SEARCH_OUTPUT_CHARS = 40_000;
32
+ export function registerContextTools(target, context) {
33
+ const { config, workspaces } = context;
34
+ target.registerTool("context_overview", {
35
+ title: "Repository overview",
36
+ description: "Get a bounded repository map for the workspace: file counts, languages, manifests, and the largest directories. Use this before exploring a large repository so you stop guessing where things live.",
37
+ inputSchema: { workspaceId: z.string().describe(workspaceIdDescription) },
38
+ outputSchema: resultOutputSchema(),
39
+ annotations: { readOnlyHint: true },
40
+ }, async ({ workspaceId }) => {
41
+ const startedAt = performance.now();
42
+ const workspace = workspaces.getWorkspace(workspaceId);
43
+ const map = await buildRepoMap(workspace.root);
44
+ const text = formatRepoMap(map);
45
+ logToolCall(config, {
46
+ tool: "context_overview",
47
+ workspaceId,
48
+ success: true,
49
+ durationMs: Math.round(performance.now() - startedAt),
50
+ });
51
+ return {
52
+ content: [textBlock(text)],
53
+ structuredContent: {
54
+ result: text,
55
+ totalFiles: map.totalFiles,
56
+ truncated: map.truncated,
57
+ manifests: map.manifests,
58
+ languages: map.languages,
59
+ },
60
+ };
61
+ });
62
+ target.registerTool("search", {
63
+ title: "Search workspace",
64
+ description: "Search file contents in the workspace with a regex (ripgrep when available, grep fallback). Read-only and bounded; prefer this over shell grep pipelines.",
65
+ inputSchema: {
66
+ workspaceId: z.string().describe(workspaceIdDescription),
67
+ pattern: z.string().min(1).max(500).describe("Regular expression to find."),
68
+ glob: z.string().max(200).optional().describe("Optional glob filter, e.g. '*.ts'."),
69
+ maxResults: z.number().int().min(1).max(500).optional().describe("Result line cap. Defaults to 100."),
70
+ },
71
+ outputSchema: resultOutputSchema(),
72
+ annotations: { readOnlyHint: true },
73
+ }, async ({ workspaceId, pattern, glob, maxResults }) => {
74
+ const startedAt = performance.now();
75
+ const workspace = workspaces.getWorkspace(workspaceId);
76
+ const cap = maxResults ?? 100;
77
+ let text;
78
+ try {
79
+ text = await runSearch(workspace.root, pattern, glob, cap);
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ logToolCall(config, {
84
+ tool: "search",
85
+ workspaceId,
86
+ success: false,
87
+ durationMs: Math.round(performance.now() - startedAt),
88
+ error: message.slice(0, 200),
89
+ });
90
+ return {
91
+ content: [textBlock(`Search failed: ${message}`)],
92
+ isError: true,
93
+ structuredContent: { result: `Search failed: ${message}` },
94
+ };
95
+ }
96
+ logToolCall(config, {
97
+ tool: "search",
98
+ workspaceId,
99
+ success: true,
100
+ durationMs: Math.round(performance.now() - startedAt),
101
+ });
102
+ return {
103
+ content: [textBlock(text)],
104
+ structuredContent: { result: text },
105
+ };
106
+ });
107
+ }
108
+ async function runSearch(root, pattern, glob, cap) {
109
+ const args = hasRipgrep()
110
+ ? [
111
+ "rg", "--no-heading", "-n", "-S",
112
+ "--max-count", String(Math.min(cap, 500)),
113
+ "--glob", "!node_modules/**", "--glob", "!.git/**",
114
+ ...(glob ? ["-g", glob] : []),
115
+ pattern, root,
116
+ ]
117
+ : [
118
+ "grep", "-rn", "-E", "-I",
119
+ "--exclude-dir=node_modules", "--exclude-dir=.git",
120
+ ...(glob ? [`--include=${glob}`] : []),
121
+ pattern, root,
122
+ ];
123
+ const argv = args.slice(1);
124
+ const command = args[0];
125
+ try {
126
+ const { stdout } = await execFileAsync(command, argv, {
127
+ cwd: root,
128
+ timeout: 10_000,
129
+ maxBuffer: 1024 * 1024,
130
+ });
131
+ return bound(String(stdout), cap);
132
+ }
133
+ catch (error) {
134
+ const err = error;
135
+ // grep/rg exit 1 on "no matches" — that is a successful empty search.
136
+ if (err.code === 1)
137
+ return "No matches.";
138
+ if (typeof err.stdout === "string" && err.stdout)
139
+ return bound(err.stdout, cap);
140
+ throw error;
141
+ }
142
+ }
143
+ function bound(output, cap) {
144
+ const lines = output.split("\n").filter((line) => line.trim().length > 0).slice(0, cap);
145
+ let text = lines.join("\n");
146
+ if (text.length > MAX_SEARCH_OUTPUT_CHARS) {
147
+ text = `${text.slice(0, MAX_SEARCH_OUTPUT_CHARS)}\n…output truncated`;
148
+ }
149
+ return text || "No matches.";
150
+ }
@@ -0,0 +1,102 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { join, relative, extname } from "node:path";
3
+ /**
4
+ * Context Engine, first slice (P6): a deterministic, local repository map.
5
+ * No embeddings, no network — just the structure a model needs to stop
6
+ * re-walking the tree: languages, manifests, and a bounded directory tree.
7
+ */
8
+ const SKIP_DIRS = new Set([
9
+ ".git", ".hg", ".svn", "node_modules", "dist", "build", "out", ".next",
10
+ ".turbo", ".cache", ".venv", "venv", "__pycache__", "target", "coverage",
11
+ ".idea", ".vscode", "vendor",
12
+ ]);
13
+ const MANIFESTS = [
14
+ "package.json", "Cargo.toml", "go.mod", "pyproject.toml",
15
+ "requirements.txt", "Makefile", "composer.json", "Gemfile", "pom.xml",
16
+ ];
17
+ export async function buildRepoMap(root, options = {}) {
18
+ const maxFiles = options.maxFiles ?? 5000;
19
+ const maxDepth = options.maxDepth ?? 20;
20
+ const files = [];
21
+ const dirFiles = new Map();
22
+ const manifests = new Set();
23
+ let truncated = false;
24
+ const walk = async (dir, depth) => {
25
+ if (depth > maxDepth)
26
+ return;
27
+ let entries;
28
+ try {
29
+ entries = await readdir(dir, { withFileTypes: true });
30
+ }
31
+ catch {
32
+ return;
33
+ }
34
+ for (const entry of entries) {
35
+ const path = join(dir, entry.name);
36
+ if (entry.isDirectory()) {
37
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
38
+ await walk(path, depth + 1);
39
+ }
40
+ continue;
41
+ }
42
+ if (!entry.isFile())
43
+ continue;
44
+ if (files.length >= maxFiles) {
45
+ truncated = true;
46
+ return;
47
+ }
48
+ files.push(path);
49
+ dirFiles.set(dir, (dirFiles.get(dir) ?? 0) + 1);
50
+ if (MANIFESTS.includes(entry.name))
51
+ manifests.add(entry.name);
52
+ }
53
+ };
54
+ await walk(root, 0);
55
+ const byExt = new Map();
56
+ let totalBytes = 0;
57
+ for (const file of files) {
58
+ const ext = extname(file).toLowerCase() || "(none)";
59
+ byExt.set(ext, (byExt.get(ext) ?? 0) + 1);
60
+ try {
61
+ const stats = await stat(file);
62
+ totalBytes += stats.size;
63
+ }
64
+ catch {
65
+ // File vanished mid-walk; fine for an approximate map.
66
+ }
67
+ }
68
+ const languages = [...byExt.entries()]
69
+ .map(([extension, count]) => ({ extension, count }))
70
+ .sort((a, b) => b.count - a.count)
71
+ .slice(0, 12);
72
+ const directories = [...dirFiles.entries()]
73
+ .map(([dir, count]) => ({ path: relative(root, dir) || ".", files: count }))
74
+ .filter((entry) => entry.path !== ".")
75
+ .sort((a, b) => b.files - a.files)
76
+ .slice(0, 25);
77
+ return {
78
+ root,
79
+ totalFiles: files.length,
80
+ truncated,
81
+ totalBytes,
82
+ languages,
83
+ manifests: [...manifests].sort(),
84
+ directories,
85
+ };
86
+ }
87
+ export function formatRepoMap(map) {
88
+ const lines = [
89
+ `Repository map for ${map.root}`,
90
+ `Files: ${map.totalFiles}${map.truncated ? "+ (truncated)" : ""}, ~${Math.round(map.totalBytes / 1024)} KB`,
91
+ `Manifests: ${map.manifests.length > 0 ? map.manifests.join(", ") : "none detected"}`,
92
+ "Languages: " + map.languages.map((lang) => `${lang.extension}×${lang.count}`).join(", "),
93
+ "Largest directories:",
94
+ ...map.directories.map((dir) => ` ${dir.path} (${dir.files} files)`),
95
+ ];
96
+ return lines.join("\n");
97
+ }
98
+ /** Cheap text file reader used by context tools (bounded). */
99
+ export async function readTextHead(path, maxBytes = 64 * 1024) {
100
+ const content = await readFile(path, "utf8");
101
+ return content.length > maxBytes ? content.slice(0, maxBytes) : content;
102
+ }