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,147 @@
1
+ /** ANSI color helpers, minimal, no dependencies */
2
+ const isColor = process.stdout.isTTY !== false;
3
+
4
+ const code = (n: number) => (isColor ? `\x1b[${n}m` : "");
5
+ const reset = code(0);
6
+
7
+ export const c = {
8
+ bold: (s: string) => `${code(1)}${s}${reset}`,
9
+ dim: (s: string) => `${code(2)}${s}${reset}`,
10
+ green: (s: string) => `${code(32)}${s}${reset}`,
11
+ red: (s: string) => `${code(31)}${s}${reset}`,
12
+ yellow: (s: string) => `${code(33)}${s}${reset}`,
13
+ cyan: (s: string) => `${code(36)}${s}${reset}`,
14
+ gray: (s: string) => `${code(90)}${s}${reset}`,
15
+ magenta: (s: string) => `${code(35)}${s}${reset}`,
16
+ };
17
+
18
+ /** Placeholder shown when a value is missing (null/empty), e.g. an unknown branch or age. */
19
+ export const NO_DATA = "—";
20
+
21
+ /**
22
+ * Pretty-print + ANSI-colorize a JSON value. 2-space indent. TTY-aware: when
23
+ * stdout is not a TTY, the underlying `c.*` helpers emit no ANSI codes, so
24
+ * output is plain text. Use this for `--pretty` flags on JSON-emitting commands.
25
+ *
26
+ * Colors: keys=cyan, strings=green, numbers=magenta, booleans=yellow, null=dim.
27
+ */
28
+ export function colorJson(value: unknown, indent = 0): string {
29
+ const seen = new WeakSet<object>();
30
+ const step = " ";
31
+
32
+ function fmt(v: unknown, depth: number): string {
33
+ if (v === null) return c.dim("null");
34
+ if (typeof v === "boolean") return c.yellow(String(v));
35
+ if (typeof v === "number") return c.magenta(String(v));
36
+ if (typeof v === "string") return c.green(JSON.stringify(v));
37
+ if (typeof v === "bigint") return c.magenta(`${v}n`);
38
+ if (Array.isArray(v)) {
39
+ if (v.length === 0) return "[]";
40
+ if (seen.has(v)) return c.dim('"[Circular]"');
41
+ seen.add(v);
42
+ const pad = step.repeat(depth + 1);
43
+ const close = step.repeat(depth);
44
+ const items = v.map((x) => pad + fmt(x, depth + 1)).join(",\n");
45
+ return `[\n${items}\n${close}]`;
46
+ }
47
+ if (typeof v === "object") {
48
+ // Class instances that define toJSON() (Big.js, Decimal.js, Date, etc.)
49
+ // would render as their raw internal shape if we walked Object.keys
50
+ // directly. Unwrap once so callers see the intended representation
51
+ // (Big.js → "1.97" instead of {s,e,c} from BigQuery NUMERIC fields).
52
+ const maybeJsonable = v as { toJSON?: () => unknown };
53
+ if (typeof maybeJsonable.toJSON === "function") {
54
+ return fmt(maybeJsonable.toJSON(), depth);
55
+ }
56
+ const obj = v as Record<string, unknown>;
57
+ const keys = Object.keys(obj);
58
+ if (keys.length === 0) return "{}";
59
+ if (seen.has(obj)) return c.dim('"[Circular]"');
60
+ seen.add(obj);
61
+ const pad = step.repeat(depth + 1);
62
+ const close = step.repeat(depth);
63
+ const items = keys
64
+ .map((k) => `${pad}${c.cyan(JSON.stringify(k))}: ${fmt(obj[k], depth + 1)}`)
65
+ .join(",\n");
66
+ return `{\n${items}\n${close}}`;
67
+ }
68
+ return JSON.stringify(v) ?? "null";
69
+ }
70
+
71
+ return fmt(value, indent);
72
+ }
73
+
74
+ /** Render a table from rows of objects */
75
+ export function table(
76
+ rows: Record<string, unknown>[],
77
+ opts: { maxColWidth?: number } = {},
78
+ ): string {
79
+ if (rows.length === 0) return c.dim("(no rows)");
80
+
81
+ const maxCol = opts.maxColWidth ?? 60;
82
+ const keys = Object.keys(rows[0]!);
83
+
84
+ // Compute column widths
85
+ const widths = new Map<string, number>();
86
+ for (const key of keys) {
87
+ let max = key.length;
88
+ for (const row of rows) {
89
+ const val = stringify(row[key]);
90
+ max = Math.max(max, Math.min(val.length, maxCol));
91
+ }
92
+ widths.set(key, max);
93
+ }
94
+
95
+ // Header
96
+ const header = keys.map((k) => c.bold(k.padEnd(widths.get(k)!))).join(" ");
97
+ const separator = keys.map((k) => "─".repeat(widths.get(k)!)).join("──");
98
+
99
+ // Rows
100
+ const lines = rows.map((row) =>
101
+ keys
102
+ .map((k) => {
103
+ const val = stringify(row[k]);
104
+ const display = val.length > maxCol ? `${val.slice(0, maxCol - 1)}…` : val;
105
+ return display.padEnd(widths.get(k)!);
106
+ })
107
+ .join(" "),
108
+ );
109
+
110
+ return [header, separator, ...lines].join("\n");
111
+ }
112
+
113
+ /** Convert a value to a display string */
114
+ function stringify(val: unknown): string {
115
+ if (val === null || val === undefined) return "NULL";
116
+ if (typeof val === "object") {
117
+ if (val instanceof Date) return val.toISOString();
118
+ // BigQuery returns { value: "..." } for some types
119
+ if ("value" in val && Object.keys(val).length === 1) {
120
+ return String((val as { value: unknown }).value);
121
+ }
122
+ return JSON.stringify(val);
123
+ }
124
+ return String(val);
125
+ }
126
+
127
+ /** Format rows as CSV */
128
+ export function csv(rows: Record<string, unknown>[]): string {
129
+ if (rows.length === 0) return "";
130
+ const keys = Object.keys(rows[0]!);
131
+ const header = keys.map(csvEscape).join(",");
132
+ const lines = rows.map((row) => keys.map((k) => csvEscape(stringify(row[k]))).join(","));
133
+ return [header, ...lines].join("\n");
134
+ }
135
+
136
+ function csvEscape(val: string): string {
137
+ if (val.includes(",") || val.includes('"') || val.includes("\n")) {
138
+ return `"${val.replace(/"/g, '""')}"`;
139
+ }
140
+ return val;
141
+ }
142
+
143
+ /** Print a labeled key-value section */
144
+ export function kvLine(label: string, value: string, color?: (s: string) => string): string {
145
+ const colorFn = color ?? ((s: string) => s);
146
+ return ` ${c.dim(label.padEnd(14))} ${colorFn(value)}`;
147
+ }
@@ -0,0 +1,211 @@
1
+ import type { Cookie, CookieJar } from "../cookies/index.ts";
2
+
3
+ /**
4
+ * Simple cookie-aware HTTP client.
5
+ *
6
+ * Wraps the global `fetch` (Bun's native one) with two extras:
7
+ * - Attaches a `Cookie:` header from a CookieJar before the request.
8
+ * - Persists `Set-Cookie` responses back into the same jar.
9
+ *
10
+ * Used by the `fetch` command and other tooling that wants to share session
11
+ * state with `browse`.
12
+ */
13
+
14
+ export interface FetchOptions {
15
+ /** HTTP method. Default GET. */
16
+ method?: string;
17
+ /** Request body. Strings/Buffers/Streams pass through to fetch directly. */
18
+ body?: BodyInit | null;
19
+ /** Extra headers (merged with auto-added Cookie). */
20
+ headers?: Record<string, string>;
21
+ /**
22
+ * Optional CookieJar. When provided:
23
+ * - Matching cookies become a Cookie header.
24
+ * - Set-Cookie response headers are parsed and merged into the jar.
25
+ * Pass `null` (or omit) to disable.
26
+ */
27
+ jar?: CookieJar | null;
28
+ /** Optional override for redirect handling. Default `'follow'`. */
29
+ redirect?: RequestRedirect;
30
+ /** AbortSignal for timeout/cancel control. */
31
+ signal?: AbortSignal;
32
+ /**
33
+ * Optional callback that returns extra headers to attach based on the
34
+ * target URL. Consumers can inject extra HTTP headers per-URL via this
35
+ * callback (e.g., a Cloudflare-bypass header for specific zones).
36
+ * Caller-provided explicit headers always win; auto-attached values
37
+ * only land when the key isn't already set.
38
+ */
39
+ extraHeaders?: (url: string) => Record<string, string>;
40
+ }
41
+
42
+ export interface FetchResult {
43
+ status: number;
44
+ statusText: string;
45
+ url: string;
46
+ headers: Record<string, string>;
47
+ body: string;
48
+ /** Number of cookies persisted back into the jar (0 if no jar passed). */
49
+ cookiesSaved: number;
50
+ }
51
+
52
+ /**
53
+ * Fetch a URL with optional cookie-jar attach + persist.
54
+ *
55
+ * Returns the response body as a string (callers handle JSON parsing).
56
+ * Streams aren't supported; this is a CLI helper, not a streaming HTTP
57
+ * client. Use Bun's `fetch` directly for streaming workloads.
58
+ */
59
+ export async function fetchWithJar(url: string, opts: FetchOptions = {}): Promise<FetchResult> {
60
+ const headers: Record<string, string> = { ...(opts.headers ?? {}) };
61
+
62
+ if (opts.jar) {
63
+ const cookieHeader = opts.jar.header(url);
64
+ if (cookieHeader && !headers.Cookie && !headers.cookie) {
65
+ headers.Cookie = cookieHeader;
66
+ }
67
+ }
68
+
69
+ // Caller-provided extraHeaders callback (e.g., for Cloudflare-bypass or
70
+ // custom auth headers). Caller-supplied explicit headers always win.
71
+ if (opts.extraHeaders) {
72
+ for (const [k, v] of Object.entries(opts.extraHeaders(url))) {
73
+ if (!(k in headers) && !(k.toLowerCase() in headers)) headers[k] = v;
74
+ }
75
+ }
76
+
77
+ const response = await fetch(url, {
78
+ method: opts.method ?? "GET",
79
+ body: opts.body,
80
+ headers,
81
+ redirect: opts.redirect ?? "follow",
82
+ signal: opts.signal,
83
+ });
84
+
85
+ let cookiesSaved = 0;
86
+ if (opts.jar) {
87
+ const setCookieHeaders = collectSetCookie(response.headers);
88
+ if (setCookieHeaders.length > 0) {
89
+ const parsedHost = new URL(response.url).hostname;
90
+ const cookies: Cookie[] = setCookieHeaders
91
+ .map((sc) => parseSetCookie(sc, parsedHost))
92
+ .filter((c): c is Cookie => c !== null);
93
+ for (const cookie of cookies) {
94
+ opts.jar.set(cookie);
95
+ }
96
+ cookiesSaved = cookies.length;
97
+ }
98
+ }
99
+
100
+ const headerObj: Record<string, string> = {};
101
+ response.headers.forEach((value, key) => {
102
+ headerObj[key] = value;
103
+ });
104
+
105
+ return {
106
+ status: response.status,
107
+ statusText: response.statusText,
108
+ url: response.url,
109
+ headers: headerObj,
110
+ body: await response.text(),
111
+ cookiesSaved,
112
+ };
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Set-Cookie parsing
117
+ // ---------------------------------------------------------------------------
118
+ //
119
+ // `fetch` joins multiple Set-Cookie headers with `, ` per the spec, but that
120
+ // collides with the comma-separated date format in `Expires=`. We use the
121
+ // dual-mode approach: prefer the `getSetCookie()` method when available
122
+ // (Node 20+, Bun 1.0+) and fall back to manual splitting otherwise.
123
+
124
+ function collectSetCookie(headers: Headers): string[] {
125
+ const h = headers as Headers & { getSetCookie?: () => string[] };
126
+ if (typeof h.getSetCookie === "function") {
127
+ return h.getSetCookie();
128
+ }
129
+ const raw = headers.get("set-cookie");
130
+ return raw ? splitSetCookieHeader(raw) : [];
131
+ }
132
+
133
+ function splitSetCookieHeader(raw: string): string[] {
134
+ // Splits on ", " that precedes a new cookie (heuristic: a token followed
135
+ // by `=`), avoiding date-internal commas like `Expires=Wed, 21 Oct …`.
136
+ const parts: string[] = [];
137
+ let depth = 0;
138
+ let buf = "";
139
+ for (let i = 0; i < raw.length; i++) {
140
+ const ch = raw[i];
141
+ if (ch === "," && depth === 0) {
142
+ const ahead = raw.slice(i + 1).trimStart();
143
+ if (/^[A-Za-z0-9_!#$%&'*+\-.^`|~]+=/.test(ahead)) {
144
+ parts.push(buf.trim());
145
+ buf = "";
146
+ continue;
147
+ }
148
+ }
149
+ if (ch === "=") depth++;
150
+ if (ch === ";") depth = 0;
151
+ buf += ch;
152
+ }
153
+ if (buf.trim()) parts.push(buf.trim());
154
+ return parts;
155
+ }
156
+
157
+ function parseSetCookie(raw: string, defaultDomain: string): Cookie | null {
158
+ const parts = raw.split(";").map((p) => p.trim());
159
+ if (parts.length === 0) return null;
160
+
161
+ const first = parts[0];
162
+ if (!first) return null;
163
+ const eq = first.indexOf("=");
164
+ if (eq < 0) return null;
165
+ const name = first.slice(0, eq).trim();
166
+ const value = first.slice(eq + 1).trim();
167
+ if (!name) return null;
168
+
169
+ const cookie: Cookie = {
170
+ name,
171
+ value,
172
+ domain: defaultDomain,
173
+ path: "/",
174
+ expires: -1,
175
+ httpOnly: false,
176
+ secure: false,
177
+ session: true,
178
+ size: name.length + value.length,
179
+ };
180
+
181
+ for (const attr of parts.slice(1)) {
182
+ const lower = attr.toLowerCase();
183
+ if (lower.startsWith("domain=")) {
184
+ const d = attr.slice(7).trim();
185
+ cookie.domain = d.startsWith(".") ? d : `.${d}`;
186
+ } else if (lower.startsWith("path=")) {
187
+ cookie.path = attr.slice(5).trim() || "/";
188
+ } else if (lower.startsWith("expires=")) {
189
+ const ts = Date.parse(attr.slice(8).trim());
190
+ if (!Number.isNaN(ts)) {
191
+ cookie.expires = Math.floor(ts / 1000);
192
+ cookie.session = false;
193
+ }
194
+ } else if (lower.startsWith("max-age=")) {
195
+ const seconds = Number.parseInt(attr.slice(8).trim(), 10);
196
+ if (!Number.isNaN(seconds)) {
197
+ cookie.expires = Math.floor(Date.now() / 1000) + seconds;
198
+ cookie.session = false;
199
+ }
200
+ } else if (lower === "secure") {
201
+ cookie.secure = true;
202
+ } else if (lower === "httponly") {
203
+ cookie.httpOnly = true;
204
+ } else if (lower.startsWith("samesite=")) {
205
+ const v = attr.slice(9).trim();
206
+ cookie.sameSite = v.charAt(0).toUpperCase() + v.slice(1).toLowerCase();
207
+ }
208
+ }
209
+
210
+ return cookie;
211
+ }
@@ -0,0 +1 @@
1
+ export { type FetchOptions, type FetchResult, fetchWithJar } from "./client.js";
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Agent persona identity registry: per-agent durable UUIDs.
3
+ *
4
+ * The coord layer has two id concepts that get confused:
5
+ * - `instance_id`: Claude Code session UUID (fresh per session). Lives at
6
+ * `.harnery/active/<instance_id>.json` as the heartbeat filename.
7
+ * - `agent_id` (this module): durable per-AGENT-PERSONA UUID. Stable
8
+ * across sessions; same UUID for every Maya session, regardless of
9
+ * how many times she restarts Claude Code.
10
+ *
11
+ * Storage: `.harnery/identities/<agent_id>.json`, one file per persona.
12
+ * The filename IS the id so reverse lookup is O(1) by id; forward lookup
13
+ * by name is a scan (small directory, ~100 personas tops in practice).
14
+ *
15
+ * Used as the canonical identifier in:
16
+ * - Council manifests (`created_by_id`, `steward_id`, `member_ids[]`)
17
+ * - Council body filenames (`<agent_id>.md` not `<name>.md`)
18
+ * - Session events ndjson (new `agent_id` field alongside `agent_name`)
19
+ * - Heartbeats (new `agent_id` field; existing CC subagent-call id
20
+ * renamed to `subagent_call_id`)
21
+ */
22
+
23
+ import { randomUUID } from "node:crypto";
24
+ import {
25
+ existsSync,
26
+ mkdirSync,
27
+ readdirSync,
28
+ readFileSync,
29
+ renameSync,
30
+ writeFileSync,
31
+ } from "node:fs";
32
+ import { resolve } from "node:path";
33
+
34
+ import { monorepoRoot } from "../../core/agents/index.ts";
35
+
36
+ export const IDENTITY_SCHEMA_VERSION = 1 as const;
37
+
38
+ export interface AgentIdentity {
39
+ schema_version: 1;
40
+ /** Persistent persona UUID. v4. */
41
+ agent_id: string;
42
+ /** Current display name (e.g. "Maya", without the "agent-" prefix). */
43
+ name: string;
44
+ /** Prior names this identity has been known by. Updated by renameIdentity(). */
45
+ aliases: Array<{ name: string; retired_at: string }>;
46
+ /** UTC ISO-8601 timestamp of first mint. */
47
+ created_at: string;
48
+ }
49
+
50
+ function identitiesDir(): string | null {
51
+ const root = monorepoRoot();
52
+ if (!root) return null;
53
+ return resolve(root, ".harnery", "identities");
54
+ }
55
+
56
+ function ensureDir(): string {
57
+ const dir = identitiesDir();
58
+ if (!dir) throw new Error("not in an agent session; no monorepo root");
59
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
60
+ return dir;
61
+ }
62
+
63
+ function identityPath(agentId: string): string {
64
+ return resolve(ensureDir(), `${agentId}.json`);
65
+ }
66
+
67
+ function nowIso(): string {
68
+ return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
69
+ }
70
+
71
+ function readIdentityFile(path: string): AgentIdentity | null {
72
+ try {
73
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as AgentIdentity;
74
+ if (parsed.schema_version !== IDENTITY_SCHEMA_VERSION) {
75
+ throw new Error(
76
+ `identity ${path}: unsupported schema_version=${parsed.schema_version} (expected ${IDENTITY_SCHEMA_VERSION})`,
77
+ );
78
+ }
79
+ return parsed;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ /** Strip an "agent-" prefix if present. The registry stores bare names. */
86
+ export function bareName(raw: string): string {
87
+ const trimmed = raw.trim();
88
+ return trimmed.startsWith("agent-") ? trimmed.slice("agent-".length) : trimmed;
89
+ }
90
+
91
+ /** Re-add the "agent-" prefix for display contexts. */
92
+ export function displayName(name: string): string {
93
+ return name.startsWith("agent-") ? name : `agent-${name}`;
94
+ }
95
+
96
+ /** Read one identity by id. Null if missing. */
97
+ export function lookupById(agentId: string): AgentIdentity | null {
98
+ const dir = identitiesDir();
99
+ if (!dir) return null;
100
+ const fp = resolve(dir, `${agentId}.json`);
101
+ if (!existsSync(fp)) return null;
102
+ return readIdentityFile(fp);
103
+ }
104
+
105
+ /** Read one identity by display name (case-insensitive on bare name).
106
+ * Scans the directory; O(N) on identity count. */
107
+ export function lookupByName(name: string): AgentIdentity | null {
108
+ const dir = identitiesDir();
109
+ if (!dir || !existsSync(dir)) return null;
110
+ const wanted = bareName(name).toLowerCase();
111
+ for (const f of readdirSync(dir)) {
112
+ if (!f.endsWith(".json")) continue;
113
+ const id = readIdentityFile(resolve(dir, f));
114
+ if (!id) continue;
115
+ if (id.name.toLowerCase() === wanted) return id;
116
+ for (const alias of id.aliases) {
117
+ if (alias.name.toLowerCase() === wanted) return id;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /** All known identities, sorted by created_at ascending. */
124
+ export function listIdentities(): AgentIdentity[] {
125
+ const dir = identitiesDir();
126
+ if (!dir || !existsSync(dir)) return [];
127
+ const out: AgentIdentity[] = [];
128
+ for (const f of readdirSync(dir)) {
129
+ if (!f.endsWith(".json")) continue;
130
+ const id = readIdentityFile(resolve(dir, f));
131
+ if (id) out.push(id);
132
+ }
133
+ out.sort((a, b) => a.created_at.localeCompare(b.created_at));
134
+ return out;
135
+ }
136
+
137
+ /**
138
+ * Find an identity by name, or mint one. Returns the resolved identity.
139
+ * Idempotent: calling twice with the same name returns the same record.
140
+ *
141
+ * Mint path: generates a fresh uuid v4, writes the file atomically (tmp +
142
+ * rename), returns the new identity. Race-safe: if two processes mint
143
+ * simultaneously, both files land (different ids), but lookupByName will
144
+ * surface whichever happened to be read first; the second is orphaned.
145
+ * For agent personas (added at human cadence) this race is theoretical.
146
+ */
147
+ export function ensureIdentity(name: string): AgentIdentity {
148
+ const existing = lookupByName(name);
149
+ if (existing) return existing;
150
+ const id: AgentIdentity = {
151
+ schema_version: IDENTITY_SCHEMA_VERSION,
152
+ agent_id: randomUUID(),
153
+ name: bareName(name),
154
+ aliases: [],
155
+ created_at: nowIso(),
156
+ };
157
+ writeIdentity(id);
158
+ return id;
159
+ }
160
+
161
+ /** Persist an identity (tmp + rename). Creates the dir if missing. */
162
+ export function writeIdentity(id: AgentIdentity): void {
163
+ ensureDir();
164
+ const fp = identityPath(id.agent_id);
165
+ const tmp = `${fp}.tmp.${process.pid}`;
166
+ writeFileSync(tmp, `${JSON.stringify(id, null, 2)}\n`, "utf8");
167
+ renameSync(tmp, fp);
168
+ }
169
+
170
+ /**
171
+ * Rename an existing identity. The prior name lands in aliases[] so name
172
+ * lookup still resolves correctly. The agent_id is stable across renames,
173
+ * which is the whole point of the registry.
174
+ */
175
+ export function renameIdentity(agentId: string, newName: string): AgentIdentity {
176
+ const existing = lookupById(agentId);
177
+ if (!existing) {
178
+ throw new Error(`renameIdentity: no identity matching '${agentId}'`);
179
+ }
180
+ const bare = bareName(newName);
181
+ if (existing.name === bare) return existing;
182
+ const next: AgentIdentity = {
183
+ ...existing,
184
+ name: bare,
185
+ aliases: [...existing.aliases, { name: existing.name, retired_at: nowIso() }],
186
+ };
187
+ writeIdentity(next);
188
+ return next;
189
+ }
190
+
191
+ /**
192
+ * Resolve a string to an agent_id. Accepts:
193
+ * - A UUID (already an id), verified to exist in registry
194
+ * - A display name (e.g. "agent-Maya" or "Maya"), resolved via lookupByName
195
+ * Returns null when the input is neither a known id nor a known name.
196
+ *
197
+ * Does NOT mint. Callers that want mint-on-miss should call ensureIdentity()
198
+ * with a name, then use the returned agent_id.
199
+ */
200
+ export function resolveAgentId(input: string): string | null {
201
+ const trimmed = input.trim();
202
+ if (!trimmed) return null;
203
+ // UUID shape check, relaxed; we only need the lookupById to confirm.
204
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmed)) {
205
+ const byId = lookupById(trimmed);
206
+ if (byId) return byId.agent_id;
207
+ }
208
+ const byName = lookupByName(trimmed);
209
+ return byName ? byName.agent_id : null;
210
+ }
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { resolve } from "node:path";
4
+ import { coordEnv } from "./env.ts";
5
+
6
+ /**
7
+ * Resolve a human-friendly label for the current machine. Used to tag
8
+ * coordination artifacts (scratch-doc headers, session telemetry) by the host
9
+ * they originated on.
10
+ *
11
+ * Precedence (first non-empty wins):
12
+ * 1. HARNERY_MACHINE env var: transient / per-launch / CI override
13
+ * 2. ~/.config/harnery/machine file: persistent, harness-neutral identity
14
+ * 3. os.hostname() (normalized): automatic floor; never empty
15
+ *
16
+ * The env var sits on top for ad-hoc overrides; the file is the durable home for
17
+ * a machine's name (it survives across Claude Code / Cursor / Codex and GUI
18
+ * launches that don't inherit a shell rc); hostname guarantees a real answer
19
+ * with zero configuration.
20
+ */
21
+ export function resolveMachineLabel(): string {
22
+ const fromEnv = coordEnv("MACHINE")?.trim();
23
+ if (fromEnv) return fromEnv;
24
+
25
+ const fromFile = readMachineFile();
26
+ if (fromFile) return fromFile;
27
+
28
+ return normalizeHost(hostname());
29
+ }
30
+
31
+ /** Absolute path of the machine-label file, honoring XDG_CONFIG_HOME. */
32
+ export function machineFilePath(): string | null {
33
+ const home = process.env.HOME ?? process.env.USERPROFILE;
34
+ if (!home) return null;
35
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || resolve(home, ".config");
36
+ return resolve(configHome, "harnery", "machine");
37
+ }
38
+
39
+ /** First non-empty, non-comment line of the machine-label file, or null. */
40
+ function readMachineFile(): string | null {
41
+ const path = machineFilePath();
42
+ if (!path || !existsSync(path)) return null;
43
+ try {
44
+ for (const line of readFileSync(path, "utf8").split("\n")) {
45
+ const trimmed = line.trim();
46
+ if (trimmed && !trimmed.startsWith("#")) return trimmed;
47
+ }
48
+ } catch {
49
+ /* unreadable; fall through to hostname */
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /** Lowercase, strip a trailing `.local` (macOS mDNS suffix). Never empty. */
55
+ function normalizeHost(raw: string): string {
56
+ const cleaned = raw
57
+ .trim()
58
+ .replace(/\.local$/i, "")
59
+ .toLowerCase();
60
+ return cleaned || "unknown";
61
+ }