harnery 0.0.1 → 0.2.1

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 +509 -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 +39 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +121 -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 +583 -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 +169 -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,513 @@
1
+ // Inline cachePath; see lib/tunnel/state.ts for rationale.
2
+ function cachePath(tool, filename) {
3
+ const dir = resolve(process.cwd(), ".cache", tool);
4
+ return resolve(dir, filename);
5
+ }
6
+ import { spawn, spawnSync } from "node:child_process";
7
+ import { existsSync, openSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { cfdLogFile, clearState, DEFAULT_INSTANCE, ensureCloudflared, gateLogFile, isProcessAlive, listStates, readConfig, readState, writeConfig, writeState, } from "../lib/tunnel/state.js";
10
+ /**
11
+ * `tunnel`: IP-gated Cloudflare quick tunnel in front of a local upstream.
12
+ *
13
+ * Two-process design: a Bun reverse-proxy worker (lib/tunnel/gate.ts) checks
14
+ * the Cloudflare-set `CF-Connecting-IP` header against an allowlist and
15
+ * rewrites Host for the upstream; cloudflared --url then exposes the gate
16
+ * as a random `<words>.trycloudflare.com` hostname.
17
+ *
18
+ * State + config persisted under `.cache/tunnel/`. cloudflared auto-installs
19
+ * to ~/.local/bin/ on first run (Linux only; macOS users `brew install`).
20
+ */
21
+ const DEFAULT_TARGET = "127.0.0.1:8001";
22
+ const DEFAULT_VHOST = "localhost";
23
+ const DEFAULT_GATE_PORT = 9001;
24
+ const MAX_GATE_PORT = DEFAULT_GATE_PORT + 99; // auto-allocation scan ceiling
25
+ /**
26
+ * Validate + normalize an instance name. Names become filename fragments
27
+ * (state-<name>.json) and pgrep patterns, so they're restricted to a safe
28
+ * charset. Throws a friendly emit.error + exits on a bad name.
29
+ */
30
+ function resolveName(raw) {
31
+ const name = (raw ?? DEFAULT_INSTANCE).trim();
32
+ if (!/^[a-z0-9][a-z0-9-]*$/i.test(name)) {
33
+ emit.error({
34
+ code: "tunnel_bad_name",
35
+ message: `Invalid instance name "${name}". Use letters, digits, and dashes (must start alphanumeric).`,
36
+ });
37
+ process.exit(1);
38
+ }
39
+ return name;
40
+ }
41
+ function gateScriptPath() {
42
+ return resolve(import.meta.dirname, "..", "lib", "tunnel", "gate.ts");
43
+ }
44
+ /**
45
+ * `harn tunnel` is the one command that hard-requires Bun: the gate worker is a
46
+ * `Bun.serve` process (HTTP + WebSocket reverse proxy), spawned as `bun run
47
+ * gate.ts`. Everything else in harnery runs on Node, but this can't until the
48
+ * gate is ported off `Bun.serve` (node:http + `ws`). Detect Bun up front so the
49
+ * failure is a clear message rather than an opaque ENOENT from the gate spawn.
50
+ */
51
+ function bunAvailable() {
52
+ return spawnSync("bun", ["--version"], { stdio: "ignore" }).status === 0;
53
+ }
54
+ function sleep(ms) {
55
+ return new Promise((r) => setTimeout(r, ms));
56
+ }
57
+ /**
58
+ * Kill every process whose command line matches `pattern` (via `pgrep -f`),
59
+ * skipping our own PID and any already-killed. Returns the count killed. Used
60
+ * as a fallback so orphaned gate/cloudflared processes get cleaned even when
61
+ * the state file was lost (which otherwise left them squatting on the port).
62
+ */
63
+ function killByPattern(pattern, alreadyKilled) {
64
+ const r = spawnSync("pgrep", ["-f", pattern], { encoding: "utf-8" });
65
+ if (r.status !== 0 || typeof r.stdout !== "string")
66
+ return 0;
67
+ let killed = 0;
68
+ for (const line of r.stdout.split("\n")) {
69
+ const pid = Number(line.trim());
70
+ if (!pid || pid === process.pid || alreadyKilled.has(pid))
71
+ continue;
72
+ try {
73
+ process.kill(pid);
74
+ alreadyKilled.add(pid);
75
+ killed++;
76
+ }
77
+ catch {
78
+ /* race: already gone */
79
+ }
80
+ }
81
+ return killed;
82
+ }
83
+ /**
84
+ * Sweep stray gate + cloudflared processes for ONE instance, identified by its
85
+ * gate port. Both signatures are port-scoped so tearing down one tunnel never
86
+ * touches another:
87
+ * - gate: `gate.ts ... --port <port>` (the port is on the gate's argv)
88
+ * - cloudflared: `--url http://localhost:<port>` (order-independent, so it
89
+ * matches regardless of the `--protocol http2` flag we also pass).
90
+ * Port boundary is guarded with `( |$)` so port 9001 doesn't match 90011.
91
+ */
92
+ function sweepStrays(gatePort, alreadyKilled) {
93
+ return (killByPattern(`gate\\.ts.*--port ${gatePort}( |$)`, alreadyKilled) +
94
+ killByPattern(`--url http://localhost:${gatePort}( |$)`, alreadyKilled));
95
+ }
96
+ /** Ports currently bound by a LISTEN socket (best-effort via `ss`). */
97
+ function listeningPorts() {
98
+ const ports = new Set();
99
+ const r = spawnSync("ss", ["-tlnH"], { encoding: "utf-8" });
100
+ if (r.status === 0 && typeof r.stdout === "string") {
101
+ for (const m of r.stdout.matchAll(/:(\d+)\s/g))
102
+ ports.add(Number(m[1]));
103
+ }
104
+ return ports;
105
+ }
106
+ /**
107
+ * Pick a gate port for a new instance. An explicit `--gate-port` is honored
108
+ * (and rejected if it's already taken); otherwise scan upward from 9001 for the
109
+ * first port that's neither held by a live instance nor currently listening.
110
+ */
111
+ function allocateGatePort(preferred) {
112
+ const used = new Set(listStates()
113
+ .filter((s) => isProcessAlive(s.gate_pid))
114
+ .map((s) => s.gate_port));
115
+ const listening = listeningPorts();
116
+ const taken = (p) => used.has(p) || listening.has(p);
117
+ if (preferred !== undefined) {
118
+ if (taken(preferred)) {
119
+ emit.error({
120
+ code: "tunnel_port_taken",
121
+ message: `Gate port ${preferred} is already in use. Omit --gate-port to auto-allocate, or pick a free one.`,
122
+ });
123
+ process.exit(1);
124
+ }
125
+ return preferred;
126
+ }
127
+ for (let p = DEFAULT_GATE_PORT; p <= MAX_GATE_PORT; p++) {
128
+ if (!taken(p))
129
+ return p;
130
+ }
131
+ emit.error({
132
+ code: "tunnel_no_free_port",
133
+ message: `No free gate port in ${DEFAULT_GATE_PORT}-${MAX_GATE_PORT}. Tear down some tunnels first.`,
134
+ });
135
+ process.exit(1);
136
+ }
137
+ function extractUrl(log) {
138
+ const m = log.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
139
+ return m ? m[0] : null;
140
+ }
141
+ /**
142
+ * cloudflared logs "Registered tunnel connection" once the edge is live and
143
+ * routable. Match that exact line only, because earlier lines carry `connIndex=`
144
+ * too (e.g. "Tunnel connection curve preferences … connIndex=0"), which would
145
+ * false-positive readiness before the connection actually registers.
146
+ */
147
+ function isRegistered(log) {
148
+ return /Registered tunnel connection/.test(log);
149
+ }
150
+ /**
151
+ * Wait for the tunnel to be genuinely usable. cloudflared prints the
152
+ * `*.trycloudflare.com` URL early (at precheck) but the hostname doesn't route
153
+ * until the edge connection is *registered*, a few seconds later, and
154
+ * occasionally never on a wedged QUIC start. We gate readiness on the
155
+ * registration line, not just the URL, so `up` doesn't hand back a URL that
156
+ * 404s/times out. Returns the URL (if seen at all) plus whether it registered.
157
+ */
158
+ async function waitForReady(logPath, timeoutMs) {
159
+ const deadline = Date.now() + timeoutMs;
160
+ let url = null;
161
+ while (Date.now() < deadline) {
162
+ if (existsSync(logPath)) {
163
+ const log = readFileSync(logPath, "utf-8");
164
+ url = url ?? extractUrl(log);
165
+ if (url && isRegistered(log))
166
+ return { url, registered: true };
167
+ }
168
+ await sleep(500);
169
+ }
170
+ return { url, registered: false };
171
+ }
172
+ /** Resolve the context-supplied default vhost (literal or lazy resolver). */
173
+ function contextVhost() {
174
+ const v = context?.tunnelDefaultVhost;
175
+ const resolved = typeof v === "function" ? v() : v;
176
+ return resolved ?? null;
177
+ }
178
+ async function up(opts) {
179
+ if (!bunAvailable()) {
180
+ emit.error({
181
+ code: "tunnel_requires_bun",
182
+ message: "harn tunnel requires Bun: the gate worker is a Bun.serve process. " +
183
+ "Install Bun (https://bun.sh) and re-run. (Every other harn command runs on Node.)",
184
+ });
185
+ process.exit(1);
186
+ }
187
+ const name = resolveName(opts.name);
188
+ const target = opts.target ?? DEFAULT_TARGET;
189
+ // Precedence: explicit --vhost > the consumer's configured default (via
190
+ // context.tunnelDefaultVhost) > "localhost".
191
+ const vhost = opts.vhost ?? contextVhost() ?? DEFAULT_VHOST;
192
+ const existing = readState(name);
193
+ if (existing && isProcessAlive(existing.gate_pid) && isProcessAlive(existing.cloudflared_pid)) {
194
+ emit.text(`Already up [${name}]: ${existing.url}\n`);
195
+ emit.text(` Forwarding: ${existing.target} (Host: ${existing.vhost})\n`);
196
+ return;
197
+ }
198
+ if (existing)
199
+ clearState(name);
200
+ // Allocate the gate port (after clearing dead state so its old port frees up
201
+ // for reuse). Explicit --gate-port is validated; otherwise auto-scan.
202
+ const gatePort = allocateGatePort(opts.gatePort ? Number(opts.gatePort) : undefined);
203
+ // Self-heal: clear any orphaned gate/cloudflared on THIS instance's port from
204
+ // a prior crashed or state-cleared run so the gate port is free before we bind.
205
+ if (sweepStrays(gatePort, new Set()))
206
+ await sleep(500);
207
+ const cfg = readConfig();
208
+ if (cfg.allowed_ips.length === 0) {
209
+ emit.error({
210
+ code: "tunnel_allowlist_empty",
211
+ message: "Allowlist is empty; refusing to start. Add an IP first: harn tunnel allow add <ip>",
212
+ });
213
+ process.exit(1);
214
+ }
215
+ const cloudflaredBin = ensureCloudflared();
216
+ const gateLogPath = cachePath("tunnel", gateLogFile(name));
217
+ const cfdLogPath = cachePath("tunnel", cfdLogFile(name));
218
+ writeFileSync(gateLogPath, "");
219
+ writeFileSync(cfdLogPath, "");
220
+ const gateFd = openSync(gateLogPath, "a");
221
+ // `--name`/`--port` on argv mirror the env vars; they're what makes the gate
222
+ // process distinguishable per-instance in `pgrep -f` (see sweepStrays).
223
+ const gateProc = spawn("bun", ["run", gateScriptPath(), "--name", name, "--port", String(gatePort)], {
224
+ detached: true,
225
+ stdio: ["ignore", gateFd, gateFd],
226
+ env: {
227
+ ...process.env,
228
+ HARNERY_TUNNEL_ALLOW: cfg.allowed_ips.join(","),
229
+ HARNERY_TUNNEL_TARGET: target,
230
+ HARNERY_TUNNEL_VHOST: vhost,
231
+ HARNERY_TUNNEL_PORT: String(gatePort),
232
+ },
233
+ });
234
+ gateProc.unref();
235
+ await sleep(800);
236
+ if (!isProcessAlive(gateProc.pid)) {
237
+ emit.error({
238
+ code: "tunnel_gate_failed",
239
+ message: `Gate failed to start. Check log: ${gateLogPath}`,
240
+ });
241
+ process.exit(1);
242
+ }
243
+ const cfdFd = openSync(cfdLogPath, "a");
244
+ // Force HTTP/2 transport. The default QUIC transport wedges at precheck on
245
+ // constrained hosts (e.g. WSL, where UDP receive buffers can't grow and ICMP
246
+ // is restricted): the URL prints but the edge never registers. HTTP/2 is
247
+ // marginally higher-latency but registers reliably, which is what a dev
248
+ // tunnel needs.
249
+ const cfdProc = spawn(cloudflaredBin, ["tunnel", "--protocol", "http2", "--url", `http://localhost:${gatePort}`], {
250
+ detached: true,
251
+ stdio: ["ignore", cfdFd, cfdFd],
252
+ });
253
+ cfdProc.unref();
254
+ const { url, registered } = await waitForReady(cfdLogPath, 30_000);
255
+ if (!url) {
256
+ try {
257
+ process.kill(gateProc.pid);
258
+ }
259
+ catch {
260
+ /* already dead */
261
+ }
262
+ try {
263
+ process.kill(cfdProc.pid);
264
+ }
265
+ catch {
266
+ /* already dead */
267
+ }
268
+ emit.error({
269
+ code: "tunnel_url_timeout",
270
+ message: `Failed to obtain tunnel URL within 30s. Check log: ${cfdLogPath}`,
271
+ });
272
+ process.exit(1);
273
+ }
274
+ const state = {
275
+ name,
276
+ url,
277
+ gate_pid: gateProc.pid,
278
+ cloudflared_pid: cfdProc.pid,
279
+ started_at: new Date().toISOString(),
280
+ target,
281
+ vhost,
282
+ gate_port: gatePort,
283
+ };
284
+ writeState(state);
285
+ const stopHint = name === DEFAULT_INSTANCE ? "harn tunnel down" : `harn tunnel down --name ${name}`;
286
+ emit.text(`\n Instance: ${name}\n`);
287
+ emit.text(` URL: ${url}\n\n`);
288
+ emit.text(` Forwarding: ${target} (Host: ${vhost})\n`);
289
+ emit.text(` Gate port: ${gatePort}\n`);
290
+ emit.text(` Allowed IPs: ${cfg.allowed_ips.join(", ")}\n\n`);
291
+ if (!registered) {
292
+ emit.text(` ⚠ Edge connection didn't register within 30s (QUIC can wedge on a cold\n start). If the URL 404s or times out, bounce it: ${stopHint} && harn tunnel up\n\n`);
293
+ }
294
+ emit.text(` Stop: ${stopHint}\n`);
295
+ emit.text(" Status: harn tunnel status\n");
296
+ }
297
+ /** Tear down a single instance by name. Returns the number of processes killed. */
298
+ function downOne(name, killed) {
299
+ const before = killed.size;
300
+ const state = readState(name);
301
+ if (state) {
302
+ for (const pid of [state.gate_pid, state.cloudflared_pid]) {
303
+ if (isProcessAlive(pid)) {
304
+ try {
305
+ process.kill(pid);
306
+ killed.add(pid);
307
+ }
308
+ catch {
309
+ /* race: already gone */
310
+ }
311
+ }
312
+ }
313
+ }
314
+ // Fallback: sweep orphans on this instance's gate port, even when state was
315
+ // lost; they'd otherwise squat on the port and break the next `up`.
316
+ sweepStrays(state?.gate_port ?? DEFAULT_GATE_PORT, killed);
317
+ clearState(name);
318
+ return killed.size - before;
319
+ }
320
+ function down(opts) {
321
+ const killed = new Set();
322
+ if (opts.all) {
323
+ const states = listStates();
324
+ if (states.length === 0) {
325
+ emit.text("No tunnels up. Nothing to stop.\n");
326
+ return;
327
+ }
328
+ for (const s of states)
329
+ downOne(s.name, killed);
330
+ emit.text(`Stopped ${states.length} tunnel(s) [${states.map((s) => s.name).join(", ")}], ${killed.size} process(es).\n`);
331
+ return;
332
+ }
333
+ const name = resolveName(opts.name);
334
+ // Bare `down` targets the default instance. If it's not up but named ones
335
+ // are, don't silently no-op; point the operator at them.
336
+ if (name === DEFAULT_INSTANCE && !readState(DEFAULT_INSTANCE)) {
337
+ const others = listStates();
338
+ if (others.length > 0) {
339
+ emit.text(`No default tunnel running. Other tunnels up: ${others.map((s) => s.name).join(", ")}.\nUse \`harn tunnel down --name <name>\` or \`harn tunnel down --all\`.\n`);
340
+ return;
341
+ }
342
+ }
343
+ downOne(name, killed);
344
+ emit.text(killed.size === 0
345
+ ? `No tunnel processes found for [${name}]. Nothing to stop.\n`
346
+ : `Stopped ${killed.size} process(es). Tunnel [${name}] down.\n`);
347
+ }
348
+ function instanceState(state) {
349
+ return isProcessAlive(state.gate_pid) && isProcessAlive(state.cloudflared_pid) ? "up" : "stale";
350
+ }
351
+ function fmtUptime(startedAt) {
352
+ const secs = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);
353
+ if (!Number.isFinite(secs) || secs < 0)
354
+ return "?";
355
+ if (secs < 60)
356
+ return `${secs}s`;
357
+ if (secs < 3600)
358
+ return `${Math.floor(secs / 60)}m`;
359
+ return `${Math.floor(secs / 3600)}h${Math.floor((secs % 3600) / 60)}m`;
360
+ }
361
+ /** Detailed single-instance block (the pre-multi-instance format). */
362
+ function statusDetail(state) {
363
+ const gateAlive = isProcessAlive(state.gate_pid);
364
+ const cfdAlive = isProcessAlive(state.cloudflared_pid);
365
+ const cfg = readConfig();
366
+ emit.text(`${gateAlive && cfdAlive ? "up" : "stale"} [${state.name}]\n`);
367
+ emit.text(` URL: ${state.url}\n`);
368
+ emit.text(` Forwarding: ${state.target} (Host: ${state.vhost})\n`);
369
+ emit.text(` Gate port: ${state.gate_port}\n`);
370
+ emit.text(` Allowed IPs: ${cfg.allowed_ips.join(", ")}\n`);
371
+ emit.text(` Gate PID: ${state.gate_pid}${gateAlive ? "" : " (DEAD)"}\n`);
372
+ emit.text(` CFD PID: ${state.cloudflared_pid}${cfdAlive ? "" : " (DEAD)"}\n`);
373
+ emit.text(` Uptime: ${fmtUptime(state.started_at)}\n`);
374
+ }
375
+ function status(opts) {
376
+ // Named → detailed single block.
377
+ if (opts.name) {
378
+ const state = readState(resolveName(opts.name));
379
+ if (!state) {
380
+ emit.text(`down [${resolveName(opts.name)}]\n`);
381
+ return;
382
+ }
383
+ statusDetail(state);
384
+ return;
385
+ }
386
+ // No name → table of every instance.
387
+ const states = listStates();
388
+ if (states.length === 0) {
389
+ emit.text("down\n");
390
+ return;
391
+ }
392
+ if (states.length === 1) {
393
+ // Single tunnel: show the full detail block (backward-compatible).
394
+ statusDetail(states[0]);
395
+ return;
396
+ }
397
+ const rows = states.map((s) => ({
398
+ name: s.name,
399
+ state: instanceState(s),
400
+ url: s.url,
401
+ fwd: `${s.target} (${s.vhost})`,
402
+ port: String(s.gate_port),
403
+ up: fmtUptime(s.started_at),
404
+ }));
405
+ const w = {
406
+ name: Math.max(4, ...rows.map((r) => r.name.length)),
407
+ state: 5,
408
+ url: Math.max(3, ...rows.map((r) => r.url.length)),
409
+ fwd: Math.max(10, ...rows.map((r) => r.fwd.length)),
410
+ port: 4,
411
+ };
412
+ const pad = (s, n) => s.padEnd(n);
413
+ emit.text(`${pad("NAME", w.name)} ${pad("STATE", w.state)} ${pad("URL", w.url)} ${pad("FORWARDING", w.fwd)} ${pad("PORT", w.port)} UPTIME\n`);
414
+ for (const r of rows) {
415
+ emit.text(`${pad(r.name, w.name)} ${pad(r.state, w.state)} ${pad(r.url, w.url)} ${pad(r.fwd, w.fwd)} ${pad(r.port, w.port)} ${r.up}\n`);
416
+ }
417
+ }
418
+ function logs(opts) {
419
+ const name = resolveName(opts.name);
420
+ const which = opts.cloudflared ? cfdLogFile(name) : gateLogFile(name);
421
+ const path = cachePath("tunnel", which);
422
+ if (!existsSync(path)) {
423
+ emit.error({ code: "tunnel_no_log", message: `No log file at ${path}` });
424
+ process.exit(1);
425
+ }
426
+ const args = opts.follow ? ["-f", path] : [path];
427
+ const r = spawnSync("tail", args, { stdio: "inherit" });
428
+ if (r.status !== null && r.status !== 0)
429
+ process.exit(r.status);
430
+ }
431
+ function allowList() {
432
+ const cfg = readConfig();
433
+ if (cfg.allowed_ips.length === 0) {
434
+ emit.text("(empty)\n");
435
+ return;
436
+ }
437
+ for (const ip of cfg.allowed_ips)
438
+ emit.text(`${ip}\n`);
439
+ }
440
+ function allowAdd(ip) {
441
+ const cfg = readConfig();
442
+ if (cfg.allowed_ips.includes(ip)) {
443
+ emit.text(`${ip} already in allowlist.\n`);
444
+ return;
445
+ }
446
+ cfg.allowed_ips.push(ip);
447
+ writeConfig(cfg);
448
+ emit.text(`Added ${ip}.\n`);
449
+ const up = listStates();
450
+ if (up.length > 0) {
451
+ emit.text(`Allowlist is shared across all tunnels; restart each to apply (${up.map((s) => s.name).join(", ")}).\n`);
452
+ }
453
+ }
454
+ function allowRm(ip) {
455
+ const cfg = readConfig();
456
+ const idx = cfg.allowed_ips.indexOf(ip);
457
+ if (idx === -1) {
458
+ emit.text(`${ip} not in allowlist.\n`);
459
+ return;
460
+ }
461
+ cfg.allowed_ips.splice(idx, 1);
462
+ writeConfig(cfg);
463
+ emit.text(`Removed ${ip}.\n`);
464
+ const up = listStates();
465
+ if (up.length > 0) {
466
+ emit.text(`Allowlist is shared across all tunnels; restart each to apply (${up.map((s) => s.name).join(", ")}).\n`);
467
+ }
468
+ }
469
+ let emit;
470
+ let context;
471
+ export function registerTunnelCommand(program, emitParam, contextParam) {
472
+ emit = emitParam;
473
+ context = contextParam;
474
+ const cmd = program
475
+ .command("tunnel")
476
+ .description("IP-gated Cloudflare quick tunnel(s) in front of a local upstream (default upstream: 127.0.0.1:8001). " +
477
+ "Run several at once with --name <instance>.");
478
+ cmd
479
+ .command("up")
480
+ .description("Start a gate + cloudflared tunnel (one per --name instance)")
481
+ .option("--name <name>", "instance name; run multiple tunnels side by side", DEFAULT_INSTANCE)
482
+ .option("--target <addr>", "upstream to forward to", DEFAULT_TARGET)
483
+ .option("--vhost <host>", "Host header sent to the upstream (default: the consumer's configured " +
484
+ "default, else localhost).")
485
+ .option("--gate-port <port>", "local port the gate binds to (default: auto-allocate the first free port from 9001)")
486
+ .action(up);
487
+ cmd
488
+ .command("down")
489
+ .description("Stop a tunnel (default instance, --name <instance>, or --all)")
490
+ .option("--name <name>", "instance to stop", DEFAULT_INSTANCE)
491
+ .option("--all", "stop every running tunnel")
492
+ .action(down);
493
+ cmd
494
+ .command("status")
495
+ .description("Show tunnel state: a table of all instances, or detail for one via --name")
496
+ .option("--name <name>", "show detail for a single instance")
497
+ .action(status);
498
+ cmd
499
+ .command("logs")
500
+ .description("Tail the gate log (default) or cloudflared log for an instance")
501
+ .option("--name <name>", "instance whose log to tail", DEFAULT_INSTANCE)
502
+ .option("-f, --follow", "follow the log")
503
+ .option("--gate", "tail the gate log (default)")
504
+ .option("--cloudflared", "tail the cloudflared log instead")
505
+ .action(logs);
506
+ const allow = cmd
507
+ .command("allow")
508
+ .description("Manage the CF-Connecting-IP allowlist")
509
+ .action(allowList);
510
+ allow.command("add <ip>").description("Add an IP to the allowlist").action(allowAdd);
511
+ allow.command("rm <ip>").description("Remove an IP from the allowlist").action(allowRm);
512
+ allow.command("list").description("List allowed IPs (default action)").action(allowList);
513
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `harn uninstall`: reverse what `harn init` wired into a project.
3
+ *
4
+ * `init` makes two kinds of change outside the harnery package:
5
+ * 1. Merges `agent-hook` entries into the harness settings file
6
+ * (Claude Code `.claude/settings.json`, Cursor `.cursor/hooks.json`, or
7
+ * Codex `.codex/hooks.json`).
8
+ * 2. Creates the `.harnery/` coord root (runtime state: events, councils,
9
+ * identities, scratch) and stamps the host bin name into
10
+ * `.harnery/config.jsonc`.
11
+ *
12
+ * `uninstall` undoes (1) by default: it removes only harnery's hook entries from
13
+ * the settings file, preserving any other hooks the consumer added, and deletes
14
+ * the settings file outright when it's left harnery-only. It does NOT touch the
15
+ * `.harnery/` coord root unless `--purge-state` is passed, because that directory
16
+ * holds session history a consumer may want to keep. Idempotent + `--dry-run`,
17
+ * mirroring `init`.
18
+ */
19
+ import type { Command } from "commander";
20
+ import type { EmitContext } from "../commander.js";
21
+ export declare function registerUninstallCommand(program: Command, emit: EmitContext): void;
22
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAWnD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAoFlF"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `harn uninstall`: reverse what `harn init` wired into a project.
3
+ *
4
+ * `init` makes two kinds of change outside the harnery package:
5
+ * 1. Merges `agent-hook` entries into the harness settings file
6
+ * (Claude Code `.claude/settings.json`, Cursor `.cursor/hooks.json`, or
7
+ * Codex `.codex/hooks.json`).
8
+ * 2. Creates the `.harnery/` coord root (runtime state: events, councils,
9
+ * identities, scratch) and stamps the host bin name into
10
+ * `.harnery/config.jsonc`.
11
+ *
12
+ * `uninstall` undoes (1) by default: it removes only harnery's hook entries from
13
+ * the settings file, preserving any other hooks the consumer added, and deletes
14
+ * the settings file outright when it's left harnery-only. It does NOT touch the
15
+ * `.harnery/` coord root unless `--purge-state` is passed, because that directory
16
+ * holds session history a consumer may want to keep. Idempotent + `--dry-run`,
17
+ * mirroring `init`.
18
+ */
19
+ import { spawnSync } from "node:child_process";
20
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
21
+ import { relative, resolve } from "node:path";
22
+ import { HARNESS_SPECS } from "../core/hooks/harness/events.js";
23
+ import { unwireHooks } from "./init.js";
24
+ export function registerUninstallCommand(program, emit) {
25
+ program
26
+ .command("uninstall")
27
+ .description("Reverse `harn init`: remove harnery's hook entries from the harness " +
28
+ "settings file (keeps any others). Pass --purge-state to also delete the " +
29
+ ".harnery/ coord root. Idempotent; use --dry-run to preview.")
30
+ .option("--harness <id>", "claude-code | cursor | codex", "claude-code")
31
+ .option("--dry-run", "Show what would change without writing")
32
+ .option("--project-root <path>", "Project root (default: git toplevel, else cwd)")
33
+ .option("--purge-state", "Also delete the .harnery/ coord root (runtime state, destructive)")
34
+ .action((opts) => {
35
+ const harness = opts.harness;
36
+ const spec = HARNESS_SPECS[harness];
37
+ if (!spec) {
38
+ emit.text(`Unknown harness '${opts.harness}'. Expected: claude-code | cursor | codex.`);
39
+ emit.setExitCode(1);
40
+ return;
41
+ }
42
+ const projectRoot = resolve(opts.projectRoot ?? gitTopLevel() ?? process.cwd());
43
+ const dryRun = opts.dryRun === true;
44
+ const actions = [];
45
+ // ── 1. unwire harness hooks ────────────────────────────────────────────
46
+ const settingsPath = resolve(projectRoot, spec.settingsFile);
47
+ if (!existsSync(settingsPath)) {
48
+ actions.push(`· ${rel(projectRoot, settingsPath)} doesn't exist; no hooks to remove`);
49
+ }
50
+ else {
51
+ let settings;
52
+ try {
53
+ settings = JSON.parse(readFileSync(settingsPath, "utf8"));
54
+ }
55
+ catch (err) {
56
+ emit.text(`✗ ${rel(projectRoot, settingsPath)} exists but isn't valid JSON; refusing to ` +
57
+ `touch it. Fix it and re-run.\n (${err.message})`);
58
+ emit.setExitCode(1);
59
+ return;
60
+ }
61
+ const { removed, remaining } = unwireHooks(settings);
62
+ if (removed === 0) {
63
+ actions.push(`· no harnery hooks found in ${rel(projectRoot, settingsPath)}`);
64
+ }
65
+ else if (harnessOnly(settings)) {
66
+ // Nothing left but what init itself put there (no hooks, at most a
67
+ // version key), so remove the file rather than leave an empty shell.
68
+ if (dryRun) {
69
+ actions.push(`+ would remove ${rel(projectRoot, settingsPath)} (now harnery-only)`);
70
+ }
71
+ else {
72
+ rmSync(settingsPath);
73
+ actions.push(`+ removed ${rel(projectRoot, settingsPath)} (was harnery-only)`);
74
+ }
75
+ }
76
+ else if (dryRun) {
77
+ actions.push(`+ would remove ${removed} harnery hook(s) from ${rel(projectRoot, settingsPath)} ` +
78
+ `(${remaining} other hook(s) kept)`);
79
+ }
80
+ else {
81
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
82
+ actions.push(`+ removed ${removed} harnery hook(s) from ${rel(projectRoot, settingsPath)} ` +
83
+ `(${remaining} other hook(s) kept)`);
84
+ }
85
+ }
86
+ // ── 2. coord root (opt-in; destructive) ────────────────────────────────
87
+ const coordDir = resolve(projectRoot, ".harnery");
88
+ if (opts.purgeState) {
89
+ if (!existsSync(coordDir)) {
90
+ actions.push("· .harnery/ doesn't exist; nothing to purge");
91
+ }
92
+ else if (dryRun) {
93
+ actions.push("+ would delete .harnery/ and all coord state (events, councils, scratch)");
94
+ }
95
+ else {
96
+ rmSync(coordDir, { recursive: true, force: true });
97
+ actions.push("+ deleted .harnery/ and all coord state");
98
+ }
99
+ }
100
+ else if (existsSync(coordDir)) {
101
+ actions.push("· left .harnery/ coord root in place (pass --purge-state to delete it)");
102
+ }
103
+ emit.text(render(projectRoot, dryRun, actions));
104
+ });
105
+ }
106
+ /** True when an unwired settings object holds nothing but (optionally) `version`. */
107
+ function harnessOnly(settings) {
108
+ const keys = Object.keys(settings);
109
+ return keys.length === 0 || (keys.length === 1 && keys[0] === "version");
110
+ }
111
+ function render(projectRoot, dryRun, actions) {
112
+ const head = dryRun ? "harn uninstall (dry run): no changes written" : "harn uninstall";
113
+ const tail = dryRun
114
+ ? "\nRe-run without --dry-run to apply."
115
+ : "\nDone. harnery hooks are unwired; restart your harness session to drop them.";
116
+ return `${head}\n root: ${projectRoot}\n${actions.map((a) => ` ${a}`).join("\n")}${tail}`;
117
+ }
118
+ function gitTopLevel() {
119
+ const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
120
+ const out = r.status === 0 ? r.stdout.trim() : "";
121
+ return out || null;
122
+ }
123
+ function rel(root, p) {
124
+ const r = relative(root, p);
125
+ return r || p;
126
+ }
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ export declare function registerWebCommand(program: Command, emit: EmitContext): void;
4
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/commands/web.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAsDnD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAqI5E"}