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,254 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import type { Command } from "commander";
4
+ import type { EmitContext, HarneryProgramContext } from "../commander.ts";
5
+ import { exec, sh } from "../lib/exec.ts";
6
+
7
+ /**
8
+ * `env`: show environment status across runtimes, docker, gcp, bq, git.
9
+ *
10
+ * Monorepo state (repoRoot, submodules) flows in via HarneryProgramContext.
11
+ * When neither is provided (harn invoked outside a monorepo), the git section
12
+ * reports just the current branch and skips the submodule row.
13
+ *
14
+ * Color hints are intentionally string labels (`"ok"`/`"missing"`/...) rather
15
+ * than ANSI-wrapper functions, so consumer adapters can consume them as
16
+ * metadata; defaultEmit just JSON-stringifies.
17
+ */
18
+
19
+ interface Check {
20
+ label: string;
21
+ value: string;
22
+ status?: "ok" | "missing" | "warn" | "info";
23
+ }
24
+
25
+ export function registerEnvCommand(
26
+ program: Command,
27
+ emit: EmitContext,
28
+ context?: HarneryProgramContext,
29
+ ): void {
30
+ program
31
+ .command("env [section]")
32
+ .description("Show environment status (docker, gcp, bq, node, python, git)")
33
+ .action(async (section?: string) => {
34
+ try {
35
+ await handleEnv(section, emit, context);
36
+ } catch (err: unknown) {
37
+ const msg = err instanceof Error ? err.message : String(err);
38
+ emit.error({ code: "env_error", message: msg });
39
+ process.exit(1);
40
+ }
41
+ });
42
+ }
43
+
44
+ async function handleEnv(
45
+ section: string | undefined,
46
+ emit: EmitContext,
47
+ context: HarneryProgramContext | undefined,
48
+ ): Promise<void> {
49
+ const sections: Record<string, () => Promise<Check[]>> = {
50
+ runtimes: checkRuntimes,
51
+ docker: checkDocker,
52
+ gcp: checkGcp,
53
+ bq: checkBigQuery,
54
+ git: () => checkGit(context),
55
+ };
56
+
57
+ if (section) {
58
+ const fn = sections[section];
59
+ if (!fn) {
60
+ emit.error({
61
+ code: "unknown_section",
62
+ message: `Unknown section "${section}". Valid: ${Object.keys(sections).join(", ")}`,
63
+ });
64
+ process.exit(1);
65
+ }
66
+ const checks = await fn();
67
+ emit.data({ ok: true, section, checks: checks.map(toRow) });
68
+ return;
69
+ }
70
+
71
+ const results = await Promise.all(
72
+ Object.entries(sections).map(async ([name, fn]) => ({
73
+ name,
74
+ checks: await fn().catch(() => [
75
+ { label: name, value: "check failed", status: "missing" as const },
76
+ ]),
77
+ })),
78
+ );
79
+
80
+ emit.data({
81
+ ok: true,
82
+ sections: Object.fromEntries(results.map(({ name, checks }) => [name, checks.map(toRow)])),
83
+ });
84
+ }
85
+
86
+ function toRow(check: Check): { label: string; value: string } {
87
+ return { label: check.label, value: check.value };
88
+ }
89
+
90
+ async function checkRuntimes(): Promise<Check[]> {
91
+ const checks: Check[] = [];
92
+
93
+ const [node, bun, python, php] = await Promise.all([
94
+ exec(["node", "--version"]).catch(() => ({ stdout: "", exitCode: 1, stderr: "" })),
95
+ exec(["bun", "--version"]).catch(() => ({ stdout: "", exitCode: 1, stderr: "" })),
96
+ exec(["python3", "--version"]).catch(() => ({ stdout: "", exitCode: 1, stderr: "" })),
97
+ exec(["php", "--version"]).catch(() => ({ stdout: "", exitCode: 1, stderr: "" })),
98
+ ]);
99
+
100
+ if (node.exitCode === 0) checks.push({ label: "Node.js", value: node.stdout, status: "ok" });
101
+ else checks.push({ label: "Node.js", value: "not found", status: "missing" });
102
+
103
+ if (bun.exitCode === 0) checks.push({ label: "Bun", value: bun.stdout, status: "ok" });
104
+ else checks.push({ label: "Bun", value: "not found", status: "missing" });
105
+
106
+ if (python.exitCode === 0)
107
+ checks.push({ label: "Python", value: python.stdout.replace("Python ", ""), status: "ok" });
108
+ else checks.push({ label: "Python", value: "not found", status: "missing" });
109
+
110
+ if (php.exitCode === 0) {
111
+ const ver = php.stdout.split("\n")[0]?.match(/PHP (\S+)/)?.[1] ?? php.stdout;
112
+ checks.push({ label: "PHP", value: ver, status: "ok" });
113
+ } else {
114
+ checks.push({ label: "PHP", value: "not found", status: "info" });
115
+ }
116
+
117
+ return checks;
118
+ }
119
+
120
+ async function checkDocker(): Promise<Check[]> {
121
+ const checks: Check[] = [];
122
+
123
+ const dockerVersion = await exec(["docker", "--version"]).catch(() => ({
124
+ stdout: "",
125
+ exitCode: 1,
126
+ stderr: "",
127
+ }));
128
+
129
+ if (dockerVersion.exitCode !== 0) {
130
+ return [{ label: "Docker", value: "not installed", status: "missing" }];
131
+ }
132
+
133
+ const ver = dockerVersion.stdout.match(/Docker version (\S+)/)?.[1] ?? dockerVersion.stdout;
134
+ checks.push({ label: "Docker", value: ver, status: "ok" });
135
+
136
+ const ps = await sh('docker ps --format "{{.Names}}" 2>/dev/null').catch(() => ({
137
+ stdout: "",
138
+ exitCode: 1,
139
+ stderr: "",
140
+ }));
141
+
142
+ if (ps.exitCode === 0) {
143
+ const names = ps.stdout.split("\n").filter(Boolean);
144
+ checks.push({
145
+ label: "Containers",
146
+ value: names.length > 0 ? `${names.length} running` : "none running",
147
+ status: names.length > 0 ? "ok" : "info",
148
+ });
149
+ } else {
150
+ checks.push({ label: "Containers", value: "docker ps unavailable", status: "info" });
151
+ }
152
+
153
+ return checks;
154
+ }
155
+
156
+ async function checkGcp(): Promise<Check[]> {
157
+ const checks: Check[] = [];
158
+
159
+ const account = await sh("gcloud config get-value account 2>/dev/null").catch(() => ({
160
+ stdout: "",
161
+ exitCode: 1,
162
+ stderr: "",
163
+ }));
164
+ const project = await sh("gcloud config get-value project 2>/dev/null").catch(() => ({
165
+ stdout: "",
166
+ exitCode: 1,
167
+ stderr: "",
168
+ }));
169
+
170
+ if (account.exitCode === 0 && account.stdout) {
171
+ checks.push({ label: "GCP Account", value: account.stdout, status: "ok" });
172
+ } else {
173
+ checks.push({ label: "GCP Account", value: "not authenticated", status: "missing" });
174
+ }
175
+
176
+ if (project.exitCode === 0 && project.stdout) {
177
+ checks.push({ label: "GCP Project", value: project.stdout, status: "ok" });
178
+ } else {
179
+ checks.push({ label: "GCP Project", value: "not set", status: "missing" });
180
+ }
181
+
182
+ return checks;
183
+ }
184
+
185
+ async function checkBigQuery(): Promise<Check[]> {
186
+ const checks: Check[] = [];
187
+
188
+ const result = await sh("bq ls --max_results=1 2>/dev/null").catch(() => ({
189
+ stdout: "",
190
+ exitCode: 1,
191
+ stderr: "",
192
+ }));
193
+
194
+ if (result.exitCode === 0) {
195
+ checks.push({ label: "BigQuery", value: "connected", status: "ok" });
196
+ } else {
197
+ checks.push({ label: "BigQuery", value: "not connected (check GCP auth)", status: "missing" });
198
+ }
199
+
200
+ const datasets = await sh("bq ls --format=json --max_results=20 2>/dev/null").catch(() => ({
201
+ stdout: "",
202
+ exitCode: 1,
203
+ stderr: "",
204
+ }));
205
+
206
+ if (datasets.exitCode === 0 && datasets.stdout) {
207
+ try {
208
+ const ds = JSON.parse(datasets.stdout) as { datasetReference?: { datasetId?: string } }[];
209
+ const names = ds
210
+ .map((d) => d.datasetReference?.datasetId)
211
+ .filter(Boolean)
212
+ .join(", ");
213
+ if (names) checks.push({ label: "Datasets", value: names, status: "info" });
214
+ } catch {
215
+ // Ignore parse failures
216
+ }
217
+ }
218
+
219
+ return checks;
220
+ }
221
+
222
+ async function checkGit(context: HarneryProgramContext | undefined): Promise<Check[]> {
223
+ const checks: Check[] = [];
224
+ const cwd = context?.repoRoot ?? process.cwd();
225
+
226
+ const branch = await exec(["git", "rev-parse", "--abbrev-ref", "HEAD"], { cwd });
227
+ checks.push({ label: "Branch", value: branch.stdout, status: "info" });
228
+
229
+ if (context?.repoRoot && context.submodules && context.submodules.length > 0) {
230
+ const initialized = context.submodules.filter((name) =>
231
+ isSubmoduleInitialized(resolve(context.repoRoot as string, name)),
232
+ );
233
+ const total = context.submodules.length;
234
+ checks.push({
235
+ label: "Submodules",
236
+ value: `${initialized.length}/${total} initialized`,
237
+ status: initialized.length === total ? "ok" : "warn",
238
+ });
239
+ }
240
+
241
+ return checks;
242
+ }
243
+
244
+ function isSubmoduleInitialized(dir: string): boolean {
245
+ if (!existsSync(dir)) return false;
246
+ try {
247
+ // readdirSync lists every entry (dotfiles included, `.`/`..` excluded):
248
+ // the non-recursive "is this dir non-empty?" check this needs, on both
249
+ // Bun and Node (replaces the Bun-only `new Bun.Glob("*").scanSync`).
250
+ return readdirSync(dir).length > 0;
251
+ } catch {
252
+ return false;
253
+ }
254
+ }
@@ -0,0 +1,136 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { resolve } from "node:path";
4
+ import type { Command } from "commander";
5
+ import type { EmitContext, HarneryProgramContext } from "../commander.ts";
6
+ import { CookieJar } from "../lib/cookies/index.ts";
7
+ import { fetchWithJar } from "../lib/http/index.ts";
8
+
9
+ /**
10
+ * `harn fetch`: HTTP request with cookie-jar attach + persist.
11
+ *
12
+ * Default jar is the same one `harn cookies` reads/writes
13
+ * (`~/.cache/harnery/cookies.json`), so cookies set during a `harn browse`
14
+ * session flow naturally to subsequent `harn fetch` calls.
15
+ */
16
+
17
+ const DEFAULT_STORE = resolve(homedir(), ".cache", "harnery", "cookies.json");
18
+
19
+ interface FetchOpts {
20
+ method: string;
21
+ header?: string[];
22
+ data?: string;
23
+ output?: string;
24
+ store?: string;
25
+ cookies?: boolean;
26
+ redirect: string;
27
+ timeout: string;
28
+ status?: boolean;
29
+ headers?: boolean;
30
+ json?: boolean;
31
+ }
32
+
33
+ export function registerFetchCommand(
34
+ program: Command,
35
+ emit: EmitContext,
36
+ context?: HarneryProgramContext,
37
+ ): void {
38
+ program
39
+ .command("fetch <url>")
40
+ .description(
41
+ "HTTP GET (or --method) with cookie-jar attach + persist. " +
42
+ "Default jar is ~/.cache/harnery/cookies.json (shared with harn cookies / harn browse).",
43
+ )
44
+ .option("-X, --method <method>", "HTTP method", "GET")
45
+ .option("-H, --header <header...>", "Extra request header (repeatable, format: 'Name: value')")
46
+ .option("-d, --data <body>", "Request body")
47
+ .option("-o, --output <file>", "Write response body to file (default: stdout)")
48
+ .option("--store <path>", `Cookie store path (default ${DEFAULT_STORE})`)
49
+ .option("--no-cookies", "Skip cookie-jar attach + persist")
50
+ .option("--redirect <mode>", "Redirect handling: follow | error | manual", "follow")
51
+ .option("--timeout <ms>", "Request timeout in milliseconds", "30000")
52
+ .option("--status", "Print status to stderr")
53
+ .option("--headers", "Print response headers to stderr")
54
+ .option("--json", "Output the full FetchResult (status, headers, body) as JSON")
55
+ .action(async (url: string, opts: FetchOpts) => {
56
+ try {
57
+ await runFetch(url, opts, emit, context);
58
+ } catch (err: unknown) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ emit.error({ code: "fetch_error", message: msg });
61
+ process.exit(1);
62
+ }
63
+ });
64
+ }
65
+
66
+ async function runFetch(
67
+ url: string,
68
+ opts: FetchOpts,
69
+ emit: EmitContext,
70
+ context: HarneryProgramContext | undefined,
71
+ ): Promise<void> {
72
+ const headers: Record<string, string> = {};
73
+ for (const h of opts.header ?? []) {
74
+ const idx = h.indexOf(":");
75
+ if (idx < 0) {
76
+ throw new Error(`Bad header (expected 'Name: value'): ${h}`);
77
+ }
78
+ headers[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
79
+ }
80
+
81
+ const jar =
82
+ opts.cookies !== false
83
+ ? new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "bp-fetch" })
84
+ : null;
85
+
86
+ const timeoutMs = Number.parseInt(opts.timeout, 10);
87
+ const ac = new AbortController();
88
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
89
+ let result: Awaited<ReturnType<typeof fetchWithJar>>;
90
+ try {
91
+ result = await fetchWithJar(url, {
92
+ method: opts.method,
93
+ headers,
94
+ body: opts.data,
95
+ jar,
96
+ redirect: opts.redirect as RequestRedirect,
97
+ signal: ac.signal,
98
+ extraHeaders: context?.extraHeaders,
99
+ });
100
+ } finally {
101
+ clearTimeout(timer);
102
+ }
103
+
104
+ if (opts.json) {
105
+ emit.data(result as unknown as Record<string, unknown>);
106
+ return;
107
+ }
108
+ if (opts.status) {
109
+ emit.log(`${result.status} ${result.statusText} ${result.url}`, "info");
110
+ }
111
+ if (opts.headers) {
112
+ for (const [k, v] of Object.entries(result.headers)) {
113
+ emit.log(`${k}: ${v}`, "info");
114
+ }
115
+ }
116
+
117
+ if (opts.output) {
118
+ writeFileSync(opts.output, result.body);
119
+ emit.file(opts.output, {
120
+ bytes: result.body.length,
121
+ status: result.status,
122
+ status_text: result.statusText,
123
+ });
124
+ } else {
125
+ // Body is potentially binary; route as text since most fetch responses
126
+ // are text/HTML/JSON. Binary callers should use --output.
127
+ emit.text(result.body);
128
+ }
129
+
130
+ if (jar && result.cookiesSaved > 0) {
131
+ emit.log(
132
+ `saved ${result.cookiesSaved} cookie${result.cookiesSaved === 1 ? "" : "s"} to ${jar.path}`,
133
+ "info",
134
+ );
135
+ }
136
+ }
@@ -0,0 +1,202 @@
1
+ import { existsSync } from "node:fs";
2
+ import { basename, dirname, relative, resolve } from "node:path";
3
+ import type { Command } from "commander";
4
+ import type { EmitContext } from "../commander.ts";
5
+ import { exec } from "../lib/exec.ts";
6
+
7
+ /**
8
+ * `file-history <path>`: concise per-file git history. Total commits, distinct
9
+ * authors, first/last touched dates, total line impact, and the N most-recent
10
+ * commits with +/- counts. Follows renames. Submodule-aware via git itself:
11
+ * runs the log inside whichever repo `git rev-parse --show-toplevel` resolves
12
+ * from the file's directory, so submodule files get the submodule's history.
13
+ */
14
+
15
+ interface FileHistoryOpts {
16
+ limit?: string;
17
+ since?: string;
18
+ author?: string;
19
+ json?: boolean;
20
+ }
21
+
22
+ interface CommitEntry {
23
+ sha: string;
24
+ short_sha: string;
25
+ author: string;
26
+ date: string;
27
+ subject: string;
28
+ added: number;
29
+ removed: number;
30
+ }
31
+
32
+ interface FileHistoryResult {
33
+ file: string;
34
+ repo: string;
35
+ total_commits: number;
36
+ authors: { name: string; commits: number }[];
37
+ first_commit: { sha: string; date: string } | null;
38
+ last_commit: { sha: string; date: string } | null;
39
+ total_added: number;
40
+ total_removed: number;
41
+ commits: CommitEntry[];
42
+ }
43
+
44
+ export function registerFileHistoryCommand(program: Command, emit: EmitContext): void {
45
+ program
46
+ .command("file-history <path>")
47
+ .description(
48
+ "Concise per-file git history: summary stats + N most-recent commits with line impact.",
49
+ )
50
+ .option("--limit <n>", "Show N most-recent commits", "20")
51
+ .option("--since <date>", "Restrict to commits since DATE (e.g. '30 days ago', '2026-01-01')")
52
+ .option("--author <pat>", "Filter to commits by author (substring match)")
53
+ .option("--json", "Structured JSON envelope")
54
+ .action(async (path: string, opts: FileHistoryOpts) => {
55
+ try {
56
+ const result = await runFileHistory(path, opts);
57
+ if (opts.json) {
58
+ emit.config({ format: "json" });
59
+ emit.data(result);
60
+ return;
61
+ }
62
+ emit.text(`${renderFileHistory(result)}\n`);
63
+ } catch (err) {
64
+ emit.error({ code: "file_history_failed", message: (err as Error).message });
65
+ process.exit(1);
66
+ }
67
+ });
68
+ }
69
+
70
+ async function runFileHistory(path: string, opts: FileHistoryOpts): Promise<FileHistoryResult> {
71
+ const absPath = resolve(path);
72
+ if (!existsSync(absPath)) throw new Error(`no such file: ${path}`);
73
+
74
+ const repo = await detectRepo(absPath);
75
+ const relPath = relative(repo.cwd, absPath);
76
+ if (relPath.startsWith("..")) {
77
+ throw new Error(`path outside detected repo (${repo.cwd}): ${absPath}`);
78
+ }
79
+
80
+ const args = [
81
+ "log",
82
+ "--no-merges",
83
+ "--follow",
84
+ "--pretty=format:%H%x09%an%x09%aI%x09%s",
85
+ "--shortstat",
86
+ ];
87
+ if (opts.since) args.push(`--since=${opts.since}`);
88
+ if (opts.author) args.push(`--author=${opts.author}`);
89
+ args.push("--", relPath);
90
+
91
+ const log = await exec(["git", ...args], { cwd: repo.cwd, timeout: 30_000 });
92
+ if (log.exitCode !== 0) throw new Error(`git log failed: ${log.stderr || log.stdout}`);
93
+
94
+ const commits = parseGitLog(log.stdout);
95
+
96
+ const authorMap = new Map<string, number>();
97
+ let totalAdded = 0;
98
+ let totalRemoved = 0;
99
+ for (const c of commits) {
100
+ authorMap.set(c.author, (authorMap.get(c.author) || 0) + 1);
101
+ totalAdded += c.added;
102
+ totalRemoved += c.removed;
103
+ }
104
+ const authors = Array.from(authorMap.entries())
105
+ .map(([name, commits]) => ({ name, commits }))
106
+ .sort((a, b) => b.commits - a.commits);
107
+
108
+ const limit = opts.limit ? Number.parseInt(opts.limit, 10) : 20;
109
+ const recentCommits = commits.slice(0, Number.isFinite(limit) ? limit : 20);
110
+
111
+ return {
112
+ file: relPath,
113
+ repo: repo.name,
114
+ total_commits: commits.length,
115
+ authors,
116
+ first_commit:
117
+ commits.length > 0
118
+ ? { sha: commits[commits.length - 1]!.short_sha, date: commits[commits.length - 1]!.date }
119
+ : null,
120
+ last_commit: commits.length > 0 ? { sha: commits[0]!.short_sha, date: commits[0]!.date } : null,
121
+ total_added: totalAdded,
122
+ total_removed: totalRemoved,
123
+ commits: recentCommits,
124
+ };
125
+ }
126
+
127
+ function parseGitLog(output: string): CommitEntry[] {
128
+ const commits: CommitEntry[] = [];
129
+ const lines = output.split("\n");
130
+ let current: CommitEntry | null = null;
131
+
132
+ for (const line of lines) {
133
+ if (line.includes("\t")) {
134
+ if (current) commits.push(current);
135
+ const [sha, author, date, ...rest] = line.split("\t");
136
+ current = {
137
+ sha: sha!,
138
+ short_sha: sha!.slice(0, 8),
139
+ author: author!,
140
+ date: date!,
141
+ subject: rest.join("\t"),
142
+ added: 0,
143
+ removed: 0,
144
+ };
145
+ } else if (current && /\bfile[s]?\b.*changed/.test(line)) {
146
+ const ins = line.match(/(\d+) insertion/);
147
+ const del = line.match(/(\d+) deletion/);
148
+ if (ins) current.added = Number.parseInt(ins[1]!, 10);
149
+ if (del) current.removed = Number.parseInt(del[1]!, 10);
150
+ }
151
+ }
152
+ if (current) commits.push(current);
153
+
154
+ return commits;
155
+ }
156
+
157
+ async function detectRepo(absPath: string): Promise<{ name: string; cwd: string }> {
158
+ const dir = dirname(absPath);
159
+ const result = await exec(["git", "rev-parse", "--show-toplevel"], { cwd: dir });
160
+ if (result.exitCode !== 0) {
161
+ throw new Error(`not inside a git repository: ${absPath}`);
162
+ }
163
+ const cwd = result.stdout.trim();
164
+ return { name: basename(cwd), cwd };
165
+ }
166
+
167
+ function renderFileHistory(r: FileHistoryResult): string {
168
+ const lines: string[] = [];
169
+ lines.push(`file-history · ${r.repo}/${r.file}`);
170
+ lines.push("");
171
+
172
+ if (r.total_commits === 0) {
173
+ lines.push("(no commits found for this path)");
174
+ return lines.join("\n");
175
+ }
176
+
177
+ lines.push(
178
+ `summary: ${r.total_commits} commit${r.total_commits === 1 ? "" : "s"} by ${r.authors.length} author${r.authors.length === 1 ? "" : "s"}, +${r.total_added}/-${r.total_removed} lines`,
179
+ );
180
+ if (r.last_commit) lines.push(` last: ${r.last_commit.sha} ${r.last_commit.date}`);
181
+ if (r.first_commit) lines.push(` first: ${r.first_commit.sha} ${r.first_commit.date}`);
182
+
183
+ if (r.authors.length > 0) {
184
+ lines.push("");
185
+ lines.push("authors:");
186
+ for (const a of r.authors.slice(0, 5)) {
187
+ lines.push(` ${a.commits.toString().padStart(3)} ${a.name}`);
188
+ }
189
+ if (r.authors.length > 5) lines.push(` ... +${r.authors.length - 5} more`);
190
+ }
191
+
192
+ lines.push("");
193
+ lines.push(`recent (showing ${r.commits.length} of ${r.total_commits}):`);
194
+ for (const c of r.commits) {
195
+ const dateOnly = c.date.slice(0, 10);
196
+ const sig = `+${c.added}/-${c.removed}`.padStart(10);
197
+ const author = c.author.slice(0, 18).padEnd(18);
198
+ lines.push(` ${c.short_sha} ${dateOnly} ${sig} ${author} ${c.subject}`);
199
+ }
200
+
201
+ return lines.join("\n");
202
+ }