harnery 0.0.1 → 0.2.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 (445) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/bin/agent-coord +42 -0
  4. package/bin/agent-hook +44 -0
  5. package/bin/harn +40 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +18 -0
  9. package/dist/commander.d.ts +128 -0
  10. package/dist/commander.d.ts.map +1 -0
  11. package/dist/commander.js +126 -0
  12. package/dist/commands/agents.d.ts +18 -0
  13. package/dist/commands/agents.d.ts.map +1 -0
  14. package/dist/commands/agents.js +3946 -0
  15. package/dist/commands/backup.d.ts +22 -0
  16. package/dist/commands/backup.d.ts.map +1 -0
  17. package/dist/commands/backup.js +262 -0
  18. package/dist/commands/browse-ai.d.ts +4 -0
  19. package/dist/commands/browse-ai.d.ts.map +1 -0
  20. package/dist/commands/browse-ai.js +156 -0
  21. package/dist/commands/browse.d.ts +4 -0
  22. package/dist/commands/browse.d.ts.map +1 -0
  23. package/dist/commands/browse.js +590 -0
  24. package/dist/commands/callers.d.ts +4 -0
  25. package/dist/commands/callers.d.ts.map +1 -0
  26. package/dist/commands/callers.js +276 -0
  27. package/dist/commands/completion.d.ts +17 -0
  28. package/dist/commands/completion.d.ts.map +1 -0
  29. package/dist/commands/completion.js +158 -0
  30. package/dist/commands/config-get.d.ts +4 -0
  31. package/dist/commands/config-get.d.ts.map +1 -0
  32. package/dist/commands/config-get.js +131 -0
  33. package/dist/commands/context.d.ts +11 -0
  34. package/dist/commands/context.d.ts.map +1 -0
  35. package/dist/commands/context.js +185 -0
  36. package/dist/commands/cookies.d.ts +4 -0
  37. package/dist/commands/cookies.d.ts.map +1 -0
  38. package/dist/commands/cookies.js +140 -0
  39. package/dist/commands/docs.d.ts +4 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +137 -0
  42. package/dist/commands/doctor.d.ts +25 -0
  43. package/dist/commands/doctor.d.ts.map +1 -0
  44. package/dist/commands/doctor.js +200 -0
  45. package/dist/commands/edit-batch.d.ts +18 -0
  46. package/dist/commands/edit-batch.d.ts.map +1 -0
  47. package/dist/commands/edit-batch.js +172 -0
  48. package/dist/commands/eml.d.ts +4 -0
  49. package/dist/commands/eml.d.ts.map +1 -0
  50. package/dist/commands/eml.js +428 -0
  51. package/dist/commands/env.d.ts +4 -0
  52. package/dist/commands/env.d.ts.map +1 -0
  53. package/dist/commands/env.js +201 -0
  54. package/dist/commands/fetch.d.ts +4 -0
  55. package/dist/commands/fetch.d.ts.map +1 -0
  56. package/dist/commands/fetch.js +99 -0
  57. package/dist/commands/file-history.d.ts +4 -0
  58. package/dist/commands/file-history.d.ts.map +1 -0
  59. package/dist/commands/file-history.js +152 -0
  60. package/dist/commands/grep.d.ts +4 -0
  61. package/dist/commands/grep.d.ts.map +1 -0
  62. package/dist/commands/grep.js +317 -0
  63. package/dist/commands/init.d.ts +82 -0
  64. package/dist/commands/init.d.ts.map +1 -0
  65. package/dist/commands/init.js +288 -0
  66. package/dist/commands/outline.d.ts +4 -0
  67. package/dist/commands/outline.d.ts.map +1 -0
  68. package/dist/commands/outline.js +494 -0
  69. package/dist/commands/presence.d.ts +12 -0
  70. package/dist/commands/presence.d.ts.map +1 -0
  71. package/dist/commands/presence.js +123 -0
  72. package/dist/commands/read.d.ts +7 -0
  73. package/dist/commands/read.d.ts.map +1 -0
  74. package/dist/commands/read.js +46 -0
  75. package/dist/commands/scratch.d.ts +4 -0
  76. package/dist/commands/scratch.d.ts.map +1 -0
  77. package/dist/commands/scratch.js +426 -0
  78. package/dist/commands/session.d.ts +4 -0
  79. package/dist/commands/session.d.ts.map +1 -0
  80. package/dist/commands/session.js +162 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.d.ts.map +1 -0
  83. package/dist/commands/sync.js +275 -0
  84. package/dist/commands/toc.d.ts +5 -0
  85. package/dist/commands/toc.d.ts.map +1 -0
  86. package/dist/commands/toc.js +153 -0
  87. package/dist/commands/tokens.d.ts +4 -0
  88. package/dist/commands/tokens.d.ts.map +1 -0
  89. package/dist/commands/tokens.js +48 -0
  90. package/dist/commands/tunnel.d.ts +4 -0
  91. package/dist/commands/tunnel.d.ts.map +1 -0
  92. package/dist/commands/tunnel.js +513 -0
  93. package/dist/commands/uninstall.d.ts +22 -0
  94. package/dist/commands/uninstall.d.ts.map +1 -0
  95. package/dist/commands/uninstall.js +126 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +165 -0
  99. package/dist/core/agents/canonical-emit.d.ts +27 -0
  100. package/dist/core/agents/canonical-emit.d.ts.map +1 -0
  101. package/dist/core/agents/canonical-emit.js +72 -0
  102. package/dist/core/agents/cli-emit.d.ts +27 -0
  103. package/dist/core/agents/cli-emit.d.ts.map +1 -0
  104. package/dist/core/agents/cli-emit.js +57 -0
  105. package/dist/core/agents/cli.d.ts +10 -0
  106. package/dist/core/agents/cli.d.ts.map +1 -0
  107. package/dist/core/agents/cli.js +757 -0
  108. package/dist/core/agents/codex-replay.d.ts +29 -0
  109. package/dist/core/agents/codex-replay.d.ts.map +1 -0
  110. package/dist/core/agents/codex-replay.js +138 -0
  111. package/dist/core/agents/coord-client.d.ts +98 -0
  112. package/dist/core/agents/coord-client.d.ts.map +1 -0
  113. package/dist/core/agents/coord-client.js +212 -0
  114. package/dist/core/agents/events/consume.d.ts +59 -0
  115. package/dist/core/agents/events/consume.d.ts.map +1 -0
  116. package/dist/core/agents/events/consume.js +147 -0
  117. package/dist/core/agents/events/emit.d.ts +42 -0
  118. package/dist/core/agents/events/emit.d.ts.map +1 -0
  119. package/dist/core/agents/events/emit.js +70 -0
  120. package/dist/core/agents/events/ulid.d.ts +11 -0
  121. package/dist/core/agents/events/ulid.d.ts.map +1 -0
  122. package/dist/core/agents/events/ulid.js +47 -0
  123. package/dist/core/agents/index.d.ts +14 -0
  124. package/dist/core/agents/index.d.ts.map +1 -0
  125. package/dist/core/agents/index.js +13 -0
  126. package/dist/core/agents/paths.d.ts +6 -0
  127. package/dist/core/agents/paths.d.ts.map +1 -0
  128. package/dist/core/agents/paths.js +17 -0
  129. package/dist/core/agents/render/prompt-context.d.ts +43 -0
  130. package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
  131. package/dist/core/agents/render/prompt-context.js +335 -0
  132. package/dist/core/agents/render/session-context.d.ts +39 -0
  133. package/dist/core/agents/render/session-context.d.ts.map +1 -0
  134. package/dist/core/agents/render/session-context.js +283 -0
  135. package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
  136. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
  137. package/dist/core/agents/rules/claim-conflict.js +244 -0
  138. package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
  139. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
  140. package/dist/core/agents/rules/commit-conflict.js +244 -0
  141. package/dist/core/agents/rules/stop-hook.d.ts +44 -0
  142. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
  143. package/dist/core/agents/rules/stop-hook.js +161 -0
  144. package/dist/core/agents/session-events.d.ts +41 -0
  145. package/dist/core/agents/session-events.d.ts.map +1 -0
  146. package/dist/core/agents/session-events.js +205 -0
  147. package/dist/core/agents/state/activity-log.d.ts +18 -0
  148. package/dist/core/agents/state/activity-log.d.ts.map +1 -0
  149. package/dist/core/agents/state/activity-log.js +34 -0
  150. package/dist/core/agents/state/council.d.ts +39 -0
  151. package/dist/core/agents/state/council.d.ts.map +1 -0
  152. package/dist/core/agents/state/council.js +216 -0
  153. package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
  154. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
  155. package/dist/core/agents/state/heartbeat-projector.js +436 -0
  156. package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
  157. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
  158. package/dist/core/agents/state/heartbeat-writer.js +271 -0
  159. package/dist/core/agents/state/names.d.ts +35 -0
  160. package/dist/core/agents/state/names.d.ts.map +1 -0
  161. package/dist/core/agents/state/names.js +376 -0
  162. package/dist/core/agents/state/pidmap.d.ts +11 -0
  163. package/dist/core/agents/state/pidmap.d.ts.map +1 -0
  164. package/dist/core/agents/state/pidmap.js +32 -0
  165. package/dist/core/agents/state/scratch.d.ts +27 -0
  166. package/dist/core/agents/state/scratch.d.ts.map +1 -0
  167. package/dist/core/agents/state/scratch.js +90 -0
  168. package/dist/core/agents/state/shell-mutation.d.ts +17 -0
  169. package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
  170. package/dist/core/agents/state/shell-mutation.js +41 -0
  171. package/dist/core/agents/state/stale-sweep.d.ts +16 -0
  172. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
  173. package/dist/core/agents/state/stale-sweep.js +166 -0
  174. package/dist/core/config.d.ts +29 -0
  175. package/dist/core/config.d.ts.map +1 -0
  176. package/dist/core/config.js +108 -0
  177. package/dist/core/hooks/cli.d.ts +21 -0
  178. package/dist/core/hooks/cli.d.ts.map +1 -0
  179. package/dist/core/hooks/cli.js +1123 -0
  180. package/dist/core/hooks/effects/image-capture.d.ts +43 -0
  181. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
  182. package/dist/core/hooks/effects/image-capture.js +288 -0
  183. package/dist/core/hooks/effects/index.d.ts +64 -0
  184. package/dist/core/hooks/effects/index.d.ts.map +1 -0
  185. package/dist/core/hooks/effects/index.js +197 -0
  186. package/dist/core/hooks/events/emit.d.ts +31 -0
  187. package/dist/core/hooks/events/emit.d.ts.map +1 -0
  188. package/dist/core/hooks/events/emit.js +89 -0
  189. package/dist/core/hooks/events/schema.d.ts +235 -0
  190. package/dist/core/hooks/events/schema.d.ts.map +1 -0
  191. package/dist/core/hooks/events/schema.js +12 -0
  192. package/dist/core/hooks/events/ulid.d.ts +10 -0
  193. package/dist/core/hooks/events/ulid.d.ts.map +1 -0
  194. package/dist/core/hooks/events/ulid.js +47 -0
  195. package/dist/core/hooks/harness/detect.d.ts +9 -0
  196. package/dist/core/hooks/harness/detect.d.ts.map +1 -0
  197. package/dist/core/hooks/harness/detect.js +29 -0
  198. package/dist/core/hooks/harness/events.d.ts +45 -0
  199. package/dist/core/hooks/harness/events.d.ts.map +1 -0
  200. package/dist/core/hooks/harness/events.js +71 -0
  201. package/dist/core/hooks/harness/output.d.ts +46 -0
  202. package/dist/core/hooks/harness/output.d.ts.map +1 -0
  203. package/dist/core/hooks/harness/output.js +87 -0
  204. package/dist/core/hooks/harness/parse.d.ts +67 -0
  205. package/dist/core/hooks/harness/parse.d.ts.map +1 -0
  206. package/dist/core/hooks/harness/parse.js +132 -0
  207. package/dist/core/hooks/index.d.ts +8 -0
  208. package/dist/core/hooks/index.d.ts.map +1 -0
  209. package/dist/core/hooks/index.js +7 -0
  210. package/dist/core/hooks/resolve/anchor.d.ts +37 -0
  211. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
  212. package/dist/core/hooks/resolve/anchor.js +48 -0
  213. package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
  214. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
  215. package/dist/core/hooks/resolve/coord-root.js +27 -0
  216. package/dist/core/hooks/resolve/intent.d.ts +33 -0
  217. package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
  218. package/dist/core/hooks/resolve/intent.js +79 -0
  219. package/dist/core/hooks/resolve/owner.d.ts +42 -0
  220. package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
  221. package/dist/core/hooks/resolve/owner.js +140 -0
  222. package/dist/core/hooks/resolve/transcript.d.ts +26 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
  224. package/dist/core/hooks/resolve/transcript.js +73 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +13 -0
  228. package/dist/lib/agent-browser/client.d.ts +99 -0
  229. package/dist/lib/agent-browser/client.d.ts.map +1 -0
  230. package/dist/lib/agent-browser/client.js +177 -0
  231. package/dist/lib/agent-browser/index.d.ts +2 -0
  232. package/dist/lib/agent-browser/index.d.ts.map +1 -0
  233. package/dist/lib/agent-browser/index.js +1 -0
  234. package/dist/lib/browser/client.d.ts +193 -0
  235. package/dist/lib/browser/client.d.ts.map +1 -0
  236. package/dist/lib/browser/client.js +325 -0
  237. package/dist/lib/browser/dev-overlay.d.ts +23 -0
  238. package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
  239. package/dist/lib/browser/dev-overlay.js +153 -0
  240. package/dist/lib/browser/index.d.ts +5 -0
  241. package/dist/lib/browser/index.d.ts.map +1 -0
  242. package/dist/lib/browser/index.js +2 -0
  243. package/dist/lib/browser/layout.d.ts +79 -0
  244. package/dist/lib/browser/layout.d.ts.map +1 -0
  245. package/dist/lib/browser/layout.js +220 -0
  246. package/dist/lib/browser/visibility.d.ts +86 -0
  247. package/dist/lib/browser/visibility.d.ts.map +1 -0
  248. package/dist/lib/browser/visibility.js +333 -0
  249. package/dist/lib/browser/visual-diff.d.ts +38 -0
  250. package/dist/lib/browser/visual-diff.d.ts.map +1 -0
  251. package/dist/lib/browser/visual-diff.js +107 -0
  252. package/dist/lib/completion/bash.d.ts +25 -0
  253. package/dist/lib/completion/bash.d.ts.map +1 -0
  254. package/dist/lib/completion/bash.js +284 -0
  255. package/dist/lib/completion/fish.d.ts +16 -0
  256. package/dist/lib/completion/fish.d.ts.map +1 -0
  257. package/dist/lib/completion/fish.js +118 -0
  258. package/dist/lib/completion/index.d.ts +5 -0
  259. package/dist/lib/completion/index.d.ts.map +1 -0
  260. package/dist/lib/completion/index.js +4 -0
  261. package/dist/lib/completion/walk.d.ts +68 -0
  262. package/dist/lib/completion/walk.d.ts.map +1 -0
  263. package/dist/lib/completion/walk.js +102 -0
  264. package/dist/lib/completion/zsh.d.ts +13 -0
  265. package/dist/lib/completion/zsh.d.ts.map +1 -0
  266. package/dist/lib/completion/zsh.js +249 -0
  267. package/dist/lib/context/index.d.ts +107 -0
  268. package/dist/lib/context/index.d.ts.map +1 -0
  269. package/dist/lib/context/index.js +275 -0
  270. package/dist/lib/cookies/client.d.ts +131 -0
  271. package/dist/lib/cookies/client.d.ts.map +1 -0
  272. package/dist/lib/cookies/client.js +239 -0
  273. package/dist/lib/cookies/index.d.ts +2 -0
  274. package/dist/lib/cookies/index.d.ts.map +1 -0
  275. package/dist/lib/cookies/index.js +1 -0
  276. package/dist/lib/council/index.d.ts +266 -0
  277. package/dist/lib/council/index.d.ts.map +1 -0
  278. package/dist/lib/council/index.js +674 -0
  279. package/dist/lib/docs-index.d.ts +28 -0
  280. package/dist/lib/docs-index.d.ts.map +1 -0
  281. package/dist/lib/docs-index.js +169 -0
  282. package/dist/lib/docs-lint.d.ts +26 -0
  283. package/dist/lib/docs-lint.d.ts.map +1 -0
  284. package/dist/lib/docs-lint.js +378 -0
  285. package/dist/lib/docs-sweep.d.ts +34 -0
  286. package/dist/lib/docs-sweep.d.ts.map +1 -0
  287. package/dist/lib/docs-sweep.js +304 -0
  288. package/dist/lib/docs.d.ts +27 -0
  289. package/dist/lib/docs.d.ts.map +1 -0
  290. package/dist/lib/docs.js +142 -0
  291. package/dist/lib/env.d.ts +11 -0
  292. package/dist/lib/env.d.ts.map +1 -0
  293. package/dist/lib/env.js +12 -0
  294. package/dist/lib/exec.d.ts +32 -0
  295. package/dist/lib/exec.d.ts.map +1 -0
  296. package/dist/lib/exec.js +54 -0
  297. package/dist/lib/format.d.ts +29 -0
  298. package/dist/lib/format.d.ts.map +1 -0
  299. package/dist/lib/format.js +139 -0
  300. package/dist/lib/http/client.d.ts +56 -0
  301. package/dist/lib/http/client.d.ts.map +1 -0
  302. package/dist/lib/http/client.js +160 -0
  303. package/dist/lib/http/index.d.ts +2 -0
  304. package/dist/lib/http/index.d.ts.map +1 -0
  305. package/dist/lib/http/index.js +1 -0
  306. package/dist/lib/identities/index.d.ts +77 -0
  307. package/dist/lib/identities/index.d.ts.map +1 -0
  308. package/dist/lib/identities/index.js +190 -0
  309. package/dist/lib/machine.d.ts +19 -0
  310. package/dist/lib/machine.d.ts.map +1 -0
  311. package/dist/lib/machine.js +61 -0
  312. package/dist/lib/presence.d.ts +48 -0
  313. package/dist/lib/presence.d.ts.map +1 -0
  314. package/dist/lib/presence.js +123 -0
  315. package/dist/lib/readability/client.d.ts +32 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +119 -0
  318. package/dist/lib/readability/index.d.ts +2 -0
  319. package/dist/lib/readability/index.d.ts.map +1 -0
  320. package/dist/lib/readability/index.js +1 -0
  321. package/dist/lib/scratch/index.d.ts +74 -0
  322. package/dist/lib/scratch/index.d.ts.map +1 -0
  323. package/dist/lib/scratch/index.js +393 -0
  324. package/dist/lib/tunnel/gate.d.ts +12 -0
  325. package/dist/lib/tunnel/gate.d.ts.map +1 -0
  326. package/dist/lib/tunnel/gate.js +101 -0
  327. package/dist/lib/tunnel/state.d.ts +34 -0
  328. package/dist/lib/tunnel/state.d.ts.map +1 -0
  329. package/dist/lib/tunnel/state.js +132 -0
  330. package/package.json +160 -8
  331. package/schemas/.gitkeep +0 -0
  332. package/schemas/config.schema.json +109 -0
  333. package/src/cli.ts +22 -0
  334. package/src/commander.ts +242 -0
  335. package/src/commands/.gitkeep +0 -0
  336. package/src/commands/agents.ts +4567 -0
  337. package/src/commands/backup.ts +305 -0
  338. package/src/commands/browse-ai.ts +198 -0
  339. package/src/commands/browse.ts +849 -0
  340. package/src/commands/callers.ts +363 -0
  341. package/src/commands/completion.ts +193 -0
  342. package/src/commands/config-get.ts +161 -0
  343. package/src/commands/context.ts +209 -0
  344. package/src/commands/cookies.ts +198 -0
  345. package/src/commands/docs.ts +174 -0
  346. package/src/commands/doctor.ts +231 -0
  347. package/src/commands/edit-batch.ts +233 -0
  348. package/src/commands/eml.ts +519 -0
  349. package/src/commands/env.ts +254 -0
  350. package/src/commands/fetch.ts +136 -0
  351. package/src/commands/file-history.ts +202 -0
  352. package/src/commands/grep.ts +371 -0
  353. package/src/commands/init.ts +335 -0
  354. package/src/commands/outline.ts +564 -0
  355. package/src/commands/presence.ts +152 -0
  356. package/src/commands/read.ts +64 -0
  357. package/src/commands/scratch.ts +445 -0
  358. package/src/commands/session.ts +187 -0
  359. package/src/commands/sync.ts +306 -0
  360. package/src/commands/toc.ts +218 -0
  361. package/src/commands/tokens.ts +79 -0
  362. package/src/commands/tunnel.ts +633 -0
  363. package/src/commands/uninstall.ts +144 -0
  364. package/src/commands/web.ts +193 -0
  365. package/src/core/agents/canonical-emit.ts +77 -0
  366. package/src/core/agents/cli-emit.ts +64 -0
  367. package/src/core/agents/cli.ts +838 -0
  368. package/src/core/agents/codex-replay.ts +163 -0
  369. package/src/core/agents/coord-client.ts +249 -0
  370. package/src/core/agents/events/consume.ts +196 -0
  371. package/src/core/agents/events/emit.ts +108 -0
  372. package/src/core/agents/events/ulid.ts +51 -0
  373. package/src/core/agents/index.ts +14 -0
  374. package/src/core/agents/paths.ts +16 -0
  375. package/src/core/agents/render/prompt-context.ts +401 -0
  376. package/src/core/agents/render/session-context.ts +341 -0
  377. package/src/core/agents/rules/claim-conflict.ts +282 -0
  378. package/src/core/agents/rules/commit-conflict.ts +303 -0
  379. package/src/core/agents/rules/stop-hook.ts +229 -0
  380. package/src/core/agents/session-events.ts +228 -0
  381. package/src/core/agents/state/activity-log.ts +33 -0
  382. package/src/core/agents/state/council.ts +265 -0
  383. package/src/core/agents/state/heartbeat-projector.ts +488 -0
  384. package/src/core/agents/state/heartbeat-writer.ts +333 -0
  385. package/src/core/agents/state/names.ts +399 -0
  386. package/src/core/agents/state/pidmap.ts +38 -0
  387. package/src/core/agents/state/scratch.ts +121 -0
  388. package/src/core/agents/state/shell-mutation.ts +44 -0
  389. package/src/core/agents/state/stale-sweep.ts +190 -0
  390. package/src/core/config.ts +111 -0
  391. package/src/core/hooks/cli.ts +1247 -0
  392. package/src/core/hooks/effects/image-capture.ts +330 -0
  393. package/src/core/hooks/effects/index.ts +210 -0
  394. package/src/core/hooks/events/emit.ts +120 -0
  395. package/src/core/hooks/events/schema.ts +430 -0
  396. package/src/core/hooks/events/ulid.ts +51 -0
  397. package/src/core/hooks/harness/detect.ts +30 -0
  398. package/src/core/hooks/harness/events.ts +102 -0
  399. package/src/core/hooks/harness/output.ts +100 -0
  400. package/src/core/hooks/harness/parse.ts +180 -0
  401. package/src/core/hooks/index.ts +16 -0
  402. package/src/core/hooks/resolve/anchor.ts +51 -0
  403. package/src/core/hooks/resolve/coord-root.ts +25 -0
  404. package/src/core/hooks/resolve/intent.ts +89 -0
  405. package/src/core/hooks/resolve/owner.ts +140 -0
  406. package/src/core/hooks/resolve/transcript.ts +72 -0
  407. package/src/hooks/.gitkeep +0 -0
  408. package/src/index.ts +15 -0
  409. package/src/lib/agent-browser/client.ts +239 -0
  410. package/src/lib/agent-browser/index.ts +1 -0
  411. package/src/lib/browser/client.ts +449 -0
  412. package/src/lib/browser/dev-overlay.ts +207 -0
  413. package/src/lib/browser/index.ts +24 -0
  414. package/src/lib/browser/layout.ts +288 -0
  415. package/src/lib/browser/visibility.ts +419 -0
  416. package/src/lib/browser/visual-diff.ts +150 -0
  417. package/src/lib/completion/bash.ts +291 -0
  418. package/src/lib/completion/fish.ts +134 -0
  419. package/src/lib/completion/index.ts +10 -0
  420. package/src/lib/completion/walk.ts +184 -0
  421. package/src/lib/completion/zsh.ts +262 -0
  422. package/src/lib/context/index.ts +386 -0
  423. package/src/lib/cookies/client.ts +301 -0
  424. package/src/lib/cookies/index.ts +13 -0
  425. package/src/lib/council/index.ts +803 -0
  426. package/src/lib/docs-index.ts +216 -0
  427. package/src/lib/docs-lint.ts +413 -0
  428. package/src/lib/docs-sweep.ts +348 -0
  429. package/src/lib/docs.ts +199 -0
  430. package/src/lib/env.ts +12 -0
  431. package/src/lib/exec.ts +74 -0
  432. package/src/lib/format.ts +147 -0
  433. package/src/lib/http/client.ts +211 -0
  434. package/src/lib/http/index.ts +1 -0
  435. package/src/lib/identities/index.ts +210 -0
  436. package/src/lib/machine.ts +61 -0
  437. package/src/lib/presence.ts +154 -0
  438. package/src/lib/readability/client.ts +156 -0
  439. package/src/lib/readability/index.ts +5 -0
  440. package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
  441. package/src/lib/scratch/index.ts +470 -0
  442. package/src/lib/tunnel/gate.ts +113 -0
  443. package/src/lib/tunnel/state.ts +167 -0
  444. package/src/web/.gitkeep +0 -0
  445. package/index.js +0 -1
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Zsh completion generator. Emits a single `#compdef <bin>` script that uses
3
+ * `_arguments -C` for nested-subcommand parsing and supports descriptions
4
+ * inline (the main zsh-over-bash win). The bin name is injected by the caller.
5
+ *
6
+ * Architecture mirrors bash.ts:
7
+ * - Static tables for subcommands + options keyed by path
8
+ * - `<bin> __complete <provider>` callback for dynamic values
9
+ * - Driver function walks $words to determine the current command path
10
+ */
11
+
12
+ import type { CommandSpec, OptionSpec, PositionalSpec } from "./walk.js";
13
+
14
+ /** Derive a valid shell function-name prefix from the bin name. */
15
+ function fnPrefix(binName: string): string {
16
+ return `_${binName.replace(/[^a-zA-Z0-9_]/g, "_")}`;
17
+ }
18
+
19
+ function valueSpec(opt: { dynamicProvider?: string; valueChoices?: string[] }): string {
20
+ if (opt.dynamicProvider) return `DYN:${opt.dynamicProvider}`;
21
+ if (opt.valueChoices && opt.valueChoices.length > 0) {
22
+ return `ENUM:${opt.valueChoices.join(" ")}`;
23
+ }
24
+ return "";
25
+ }
26
+
27
+ /** Escape a string for single-quoted zsh string. Same rule as bash. */
28
+ function zshEscape(s: string): string {
29
+ return s.replace(/'/g, `'\\''`);
30
+ }
31
+
32
+ function zshPath(path: string): string {
33
+ return path === "" ? "ROOT" : path.replace(/ /g, "__");
34
+ }
35
+
36
+ export function generateZsh(root: CommandSpec, binName: string): string {
37
+ const fn = fnPrefix(binName);
38
+ const subcommandsByPath = new Map<string, Array<{ name: string; description: string }>>();
39
+ const optionsByPath = new Map<string, OptionSpec[]>();
40
+ const positionalsByPath = new Map<string, PositionalSpec[]>();
41
+
42
+ const walk = (s: CommandSpec): void => {
43
+ subcommandsByPath.set(
44
+ s.path,
45
+ s.subcommands
46
+ .map((c) => ({ name: c.name, description: c.description }))
47
+ .sort((a, b) => a.name.localeCompare(b.name)),
48
+ );
49
+ optionsByPath.set(s.path, s.options);
50
+ positionalsByPath.set(s.path, s.positionals);
51
+ for (const sub of s.subcommands) walk(sub);
52
+ };
53
+ walk(root);
54
+
55
+ const out: string[] = [];
56
+ out.push(`#compdef ${binName}`);
57
+ out.push(`# ${binName} zsh completion, generated by \`${binName} completion zsh\`. Do not edit.`);
58
+ out.push(`# Source via: eval "$(${binName} completion zsh)"`);
59
+ out.push("");
60
+
61
+ out.push(`${fn}_subcommands_with_desc() {`);
62
+ out.push(` case "$1" in`);
63
+ for (const [path, subs] of [...subcommandsByPath.entries()].sort()) {
64
+ if (subs.length === 0) continue;
65
+ const lines = subs.map(
66
+ (s) => `'${zshEscape(s.name)}:${zshEscape(truncate(s.description, 80))}'`,
67
+ );
68
+ out.push(` ${zshPath(path)}) print -l ${lines.join(" ")} ;;`);
69
+ }
70
+ out.push(" esac");
71
+ out.push("}");
72
+ out.push("");
73
+
74
+ out.push(`${fn}_options_with_desc() {`);
75
+ out.push(` case "$1" in`);
76
+ for (const [path, opts] of [...optionsByPath.entries()].sort()) {
77
+ if (opts.length === 0) continue;
78
+ const lines: string[] = [];
79
+ for (const o of opts) {
80
+ const flags = [o.long, o.short].filter((f) => f.length > 0);
81
+ for (const flag of flags) {
82
+ lines.push(`'${zshEscape(flag)}:${zshEscape(truncate(o.description, 80))}'`);
83
+ }
84
+ }
85
+ out.push(` ${zshPath(path)}) print -l ${lines.join(" ")} ;;`);
86
+ }
87
+ out.push(" esac");
88
+ out.push("}");
89
+ out.push("");
90
+
91
+ out.push(`${fn}_option_takes_value() {`);
92
+ out.push(` case "$1|$2" in`);
93
+ for (const [path, opts] of [...optionsByPath.entries()].sort()) {
94
+ for (const o of opts) {
95
+ if (!o.takesValue) continue;
96
+ for (const flag of [o.long, o.short].filter((f) => f.length > 0)) {
97
+ out.push(` ${zshPath(path)}\\|${zshEscape(flag)}) echo 1 ;;`);
98
+ }
99
+ }
100
+ }
101
+ out.push(" esac");
102
+ out.push("}");
103
+ out.push("");
104
+
105
+ out.push(`${fn}_option_value_spec() {`);
106
+ out.push(` case "$1|$2" in`);
107
+ for (const [path, opts] of [...optionsByPath.entries()].sort()) {
108
+ for (const o of opts) {
109
+ if (!o.takesValue) continue;
110
+ const spec = valueSpec(o);
111
+ if (!spec) continue;
112
+ for (const flag of [o.long, o.short].filter((f) => f.length > 0)) {
113
+ out.push(` ${zshPath(path)}\\|${zshEscape(flag)}) echo '${zshEscape(spec)}' ;;`);
114
+ }
115
+ }
116
+ }
117
+ out.push(" esac");
118
+ out.push("}");
119
+ out.push("");
120
+
121
+ out.push(`${fn}_positional_value_spec() {`);
122
+ out.push(` case "$1|$2" in`);
123
+ for (const [path, positionals] of [...positionalsByPath.entries()].sort()) {
124
+ positionals.forEach((p, i) => {
125
+ const spec = valueSpec(p);
126
+ if (!spec) return;
127
+ out.push(` ${zshPath(path)}\\|${i}) echo '${zshEscape(spec)}' ;;`);
128
+ });
129
+ }
130
+ out.push(" esac");
131
+ out.push("}");
132
+ out.push("");
133
+
134
+ out.push(zshDriver(fn, binName));
135
+ out.push("");
136
+ out.push(`compdef ${fn} ${binName}`);
137
+ return `${out.join("\n")}\n`;
138
+ }
139
+
140
+ function truncate(s: string, n: number): string {
141
+ if (!s) return "";
142
+ return s.length > n ? `${s.slice(0, n - 1)}…` : s;
143
+ }
144
+
145
+ /**
146
+ * The driver walks `$words` to determine the current command path, then
147
+ * dispatches to subcommand / option / value completion. `fn` is the
148
+ * function-name prefix; `binName` is the CLI name for the `__complete` callback.
149
+ */
150
+ function zshDriver(fn: string, binName: string): string {
151
+ return `${fn}() {
152
+ local cur prev path_str path_parts
153
+ local -a words
154
+ words=( "\${(@)words}" )
155
+ local cword=$CURRENT
156
+ cur="\${words[$cword]}"
157
+ prev="\${words[$((cword-1))]}"
158
+
159
+ # Walk words[2..cword-1] (zsh is 1-indexed; words[1] is the bin) to determine path.
160
+ path_str="ROOT"
161
+ local -a path_parts
162
+ local i=2
163
+ while [ $i -lt $cword ]; do
164
+ local w="\${words[$i]}"
165
+ case "$w" in
166
+ -*)
167
+ if [ "$(${fn}_option_takes_value "$path_str" "$w")" = "1" ]; then
168
+ i=$((i+1))
169
+ fi
170
+ ;;
171
+ *)
172
+ path_parts+=("$w")
173
+ local joined="\${(j:__:)path_parts}"
174
+ local known
175
+ known=$(${fn}_subcommands_with_desc "$path_str" | awk -F: '{print $1}' | tr -d "'")
176
+ if [[ " $known " == *" $w "* ]]; then
177
+ path_str="$joined"
178
+ fi
179
+ ;;
180
+ esac
181
+ i=$((i+1))
182
+ done
183
+
184
+ # Case 1: prev is an option that takes a value.
185
+ if [ -n "$prev" ] && [ "$(${fn}_option_takes_value "$path_str" "$prev")" = "1" ]; then
186
+ local spec=$(${fn}_option_value_spec "$path_str" "$prev")
187
+ ${fn}_emit_values "$spec" "$cur"
188
+ return
189
+ fi
190
+
191
+ # Case 2: completing an option (cur starts with -).
192
+ if [[ "$cur" == -* ]]; then
193
+ local -a opt_descs
194
+ opt_descs=( \${(f)"$(${fn}_options_with_desc "$path_str")"} )
195
+ if [ \${#opt_descs[@]} -gt 0 ]; then
196
+ _describe 'options' opt_descs
197
+ fi
198
+ return
199
+ fi
200
+
201
+ # Case 3: subcommands at this level.
202
+ local -a sub_descs
203
+ sub_descs=( \${(f)"$(${fn}_subcommands_with_desc "$path_str")"} )
204
+ if [ \${#sub_descs[@]} -gt 0 ]; then
205
+ _describe 'subcommands' sub_descs
206
+ return
207
+ fi
208
+
209
+ # Case 4: positional value. Count non-option, non-subcommand words after path.
210
+ local after_cmd=0
211
+ local seen_path="ROOT"
212
+ local j=2
213
+ while [ $j -lt $cword ]; do
214
+ local w="\${words[$j]}"
215
+ case "$w" in
216
+ -*)
217
+ if [ "$(${fn}_option_takes_value "$seen_path" "$w")" = "1" ]; then
218
+ j=$((j+1))
219
+ fi
220
+ ;;
221
+ *)
222
+ local joined
223
+ if [ "$seen_path" = "ROOT" ]; then joined="$w"; else joined="\${seen_path}__\${w}"; fi
224
+ local known
225
+ known=$(${fn}_subcommands_with_desc "$seen_path" | awk -F: '{print $1}' | tr -d "'")
226
+ if [[ " $known " == *" $w "* ]]; then
227
+ seen_path="$joined"
228
+ else
229
+ after_cmd=$((after_cmd+1))
230
+ fi
231
+ ;;
232
+ esac
233
+ j=$((j+1))
234
+ done
235
+
236
+ local pos_spec=$(${fn}_positional_value_spec "$path_str" "$after_cmd")
237
+ if [ -n "$pos_spec" ]; then
238
+ ${fn}_emit_values "$pos_spec" "$cur"
239
+ return
240
+ fi
241
+
242
+ _files
243
+ }
244
+
245
+ ${fn}_emit_values() {
246
+ local spec="$1"
247
+ local cur="$2"
248
+ if [[ "$spec" == DYN:* ]]; then
249
+ local provider="\${spec#DYN:}"
250
+ local -a values
251
+ values=( \${(f)"$(${binName} __complete "$provider" -- "$cur" 2>/dev/null)"} )
252
+ if [ \${#values[@]} -gt 0 ]; then
253
+ compadd -a values
254
+ fi
255
+ elif [[ "$spec" == ENUM:* ]]; then
256
+ local choices="\${spec#ENUM:}"
257
+ local -a values
258
+ values=( \${(s: :)choices} )
259
+ compadd -a values
260
+ fi
261
+ }`;
262
+ }
@@ -0,0 +1,386 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ type Heartbeat,
5
+ monorepoRoot,
6
+ readHeartbeat,
7
+ resolveOwner,
8
+ } from "../../core/agents/index.ts";
9
+ import { exec } from "../exec.ts";
10
+ import { NO_DATA } from "../format.ts";
11
+
12
+ interface SubmoduleStatus {
13
+ name: string;
14
+ branch: string;
15
+ ahead: number;
16
+ behind: number;
17
+ dirty: boolean;
18
+ modifiedFiles: number;
19
+ untrackedFiles: number;
20
+ }
21
+
22
+ /**
23
+ * `harn context`: one-shot orientation snapshot for session start / post-compaction
24
+ * recovery / subagent dispatch. Aggregates self / time / repo / commits / submodules
25
+ * / peers, plus opt-in services. Fail-open per section so a missing dependency
26
+ * never blocks the rest from rendering.
27
+ *
28
+ * No process spawns for default sections except git; every other source is
29
+ * filesystem-direct via existing lib helpers.
30
+ */
31
+
32
+ export type SectionName =
33
+ | "self"
34
+ | "time"
35
+ | "repo"
36
+ | "commits"
37
+ | "submodules"
38
+ | "peers"
39
+ | "services";
40
+
41
+ export const DEFAULT_SECTIONS: SectionName[] = [
42
+ "self",
43
+ "time",
44
+ "repo",
45
+ "commits",
46
+ "submodules",
47
+ "peers",
48
+ ];
49
+
50
+ export const OPT_IN_SECTIONS: SectionName[] = ["services"];
51
+
52
+ export interface ContextOptions {
53
+ sections?: SectionName[];
54
+ include?: SectionName[];
55
+ showClean?: boolean;
56
+ /** Monorepo root for git ops. Defaults to process.cwd() when omitted. */
57
+ repoRoot?: string;
58
+ /** Submodule names relative to repoRoot. When omitted, submodules section reports an error. */
59
+ submodules?: readonly string[];
60
+ }
61
+
62
+ export interface ContextReport {
63
+ self?: SelfSection | { error: string };
64
+ time?: TimeSection;
65
+ repo?: RepoSection | { error: string };
66
+ commits?: CommitsSection | { error: string };
67
+ submodules?: SubmodulesSection | { error: string };
68
+ peers?: PeersSection | { error: string };
69
+ services?: ServicesSection | { error: string };
70
+ meta: {
71
+ elapsed_ms: number;
72
+ sections_requested: SectionName[];
73
+ };
74
+ }
75
+
76
+ export interface SelfSection {
77
+ name: string | null;
78
+ instance_id: string;
79
+ session_age_secs: number;
80
+ task: string | null;
81
+ last_tool: string | null;
82
+ last_tool_target: string | null;
83
+ files_held: string[];
84
+ }
85
+
86
+ export interface TimeSection {
87
+ chicago: string;
88
+ utc: string;
89
+ }
90
+
91
+ export interface RepoSection {
92
+ cwd: string;
93
+ branch: string;
94
+ ahead: number;
95
+ behind: number;
96
+ modified: number;
97
+ untracked: number;
98
+ staged: number;
99
+ }
100
+
101
+ export interface CommitsSection {
102
+ rows: { sha: string; subject: string }[];
103
+ }
104
+
105
+ export interface SubmodulesSection {
106
+ rows: SubmoduleStatus[];
107
+ clean_omitted: number;
108
+ }
109
+
110
+ export interface PeersSection {
111
+ rows: {
112
+ name: string;
113
+ instance_id_short: string;
114
+ age_min: number;
115
+ files: number;
116
+ last_tool: string | null;
117
+ task: string | null;
118
+ }[];
119
+ }
120
+
121
+ export interface ServicesSection {
122
+ docker_compose: { project: string; service: string; status: string }[];
123
+ }
124
+
125
+ export async function buildContext(opts: ContextOptions = {}): Promise<ContextReport> {
126
+ const started = Date.now();
127
+ const requested = new Set<SectionName>(opts.sections ?? DEFAULT_SECTIONS);
128
+ for (const s of opts.include ?? []) requested.add(s);
129
+
130
+ const report: ContextReport = {
131
+ meta: {
132
+ elapsed_ms: 0,
133
+ sections_requested: Array.from(requested),
134
+ },
135
+ };
136
+
137
+ const repoRoot = opts.repoRoot ?? process.cwd();
138
+ const submodules = opts.submodules ?? null;
139
+ if (requested.has("self")) report.self = await safe(() => buildSelf());
140
+ if (requested.has("time")) report.time = buildTime();
141
+ if (requested.has("repo")) report.repo = await safe(() => buildRepo(repoRoot));
142
+ if (requested.has("commits")) report.commits = await safe(() => buildCommits(repoRoot));
143
+ if (requested.has("submodules")) {
144
+ report.submodules = await safe(() => buildSubmodules(!!opts.showClean, repoRoot, submodules));
145
+ }
146
+ if (requested.has("peers")) report.peers = await safe(() => buildPeers());
147
+ if (requested.has("services")) report.services = await safe(() => buildServices(repoRoot));
148
+
149
+ report.meta.elapsed_ms = Date.now() - started;
150
+ return report;
151
+ }
152
+
153
+ // ─── Sections ──────────────────────────────────────────────────────────────
154
+
155
+ function buildSelf(): SelfSection {
156
+ const owner = resolveOwner();
157
+ if (!owner) throw new Error("not in an agent session (no pid-map entry)");
158
+ const hb = readHeartbeat(owner);
159
+ if (!hb) throw new Error(`pid-map resolved owner=${owner.slice(0, 8)}… but no heartbeat`);
160
+ const startedMs = Date.parse(hb.started_at);
161
+ const ageSecs = Number.isFinite(startedMs)
162
+ ? Math.max(0, Math.floor((Date.now() - startedMs) / 1000))
163
+ : 0;
164
+ return {
165
+ name: hb.name ?? null,
166
+ instance_id: hb.instance_id,
167
+ session_age_secs: ageSecs,
168
+ task: hb.task ?? null,
169
+ last_tool: hb.last_tool ?? null,
170
+ last_tool_target: hb.last_tool_target ?? null,
171
+ files_held: hb.files_touched ?? [],
172
+ };
173
+ }
174
+
175
+ function buildTime(): TimeSection {
176
+ const now = new Date();
177
+ return {
178
+ chicago: formatChicago(now),
179
+ utc: now.toISOString(),
180
+ };
181
+ }
182
+
183
+ async function buildRepo(repoRoot: string): Promise<RepoSection> {
184
+ const [branchResult, statusResult, aheadBehindResult] = await Promise.all([
185
+ exec(["git", "rev-parse", "--abbrev-ref", "HEAD"], { cwd: repoRoot }),
186
+ // trim:false, since porcelain's first line of ` M PATH` would lose its leading
187
+ // space and shift the X/Y status columns, miscounting staged vs modified.
188
+ exec(["git", "status", "--porcelain"], { cwd: repoRoot, trim: false }),
189
+ exec(["git", "rev-list", "--left-right", "--count", "HEAD...@{upstream}"], { cwd: repoRoot }),
190
+ ]);
191
+ const branch = branchResult.stdout || "detached";
192
+ const statusLines = statusResult.stdout ? statusResult.stdout.split("\n") : [];
193
+ let modified = 0;
194
+ let untracked = 0;
195
+ let staged = 0;
196
+ for (const line of statusLines) {
197
+ if (line.startsWith("??")) untracked++;
198
+ else if (line.length >= 2) {
199
+ const x = line[0];
200
+ const y = line[1];
201
+ if (x !== " " && x !== "?") staged++;
202
+ if (y !== " " && y !== "?") modified++;
203
+ }
204
+ }
205
+ let ahead = 0;
206
+ let behind = 0;
207
+ if (aheadBehindResult.exitCode === 0) {
208
+ const parts = aheadBehindResult.stdout.split(/\s+/);
209
+ ahead = Number.parseInt(parts[0] ?? "0", 10);
210
+ behind = Number.parseInt(parts[1] ?? "0", 10);
211
+ }
212
+ return {
213
+ cwd: process.cwd(),
214
+ branch,
215
+ ahead,
216
+ behind,
217
+ modified,
218
+ untracked,
219
+ staged,
220
+ };
221
+ }
222
+
223
+ async function buildCommits(repoRoot: string): Promise<CommitsSection> {
224
+ const result = await exec(["git", "log", "--oneline", "-3", "--no-decorate"], { cwd: repoRoot });
225
+ const rows = result.stdout
226
+ .split("\n")
227
+ .filter((l) => l.length > 0)
228
+ .map((l) => {
229
+ const idx = l.indexOf(" ");
230
+ if (idx < 0) return { sha: l, subject: "" };
231
+ return { sha: l.slice(0, idx), subject: l.slice(idx + 1) };
232
+ });
233
+ return { rows };
234
+ }
235
+
236
+ async function buildSubmodules(
237
+ showClean: boolean,
238
+ repoRoot: string,
239
+ submodules: readonly string[] | null,
240
+ ): Promise<SubmodulesSection> {
241
+ if (!submodules || submodules.length === 0) {
242
+ throw new Error("no submodules configured (pass submodules via HarneryProgramContext)");
243
+ }
244
+ const all = await Promise.all(
245
+ submodules.map(async (name) => statusFor(name, resolve(repoRoot, name))),
246
+ );
247
+ const rows = showClean ? all : all.filter((s) => s.dirty || s.ahead > 0 || s.behind > 0);
248
+ const cleanOmitted = showClean ? 0 : all.length - rows.length;
249
+ return { rows, clean_omitted: cleanOmitted };
250
+ }
251
+
252
+ async function statusFor(name: string, cwd: string): Promise<SubmoduleStatus> {
253
+ const empty: SubmoduleStatus = {
254
+ name,
255
+ branch: NO_DATA,
256
+ ahead: 0,
257
+ behind: 0,
258
+ dirty: false,
259
+ modifiedFiles: 0,
260
+ untrackedFiles: 0,
261
+ };
262
+ if (!existsSync(cwd)) return empty;
263
+ const [branchR, statusR, abR] = await Promise.all([
264
+ exec(["git", "rev-parse", "--abbrev-ref", "HEAD"], { cwd }),
265
+ exec(["git", "status", "--porcelain"], { cwd, trim: false }),
266
+ exec(["git", "rev-list", "--left-right", "--count", "HEAD...@{upstream}"], { cwd }),
267
+ ]);
268
+ let modified = 0;
269
+ let untracked = 0;
270
+ for (const line of statusR.stdout.split("\n")) {
271
+ if (line.startsWith("??")) untracked++;
272
+ else if (line.length >= 2) modified++;
273
+ }
274
+ const [aheadS, behindS] = abR.stdout.split("\t");
275
+ return {
276
+ name,
277
+ branch: branchR.stdout || "detached",
278
+ ahead: Number.parseInt(aheadS ?? "0", 10) || 0,
279
+ behind: Number.parseInt(behindS ?? "0", 10) || 0,
280
+ dirty: modified + untracked > 0,
281
+ modifiedFiles: modified,
282
+ untrackedFiles: untracked,
283
+ };
284
+ }
285
+
286
+ function buildPeers(): PeersSection {
287
+ const root = monorepoRoot();
288
+ if (!root) throw new Error("coord_root() returned null");
289
+ const activeDir = resolve(root, ".harnery", "active");
290
+ if (!existsSync(activeDir)) return { rows: [] };
291
+ const myOwner = resolveOwner();
292
+ // No freshness filter; matches the SessionStart/UserPromptSubmit snapshot.
293
+ // Stale entries get swept by the SessionStart janitor on the next session start.
294
+ const rows: PeersSection["rows"] = [];
295
+ for (const f of readdirSync(activeDir)) {
296
+ if (!f.endsWith(".json")) continue;
297
+ try {
298
+ const hb = JSON.parse(readFileSync(resolve(activeDir, f), "utf8")) as Heartbeat;
299
+ if (!hb || typeof hb.instance_id !== "string") continue;
300
+ if (hb.instance_id === myOwner) continue;
301
+ if ((hb.kind ?? "") === "transient") continue;
302
+ const startedMs = Date.parse(hb.started_at);
303
+ const ageMin = Number.isFinite(startedMs) ? Math.floor((Date.now() - startedMs) / 60000) : 0;
304
+ rows.push({
305
+ name: hb.name ?? "unknown",
306
+ instance_id_short: hb.instance_id.slice(0, 8),
307
+ age_min: ageMin,
308
+ files: hb.files_touched?.length ?? 0,
309
+ last_tool: hb.last_tool ?? null,
310
+ task: hb.task ?? null,
311
+ });
312
+ } catch {
313
+ // skip
314
+ }
315
+ }
316
+ // Sort: file-holders first (more activity), then by recency (younger age first).
317
+ rows.sort((a, b) => b.files - a.files || a.age_min - b.age_min);
318
+ return { rows };
319
+ }
320
+
321
+ async function buildServices(repoRoot: string): Promise<ServicesSection> {
322
+ // Probe common compose-file locations at the repo root.
323
+ const candidates = [
324
+ resolve(repoRoot, "docker-compose.yml"),
325
+ resolve(repoRoot, "compose.yml"),
326
+ resolve(repoRoot, "compose.yaml"),
327
+ ];
328
+ const composePath = candidates.find((p) => existsSync(p));
329
+ if (!composePath) {
330
+ return { docker_compose: [] };
331
+ }
332
+ const result = await exec(["docker", "compose", "-f", composePath, "ps", "--format", "json"], {
333
+ cwd: repoRoot,
334
+ timeout: 5000,
335
+ });
336
+ if (result.exitCode !== 0) {
337
+ throw new Error(result.stderr || `docker compose ps exited ${result.exitCode}`);
338
+ }
339
+ // `docker compose ps --format json` emits one JSON object per line.
340
+ const items: ServicesSection["docker_compose"] = [];
341
+ for (const line of result.stdout.split("\n")) {
342
+ if (!line.trim()) continue;
343
+ try {
344
+ const obj = JSON.parse(line) as {
345
+ Project?: string;
346
+ Service?: string;
347
+ State?: string;
348
+ Status?: string;
349
+ };
350
+ items.push({
351
+ project: obj.Project ?? "",
352
+ service: obj.Service ?? "",
353
+ status: obj.Status ?? obj.State ?? "",
354
+ });
355
+ } catch {
356
+ // tolerate non-JSON banner lines
357
+ }
358
+ }
359
+ return { docker_compose: items };
360
+ }
361
+
362
+ // ─── Helpers ───────────────────────────────────────────────────────────────
363
+
364
+ async function safe<T>(fn: () => T | Promise<T>): Promise<T | { error: string }> {
365
+ try {
366
+ return await fn();
367
+ } catch (err) {
368
+ return { error: (err as Error).message };
369
+ }
370
+ }
371
+
372
+ const CHICAGO_FORMATTER = new Intl.DateTimeFormat("en-US", {
373
+ weekday: "short",
374
+ year: "numeric",
375
+ month: "short",
376
+ day: "numeric",
377
+ hour: "numeric",
378
+ minute: "2-digit",
379
+ timeZoneName: "short",
380
+ hour12: true,
381
+ timeZone: "America/Chicago",
382
+ });
383
+
384
+ function formatChicago(d: Date): string {
385
+ return CHICAGO_FORMATTER.format(d);
386
+ }