harnery 0.31.6 → 0.33.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 (264) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +5 -1
  4. package/dist/commands/agents.d.ts +0 -9
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +280 -94
  7. package/dist/commands/browse-session.d.ts +21 -0
  8. package/dist/commands/browse-session.d.ts.map +1 -0
  9. package/dist/commands/browse-session.js +157 -0
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +205 -37
  12. package/dist/commands/checkpoint.js +1 -1
  13. package/dist/commands/claude-desktop.d.ts +2 -2
  14. package/dist/commands/claude-desktop.d.ts.map +1 -1
  15. package/dist/commands/claude-desktop.js +74 -3
  16. package/dist/commands/deinit.d.ts.map +1 -1
  17. package/dist/commands/deinit.js +4 -0
  18. package/dist/commands/docs.d.ts.map +1 -1
  19. package/dist/commands/docs.js +40 -0
  20. package/dist/commands/doctor.d.ts +4 -2
  21. package/dist/commands/doctor.d.ts.map +1 -1
  22. package/dist/commands/doctor.js +141 -19
  23. package/dist/commands/init.d.ts +2 -2
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +26 -5
  26. package/dist/commands/instructions.d.ts +4 -0
  27. package/dist/commands/instructions.d.ts.map +1 -0
  28. package/dist/commands/instructions.js +100 -0
  29. package/dist/commands/presence.d.ts.map +1 -1
  30. package/dist/commands/presence.js +4 -1
  31. package/dist/commands/tunnel.d.ts.map +1 -1
  32. package/dist/commands/tunnel.js +2 -3
  33. package/dist/core/agents/canonical-emit.d.ts +2 -3
  34. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  35. package/dist/core/agents/canonical-emit.js +6 -5
  36. package/dist/core/agents/cli.js +128 -89
  37. package/dist/core/agents/coord-client.d.ts +18 -3
  38. package/dist/core/agents/coord-client.d.ts.map +1 -1
  39. package/dist/core/agents/coord-client.js +5 -4
  40. package/dist/core/agents/finalization.d.ts +68 -0
  41. package/dist/core/agents/finalization.d.ts.map +1 -0
  42. package/dist/core/agents/finalization.js +443 -0
  43. package/dist/core/agents/git-hook.d.ts +51 -0
  44. package/dist/core/agents/git-hook.d.ts.map +1 -0
  45. package/dist/core/agents/git-hook.js +118 -0
  46. package/dist/core/agents/index.d.ts +1 -0
  47. package/dist/core/agents/index.d.ts.map +1 -1
  48. package/dist/core/agents/index.js +1 -0
  49. package/dist/core/agents/render/prompt-context.d.ts +12 -5
  50. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  51. package/dist/core/agents/render/prompt-context.js +70 -16
  52. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  53. package/dist/core/agents/render/session-context.js +23 -4
  54. package/dist/core/agents/rules/claim-conflict.js +3 -3
  55. package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
  56. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  57. package/dist/core/agents/rules/commit-conflict.js +21 -5
  58. package/dist/core/agents/rules/stop-hook.d.ts +3 -0
  59. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  60. package/dist/core/agents/rules/stop-hook.js +59 -23
  61. package/dist/core/agents/session-events.d.ts +8 -16
  62. package/dist/core/agents/session-events.d.ts.map +1 -1
  63. package/dist/core/agents/session-events.js +12 -26
  64. package/dist/core/agents/state/heartbeat-projector.d.ts +14 -0
  65. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  66. package/dist/core/agents/state/heartbeat-projector.js +98 -1
  67. package/dist/core/agents/state/heartbeat-writer.d.ts +47 -5
  68. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  69. package/dist/core/agents/state/heartbeat-writer.js +86 -17
  70. package/dist/core/agents/state/names.d.ts +22 -1
  71. package/dist/core/agents/state/names.d.ts.map +1 -1
  72. package/dist/core/agents/state/names.js +40 -2
  73. package/dist/core/agents/state/session-state.d.ts +44 -0
  74. package/dist/core/agents/state/session-state.d.ts.map +1 -0
  75. package/dist/core/agents/state/session-state.js +81 -0
  76. package/dist/core/config.d.ts +30 -2
  77. package/dist/core/config.d.ts.map +1 -1
  78. package/dist/core/config.js +74 -11
  79. package/dist/core/governor/planning.d.ts.map +1 -1
  80. package/dist/core/governor/planning.js +12 -13
  81. package/dist/core/hooks/adapter/detect.d.ts +2 -4
  82. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  83. package/dist/core/hooks/adapter/detect.js +4 -14
  84. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  85. package/dist/core/hooks/adapter/events.js +1 -0
  86. package/dist/core/hooks/adapter/output.d.ts +1 -1
  87. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  88. package/dist/core/hooks/adapter/output.js +5 -4
  89. package/dist/core/hooks/adapter/parse.d.ts +1 -1
  90. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  91. package/dist/core/hooks/adapter/parse.js +2 -0
  92. package/dist/core/hooks/adapter/wiring.d.ts +23 -0
  93. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  94. package/dist/core/hooks/adapter/wiring.js +32 -0
  95. package/dist/core/hooks/cli.d.ts +1 -2
  96. package/dist/core/hooks/cli.d.ts.map +1 -1
  97. package/dist/core/hooks/cli.js +258 -32
  98. package/dist/core/hooks/codex-authorization.d.ts +30 -0
  99. package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
  100. package/dist/core/hooks/codex-authorization.js +199 -0
  101. package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
  102. package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
  103. package/dist/core/hooks/codex-wsl-bridge.js +137 -0
  104. package/dist/core/hooks/effects/index.d.ts +1 -1
  105. package/dist/core/hooks/effects/index.js +3 -3
  106. package/dist/core/hooks/events/schema.d.ts +57 -1
  107. package/dist/core/hooks/events/schema.d.ts.map +1 -1
  108. package/dist/core/hooks/resolve/owner.d.ts +7 -1
  109. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  110. package/dist/core/hooks/resolve/owner.js +15 -0
  111. package/dist/core/hooks/resolve/transcript.d.ts +44 -0
  112. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  113. package/dist/core/hooks/resolve/transcript.js +176 -1
  114. package/dist/core/hooks/session-name-presence.d.ts +30 -0
  115. package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
  116. package/dist/core/hooks/session-name-presence.js +43 -0
  117. package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
  118. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
  119. package/dist/core/hooks/unsafe-cross-shell.js +151 -0
  120. package/dist/core/presence/blob.d.ts +4 -0
  121. package/dist/core/presence/blob.d.ts.map +1 -1
  122. package/dist/core/presence/blob.js +12 -0
  123. package/dist/core/presence/index.js +15 -1
  124. package/dist/core/work/state.d.ts +4 -5
  125. package/dist/core/work/state.d.ts.map +1 -1
  126. package/dist/core/work/state.js +5 -8
  127. package/dist/core/workflow/index.d.ts +1 -1
  128. package/dist/core/workflow/index.d.ts.map +1 -1
  129. package/dist/core/workflow/proof.d.ts +2 -2
  130. package/dist/core/workflow/proof.d.ts.map +1 -1
  131. package/dist/core/workflow/proof.js +2 -2
  132. package/dist/core/workflow/run-state.d.ts +2 -2
  133. package/dist/core/workflow/run-state.d.ts.map +1 -1
  134. package/dist/core/workflow/run-state.js +2 -2
  135. package/dist/core/workflow/types.d.ts +3 -3
  136. package/dist/core/workflow/types.d.ts.map +1 -1
  137. package/dist/core/workflow/workspaces/execution.d.ts +2 -2
  138. package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
  139. package/dist/core/workflow/workspaces/execution.js +5 -0
  140. package/dist/core/workflow/workspaces/index.d.ts +1 -1
  141. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  142. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  143. package/dist/core/workflow/workspaces/local-git.js +1 -2
  144. package/dist/core/workflow/workspaces/paths.d.ts +2 -2
  145. package/dist/core/workflow/workspaces/types.d.ts +0 -6
  146. package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
  147. package/dist/core/workflow/workspaces/validate.d.ts +0 -2
  148. package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
  149. package/dist/core/workflow/workspaces/validate.js +0 -2
  150. package/dist/lib/browser/client.d.ts +118 -1
  151. package/dist/lib/browser/client.d.ts.map +1 -1
  152. package/dist/lib/browser/client.js +435 -6
  153. package/dist/lib/browser/geometry.d.ts.map +1 -1
  154. package/dist/lib/browser/geometry.js +180 -37
  155. package/dist/lib/browser/index.d.ts +5 -1
  156. package/dist/lib/browser/index.d.ts.map +1 -1
  157. package/dist/lib/browser/index.js +5 -1
  158. package/dist/lib/browser/netscape-cookies.d.ts +6 -0
  159. package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
  160. package/dist/lib/browser/netscape-cookies.js +31 -0
  161. package/dist/lib/browser/proxy.d.ts +24 -0
  162. package/dist/lib/browser/proxy.d.ts.map +1 -0
  163. package/dist/lib/browser/proxy.js +84 -0
  164. package/dist/lib/browser/runts.d.ts +6 -0
  165. package/dist/lib/browser/runts.d.ts.map +1 -1
  166. package/dist/lib/browser/runts.js +21 -4
  167. package/dist/lib/browser/session-control.d.ts +112 -0
  168. package/dist/lib/browser/session-control.d.ts.map +1 -0
  169. package/dist/lib/browser/session-control.js +670 -0
  170. package/dist/lib/claude-desktop.d.ts +47 -0
  171. package/dist/lib/claude-desktop.d.ts.map +1 -1
  172. package/dist/lib/claude-desktop.js +169 -1
  173. package/dist/lib/docs-links.d.ts +108 -0
  174. package/dist/lib/docs-links.d.ts.map +1 -0
  175. package/dist/lib/docs-links.js +555 -0
  176. package/dist/lib/exec.d.ts +1 -1
  177. package/dist/lib/exec.js +1 -1
  178. package/dist/lib/identities/assume.d.ts +4 -2
  179. package/dist/lib/identities/assume.d.ts.map +1 -1
  180. package/dist/lib/identities/assume.js +15 -2
  181. package/dist/lib/instructions/bundle.d.ts +33 -0
  182. package/dist/lib/instructions/bundle.d.ts.map +1 -0
  183. package/dist/lib/instructions/bundle.js +149 -0
  184. package/dist/lib/instructions/git-hooks.d.ts +75 -0
  185. package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
  186. package/dist/lib/instructions/git-hooks.js +238 -0
  187. package/dist/lib/instructions/splice.d.ts +10 -4
  188. package/dist/lib/instructions/splice.d.ts.map +1 -1
  189. package/dist/lib/instructions/splice.js +19 -11
  190. package/package.json +2 -2
  191. package/schemas/config.schema.json +32 -0
  192. package/src/commander.ts +5 -1
  193. package/src/commands/agents.ts +341 -113
  194. package/src/commands/browse-session.ts +245 -0
  195. package/src/commands/browse.ts +287 -44
  196. package/src/commands/checkpoint.ts +1 -1
  197. package/src/commands/claude-desktop.ts +106 -5
  198. package/src/commands/deinit.ts +7 -1
  199. package/src/commands/docs.ts +56 -0
  200. package/src/commands/doctor.ts +155 -21
  201. package/src/commands/init.ts +28 -9
  202. package/src/commands/instructions.ts +129 -0
  203. package/src/commands/presence.ts +7 -1
  204. package/src/commands/tunnel.ts +2 -5
  205. package/src/core/agents/canonical-emit.ts +7 -6
  206. package/src/core/agents/cli.ts +143 -89
  207. package/src/core/agents/coord-client.ts +21 -5
  208. package/src/core/agents/finalization.ts +595 -0
  209. package/src/core/agents/git-hook.ts +126 -0
  210. package/src/core/agents/index.ts +1 -0
  211. package/src/core/agents/render/prompt-context.ts +96 -16
  212. package/src/core/agents/render/session-context.ts +29 -4
  213. package/src/core/agents/rules/claim-conflict.ts +3 -3
  214. package/src/core/agents/rules/commit-conflict.ts +35 -8
  215. package/src/core/agents/rules/stop-hook.ts +71 -23
  216. package/src/core/agents/session-events.ts +14 -41
  217. package/src/core/agents/state/heartbeat-projector.ts +108 -1
  218. package/src/core/agents/state/heartbeat-writer.ts +129 -16
  219. package/src/core/agents/state/names.ts +54 -2
  220. package/src/core/agents/state/session-state.ts +131 -0
  221. package/src/core/config.ts +92 -12
  222. package/src/core/governor/planning.ts +17 -12
  223. package/src/core/hooks/adapter/detect.ts +4 -13
  224. package/src/core/hooks/adapter/events.ts +1 -0
  225. package/src/core/hooks/adapter/output.ts +9 -4
  226. package/src/core/hooks/adapter/parse.ts +3 -0
  227. package/src/core/hooks/adapter/wiring.ts +43 -0
  228. package/src/core/hooks/cli.ts +296 -27
  229. package/src/core/hooks/codex-authorization.ts +253 -0
  230. package/src/core/hooks/codex-wsl-bridge.ts +188 -0
  231. package/src/core/hooks/effects/index.ts +5 -5
  232. package/src/core/hooks/events/schema.ts +71 -0
  233. package/src/core/hooks/resolve/owner.ts +22 -1
  234. package/src/core/hooks/resolve/transcript.ts +164 -1
  235. package/src/core/hooks/session-name-presence.ts +52 -0
  236. package/src/core/hooks/unsafe-cross-shell.ts +160 -0
  237. package/src/core/presence/blob.ts +18 -0
  238. package/src/core/presence/index.ts +18 -1
  239. package/src/core/work/state.ts +8 -12
  240. package/src/core/workflow/engine.ts +2 -2
  241. package/src/core/workflow/index.ts +0 -1
  242. package/src/core/workflow/proof.ts +4 -4
  243. package/src/core/workflow/run-state.ts +5 -5
  244. package/src/core/workflow/types.ts +3 -3
  245. package/src/core/workflow/workspaces/execution.ts +11 -4
  246. package/src/core/workflow/workspaces/index.ts +0 -3
  247. package/src/core/workflow/workspaces/local-git.ts +1 -2
  248. package/src/core/workflow/workspaces/paths.ts +2 -2
  249. package/src/core/workflow/workspaces/types.ts +0 -9
  250. package/src/core/workflow/workspaces/validate.ts +1 -5
  251. package/src/lib/browser/client.ts +528 -6
  252. package/src/lib/browser/geometry.ts +197 -37
  253. package/src/lib/browser/index.ts +39 -0
  254. package/src/lib/browser/netscape-cookies.ts +39 -0
  255. package/src/lib/browser/proxy.ts +105 -0
  256. package/src/lib/browser/runts.ts +27 -3
  257. package/src/lib/browser/session-control.ts +892 -0
  258. package/src/lib/claude-desktop.ts +232 -1
  259. package/src/lib/docs-links.ts +674 -0
  260. package/src/lib/exec.ts +1 -1
  261. package/src/lib/identities/assume.ts +22 -1
  262. package/src/lib/instructions/bundle.ts +199 -0
  263. package/src/lib/instructions/git-hooks.ts +259 -0
  264. package/src/lib/instructions/splice.ts +41 -11
@@ -1,6 +1,7 @@
1
1
  import { initDocsContext as initDocs, scanDocs } from "../lib/docs.js";
2
2
  import { initDocsMigrationContext, runFrontmatterMigration, } from "../lib/docs-frontmatter-migrate.js";
3
3
  import { initDocsContext as initDocsIndex, runIndex } from "../lib/docs-index.js";
4
+ import { initDocsContext as initDocsLinks, runLinks } from "../lib/docs-links.js";
4
5
  import { initDocsContext as initDocsLint, runLint } from "../lib/docs-lint.js";
5
6
  import { readDocsMetadata, readDocsMetadataKey } from "../lib/docs-meta.js";
6
7
  import { countColdHandoffs, initDocsContext as initDocsSweep, runSweep, } from "../lib/docs-sweep.js";
@@ -17,6 +18,7 @@ function ensureContext(context) {
17
18
  extraExcludedPrefixes: context.extraDocsExcludedPrefixes,
18
19
  docsRootAllowlist: context.docsRootAllowlist,
19
20
  });
21
+ initDocsLinks({ ...opts, extraExcludedPrefixes: context.extraDocsExcludedPrefixes });
20
22
  initDocsSweep(opts);
21
23
  }
22
24
  let emit;
@@ -89,6 +91,25 @@ export function registerDocsCommand(program, emitParam, context) {
89
91
  emit.error({ code: "docs_error", message: msg });
90
92
  }
91
93
  });
94
+ docs
95
+ .command("links")
96
+ .description("Check internal Markdown link targets and heading fragments")
97
+ .option("--repo <name>", "Limit to one submodule or '.' for parent")
98
+ .option("--no-fragments", "Check target existence only; skip heading-fragment validation")
99
+ .option("--strict", "Report findings in archive/audit/changelog docs as errors, not warnings")
100
+ .option("--check-escapes", "Also flag links that resolve outside their own repo root")
101
+ .option("--fail", "Exit non-zero when errors are found (advisory by default)")
102
+ .option("--format <type>", "Output format: human, json", "human")
103
+ .action(async (opts) => {
104
+ try {
105
+ ensureContext(context);
106
+ await handleLinks(opts);
107
+ }
108
+ catch (err) {
109
+ const msg = err instanceof Error ? err.message : String(err);
110
+ emit.error({ code: "docs_error", message: msg });
111
+ }
112
+ });
92
113
  docs
93
114
  .command("sweep")
94
115
  .description("Surface stalled lifecycle states: stalled plans, cold issues, unverified runbooks")
@@ -185,6 +206,25 @@ async function handleLint(opts) {
185
206
  if (errors.length > 0)
186
207
  emit.setExitCode(1);
187
208
  }
209
+ // --- `harn docs links` ---
210
+ /**
211
+ * Advisory by default: a clean exit code even with findings, because turning
212
+ * link health into a gate is an enforcement policy decision for the host, not
213
+ * something this command should assume. `--fail` is the opt-in for CI or a hook.
214
+ */
215
+ async function handleLinks(opts) {
216
+ if (opts.format === "json")
217
+ emit.config({ format: "json" });
218
+ const report = await runLinks({
219
+ repo: opts.repo,
220
+ noFragments: opts.fragments === false,
221
+ strict: opts.strict,
222
+ checkEscapes: opts.checkEscapes,
223
+ });
224
+ emit.data({ ...report, advisory: !opts.fail });
225
+ if (opts.fail && report.error_count > 0)
226
+ emit.setExitCode(1);
227
+ }
188
228
  // --- `harn docs sweep` ---
189
229
  async function handleSweep(opts) {
190
230
  if (opts.format === "json")
@@ -12,14 +12,16 @@
12
12
  */
13
13
  import type { Command } from "commander";
14
14
  import type { EmitContext } from "../commander.js";
15
+ import { type CodexHookAuthorizationResult } from "../core/hooks/codex-authorization.js";
15
16
  type Severity = "ok" | "warn" | "fail";
16
- interface Check {
17
+ export interface Check {
17
18
  name: string;
18
19
  severity: Severity;
19
20
  detail: string;
20
21
  hint?: string;
21
22
  }
22
23
  export declare function registerDoctorCommand(program: Command, emit: EmitContext): void;
23
- export declare function runChecks(): Check[];
24
+ export declare function runChecks(): Promise<Check[]>;
25
+ export declare function codexAuthorizationCheck(result: CodexHookAuthorizationResult): Check;
24
26
  export {};
25
27
  //# sourceMappingURL=doctor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAanD,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAgB,SAAS,IAAI,KAAK,EAAE,CAcnC"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAe9C,KAAK,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAOD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/E;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAyBlD;AA4CD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAoBnF"}
@@ -16,9 +16,13 @@ import os from "node:os";
16
16
  import path from "node:path";
17
17
  import { BUILTIN_ADAPTER_IDS } from "../core/adapters/index.js";
18
18
  import { resolveBinName, ripgrepAutoInstall } from "../core/config.js";
19
- import { loadAdapterWiring } from "../core/hooks/adapter/wiring.js";
19
+ import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
20
+ import { loadAdapterWiring, summarizeAdapterWiring } from "../core/hooks/adapter/wiring.js";
21
+ import { probeCodexHookAuthorization, } from "../core/hooks/codex-authorization.js";
22
+ import { inspectCodexWslBridge, } from "../core/hooks/codex-wsl-bridge.js";
20
23
  import { ADAPTER_BINARIES, ADAPTER_INSTALL_HINTS, ADAPTER_LOGIN_HINTS, } from "../core/workflow/adapters.js";
21
24
  import { probeBilling } from "../core/workflow/billing.js";
25
+ import { checkGitHooks, gitHooksInstalled } from "../lib/instructions/git-hooks.js";
22
26
  import { findRg, installRg, managedRgPath, rgInstallSupported } from "../lib/tools/ripgrep.js";
23
27
  export function registerDoctorCommand(program, emit) {
24
28
  program
@@ -36,7 +40,7 @@ export function registerDoctorCommand(program, emit) {
36
40
  emit.text(`ripgrep install failed: ${err.message}\n`);
37
41
  }
38
42
  }
39
- const checks = runChecks();
43
+ const checks = await runChecks();
40
44
  const requiredFailed = checks.some((c) => c.severity === "fail");
41
45
  if (opts.json) {
42
46
  emit.data({
@@ -64,21 +68,109 @@ export function registerDoctorCommand(program, emit) {
64
68
  emit.setExitCode(requiredFailed ? 1 : 0);
65
69
  });
66
70
  }
67
- export function runChecks() {
71
+ export async function runChecks() {
72
+ const codexWslStatus = inspectCodexWslBridge();
73
+ const codexWslBridge = checkCodexWslBridge(codexWslStatus);
74
+ // Probe the adapter CLIs before the hook check runs, not in output order:
75
+ // an unwired adapter only matters if its CLI is actually installed, and
76
+ // probing once here keeps that join to one spawn per CLI.
77
+ const workflow = BUILTIN_ADAPTER_IDS.map((id) => ({ id, ...checkWorkflowAdapter(id) }));
78
+ const installed = workflow.filter((w) => w.installed).map((w) => w.id);
79
+ const codexAuthorization = await checkCodexHookAuthorization(installed, codexWslStatus);
68
80
  return [
69
81
  checkNode(),
70
82
  checkGit(),
71
83
  checkBun(),
72
84
  checkRipgrep(),
73
85
  checkHarneryDir(),
74
- checkAdapterHooks(),
75
- ...BUILTIN_ADAPTER_IDS.map(checkWorkflowAdapter),
86
+ checkAdapterHooks(installed),
87
+ ...(codexAuthorization ? [codexAuthorization] : []),
88
+ checkGitHookRegions(),
89
+ ...(codexWslBridge ? [codexWslBridge] : []),
90
+ ...workflow.map((w) => w.check),
76
91
  checkRestic(),
77
92
  checkRclone(),
78
93
  checkPlaywright(),
79
94
  checkPython(),
80
95
  ];
81
96
  }
97
+ function checkGitHookRegions() {
98
+ const root = findCoordProjectRoot();
99
+ if (!root) {
100
+ return { name: "git hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
101
+ }
102
+ try {
103
+ if (!gitHooksInstalled(root)) {
104
+ const bin = resolveBinName(root);
105
+ return {
106
+ name: "git hooks",
107
+ severity: "warn",
108
+ detail: `not installed - commits are not guarded against peer-claim conflicts and ` +
109
+ `claims never auto-release on commit/checkout; run \`${bin} init\` to install`,
110
+ };
111
+ }
112
+ const { status, issues } = checkGitHooks(root);
113
+ if (status === "fresh") {
114
+ return { name: "git hooks", severity: "ok", detail: "managed regions current" };
115
+ }
116
+ return { name: "git hooks", severity: "warn", detail: issues.join("; ") };
117
+ }
118
+ catch (err) {
119
+ return {
120
+ name: "git hooks",
121
+ severity: "warn",
122
+ detail: `check failed: ${err.message}`,
123
+ };
124
+ }
125
+ }
126
+ function checkCodexWslBridge(status) {
127
+ if (!status)
128
+ return null;
129
+ return {
130
+ name: "codex:WSL bridge",
131
+ severity: status.ok ? "ok" : "warn",
132
+ detail: status.detail,
133
+ hint: status.ok
134
+ ? undefined
135
+ : "forward CODEX_THREAD_ID through WSLENV in the Codex shell environment policy, then start a fresh task",
136
+ };
137
+ }
138
+ export function codexAuthorizationCheck(result) {
139
+ const reviewHint = "terminal UI: run `/hooks`; Codex Desktop: open Settings > Hooks; after approval, start a fresh task";
140
+ if (result.status === "runnable") {
141
+ return { name: "codex:hook authorization", severity: "ok", detail: result.detail };
142
+ }
143
+ if (result.status === "review_required" || result.status === "disabled") {
144
+ return {
145
+ name: "codex:hook authorization",
146
+ severity: "warn",
147
+ detail: result.detail,
148
+ hint: reviewHint,
149
+ };
150
+ }
151
+ return {
152
+ name: "codex:hook authorization",
153
+ severity: "warn",
154
+ detail: `authorization unverified: ${result.detail}`,
155
+ hint: reviewHint,
156
+ };
157
+ }
158
+ async function checkCodexHookAuthorization(installedAdapters, codexWslStatus) {
159
+ const root = findCoordProjectRoot();
160
+ if (!root || !summarizeAdapterWiring(root).wired.includes("codex"))
161
+ return null;
162
+ if (codexWslStatus) {
163
+ return {
164
+ name: "codex:hook authorization",
165
+ severity: "warn",
166
+ detail: "unverified from WSL; the Windows-native Codex runtime owns this workspace's trust state",
167
+ hint: "run the host's Windows bridge doctor, or open Codex Desktop Settings > Hooks; start a fresh task after approval",
168
+ };
169
+ }
170
+ if (!installedAdapters.includes("codex"))
171
+ return null;
172
+ return codexAuthorizationCheck(await probeCodexHookAuthorization({ cwd: root }));
173
+ }
82
174
  function checkRipgrep() {
83
175
  const found = findRg();
84
176
  if (found) {
@@ -123,6 +215,9 @@ function whichVersion(bin, args = ["--version"]) {
123
215
  * One workflow spawn target: is the adapter CLI installed, and how will its
124
216
  * headless children bill (subscription login vs API key — see billing.ts)?
125
217
  * Missing is a warn, not a fail: workflows degrade to the adapters you have.
218
+ *
219
+ * Reports `installed` alongside the check so `checkAdapterHooks` can reuse the
220
+ * probe instead of spawning every adapter CLI a second time.
126
221
  */
127
222
  function checkWorkflowAdapter(adapter) {
128
223
  const bin = ADAPTER_BINARIES[adapter];
@@ -130,19 +225,25 @@ function checkWorkflowAdapter(adapter) {
130
225
  const r = whichVersion(bin);
131
226
  if (!r.ok) {
132
227
  return {
133
- name,
134
- severity: "warn",
135
- detail: `${bin} missing (workflow --adapter ${adapter} unavailable)`,
136
- hint: `${ADAPTER_INSTALL_HINTS[adapter]} then: ${ADAPTER_LOGIN_HINTS[adapter]}`,
228
+ check: {
229
+ name,
230
+ severity: "warn",
231
+ detail: `${bin} missing (workflow --adapter ${adapter} unavailable)`,
232
+ hint: `${ADAPTER_INSTALL_HINTS[adapter]} then: ${ADAPTER_LOGIN_HINTS[adapter]}`,
233
+ },
234
+ installed: false,
137
235
  };
138
236
  }
139
237
  const probe = probeBilling(adapter);
140
238
  if (probe.login === "absent" && !probe.apiKeyPresent) {
141
239
  return {
142
- name,
143
- severity: "warn",
144
- detail: `${r.out} — installed, but no stored login or API key detected`,
145
- hint: ADAPTER_LOGIN_HINTS[adapter],
240
+ check: {
241
+ name,
242
+ severity: "warn",
243
+ detail: `${r.out} — installed, but no stored login or API key detected`,
244
+ hint: ADAPTER_LOGIN_HINTS[adapter],
245
+ },
246
+ installed: true,
146
247
  };
147
248
  }
148
249
  const billing = probe.mode === "subscription"
@@ -150,7 +251,7 @@ function checkWorkflowAdapter(adapter) {
150
251
  ? "billing: subscription"
151
252
  : "billing: subscription (login unverifiable, CLI is the authority)"
152
253
  : `billing: ${probe.mode} (${probe.apiKeySource})`;
153
- return { name, severity: "ok", detail: `${r.out} — ${billing}` };
254
+ return { check: { name, severity: "ok", detail: `${r.out} — ${billing}` }, installed: true };
154
255
  }
155
256
  function checkNode() {
156
257
  const v = process.versions.node;
@@ -289,13 +390,21 @@ function checkHarneryDir() {
289
390
  * loadAdapterWiring — so a bare settings file never false-warns. The remedy is
290
391
  * always the same: re-run `<bin> init` (idempotent, additive).
291
392
  */
292
- function checkAdapterHooks() {
393
+ function checkAdapterHooks(installedAdapters = []) {
293
394
  const root = findCoordProjectRoot();
294
395
  if (!root) {
295
396
  return { name: "adapter hooks", severity: "ok", detail: "n/a (no .harnery/ above cwd)" };
296
397
  }
297
398
  const drift = loadAdapterWiring(root);
298
- if (drift.length === 0) {
399
+ // A adapter with zero harnery hooks is only worth flagging when the project
400
+ // plainly uses harnery hooks (something else is wired) AND that adapter's CLI
401
+ // is installed. Both conditions matter: the first keeps a project that has
402
+ // never run init quiet, the second keeps a project that simply doesn't have
403
+ // Codex quiet. What's left is the case that reads as healthy but isn't — an
404
+ // agent can start a session through that adapter and register nothing.
405
+ const summary = summarizeAdapterWiring(root);
406
+ const unwired = summary.wired.length > 0 ? summary.unwired.filter((id) => installedAdapters.includes(id)) : [];
407
+ if (drift.length === 0 && unwired.length === 0) {
299
408
  return { name: "adapter hooks", severity: "ok", detail: "wired + current" };
300
409
  }
301
410
  const bin = resolveBinName(root);
@@ -316,10 +425,23 @@ function checkAdapterHooks() {
316
425
  }
317
426
  return `${d.settingsFile}: ${bits.join("; ")}`;
318
427
  });
428
+ for (const id of unwired) {
429
+ parts.push(`${ADAPTER_SPECS[id].settingsFile}: ${id} CLI installed, no harnery hooks wired`);
430
+ }
319
431
  const needsManualRepair = drift.some((d) => d.parseError || d.invalidTopLevelKeys.length > 0 || d.invalidEventKeys.length > 0);
320
- const hint = needsManualRepair
321
- ? `repair the invalid adapter settings, then run \`${bin} init\` to migrate harnery hooks`
322
- : `run \`${bin} init\` to migrate the hook set (idempotent)`;
432
+ const wireHints = unwired.map((id) => `\`${bin} init --adapter ${id}\``);
433
+ let hint;
434
+ if (needsManualRepair) {
435
+ hint = `repair the invalid adapter settings, then run \`${bin} init\` to migrate harnery hooks`;
436
+ }
437
+ else if (drift.length > 0) {
438
+ hint = `run \`${bin} init\` to migrate the hook set (idempotent)`;
439
+ if (wireHints.length > 0)
440
+ hint += `; ${wireHints.join(" and ")} to wire the rest`;
441
+ }
442
+ else {
443
+ hint = `run ${wireHints.join(" and ")} (idempotent, additive)`;
444
+ }
323
445
  return { name: "adapter hooks", severity: "warn", detail: parts.join(" | "), hint };
324
446
  }
325
447
  function macOrLinux(mac, linux) {
@@ -18,9 +18,9 @@
18
18
  import type { Command } from "commander";
19
19
  import type { EmitContext } from "../commander.js";
20
20
  import { type AdapterId, type AdapterSpec } from "../core/hooks/adapter/events.js";
21
- import { type HookGroup, type SettingsFile } from "../core/hooks/adapter/wiring.js";
22
- export type { HookGroup, SettingsFile };
21
+ import { type SettingsFile } from "../core/hooks/adapter/wiring.js";
23
22
  export declare function registerInitCommand(program: Command, emit: EmitContext, binName?: string): void;
23
+ export declare function codexHookReviewAction(adapter: AdapterId): string | null;
24
24
  /**
25
25
  * Merge agent-hook entries into a adapter settings object in place, idempotently.
26
26
  * Preserves every existing hook; rewrites events already wired to
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAUzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAYxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CA4I/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvE;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkCxF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAmBzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAyJ/F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAOvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvE;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkCxF"}
@@ -23,6 +23,7 @@ import { DEFAULT_BIN_NAME, pinnedBinName, stripJsonComments } from "../core/conf
23
23
  import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
24
24
  import { commandWiresSubcommand, groupCommands, makeEntry, } from "../core/hooks/adapter/wiring.js";
25
25
  import { applyInstructions, checkInstructions, } from "../lib/instructions/apply.js";
26
+ import { applyGitHooks, checkGitHooks } from "../lib/instructions/git-hooks.js";
26
27
  import { HostAddendumError } from "../lib/instructions/host-addendum.js";
27
28
  // This file is src/commands/init.ts → harnery package root is two levels up.
28
29
  const HARNERY_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
@@ -55,14 +56,18 @@ export function registerInitCommand(program, emit, binName) {
55
56
  // ── --check: read-only drift report on the block + skills ──────────────
56
57
  if (opts.check === true) {
57
58
  const { status, issues } = checkInstructions(projectRoot, { binName: bin, adapter });
58
- const head = status === "fresh"
59
- ? "harn init --check: instructions + skills are current"
60
- : status === "drift"
59
+ const gitHooks = checkGitHooks(projectRoot);
60
+ if (gitHooks.status !== "fresh")
61
+ issues.push(...gitHooks.issues);
62
+ const merged = status === "error" ? "error" : gitHooks.status !== "fresh" ? "drift" : status;
63
+ const head = merged === "fresh"
64
+ ? "harn init --check: instructions + skills + git hooks are current"
65
+ : merged === "drift"
61
66
  ? "harn init --check: drift found (re-run `init` to refresh)"
62
67
  : "harn init --check: error";
63
68
  const lines = issues.length ? `\n${issues.map((i) => ` ✗ ${i}`).join("\n")}` : "";
64
69
  emit.text(`${head}${lines}`);
65
- emit.setExitCode(status === "fresh" ? 0 : status === "drift" ? 2 : 1);
70
+ emit.setExitCode(merged === "fresh" ? 0 : merged === "drift" ? 2 : 1);
66
71
  return;
67
72
  }
68
73
  const dryRun = opts.dryRun === true;
@@ -136,6 +141,9 @@ export function registerInitCommand(program, emit, binName) {
136
141
  actions.push(`+ wired ${wired} hook(s), upgraded ${upgraded} stale command(s), and removed ` +
137
142
  `${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
138
143
  }
144
+ const authorizationReview = codexHookReviewAction(adapter);
145
+ if (authorizationReview)
146
+ actions.push(authorizationReview);
139
147
  // ── 3. agent-facing instructions block + skills ────────────────────────
140
148
  // A misconfigured host addendum aborts here rather than half-writing: the
141
149
  // apply step validates the configured source before it touches a file, so
@@ -152,9 +160,22 @@ export function registerInitCommand(program, emit, binName) {
152
160
  return;
153
161
  }
154
162
  actions.push(...applied.actions);
155
- emit.text(render(projectRoot, dryRun, actions, applied.warnings));
163
+ // ── 4. git-hook managed regions ────────────────────────────────────────
164
+ // Same lifecycle contract as the instructions block: the coordination
165
+ // content of the consumer's git hooks is machine-owned and versioned;
166
+ // everything else in those files belongs to the host.
167
+ const gitHooks = applyGitHooks(projectRoot, { dryRun });
168
+ actions.push(...gitHooks.actions);
169
+ emit.text(render(projectRoot, dryRun, actions, [...applied.warnings, ...gitHooks.warnings]));
156
170
  });
157
171
  }
172
+ export function codexHookReviewAction(adapter) {
173
+ if (adapter !== "codex")
174
+ return null;
175
+ return ("! Codex authorizes hook commands separately. Before starting work, review them in " +
176
+ "the terminal UI with `/hooks` or in Codex Desktop under Settings > Hooks. " +
177
+ "After approval, start a fresh task so SessionStart can run.");
178
+ }
158
179
  /**
159
180
  * Merge agent-hook entries into a adapter settings object in place, idempotently.
160
181
  * Preserves every existing hook; rewrites events already wired to
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ export declare function registerInstructionsCommand(program: Command, emit: EmitContext): void;
4
+ //# sourceMappingURL=instructions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/commands/instructions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAcnD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmDrF"}
@@ -0,0 +1,100 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { countTokens } from "gpt-tokenizer/model/gpt-4o";
5
+ import { monorepoRoot } from "../core/agents/index.js";
6
+ import { buildInstructionBundle, } from "../lib/instructions/bundle.js";
7
+ export function registerInstructionsCommand(program, emit) {
8
+ const command = program
9
+ .command("instructions")
10
+ .description("Inspect and version the repo-authored instruction bundle loaded by each adapter.");
11
+ command
12
+ .command("manifest [paths...]")
13
+ .description("Emit a machine-readable bundle manifest for one or more working paths")
14
+ .option("--adapter <adapter>", "claude-code, cursor, codex, or all", "all")
15
+ .option("--format <type>", "Output format: json or table", "json")
16
+ .option("-o, --output <file>", "Write the complete JSON manifest to a file")
17
+ .action((paths, options) => {
18
+ if (options.format === "json")
19
+ emit.config({ format: "json" });
20
+ const coordRoot = monorepoRoot() ?? process.cwd();
21
+ const adapters = parseAdapters(options.adapter);
22
+ const profiles = (paths.length > 0 ? paths : [process.cwd()]).flatMap((path) => adapters.map((adapter) => enrichBundle(buildInstructionBundle({ coordRoot, cwd: resolve(path), adapter }), coordRoot)));
23
+ const manifest = {
24
+ ok: true,
25
+ schema_version: 1,
26
+ generated_at: new Date().toISOString(),
27
+ source_commit: gitHead(coordRoot),
28
+ tokenizer: "o200k_base",
29
+ coord_root: ".",
30
+ profile_count: profiles.length,
31
+ profiles,
32
+ };
33
+ if (options.output) {
34
+ const output = resolve(options.output);
35
+ writeFileSync(output, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
36
+ emit.data({
37
+ ok: true,
38
+ output,
39
+ schema_version: manifest.schema_version,
40
+ generated_at: manifest.generated_at,
41
+ source_commit: manifest.source_commit,
42
+ profile_count: manifest.profile_count,
43
+ });
44
+ }
45
+ else {
46
+ emit.data(manifest);
47
+ }
48
+ });
49
+ }
50
+ function parseAdapters(value) {
51
+ if (value === "all")
52
+ return ["claude-code", "cursor", "codex"];
53
+ if (value === "claude-code" || value === "cursor" || value === "codex")
54
+ return [value];
55
+ throw new Error(`unsupported adapter '${value}' (expected claude-code, cursor, codex, or all)`);
56
+ }
57
+ function enrichBundle(bundle, coordRoot) {
58
+ const components = bundle.components.map((component) => enrichComponent(component, coordRoot));
59
+ const canonicalSources = bundle.canonical_sources.map((component) => enrichComponent(component, coordRoot));
60
+ return {
61
+ ...bundle,
62
+ coord_root: ".",
63
+ components,
64
+ canonical_sources: canonicalSources,
65
+ totals: totals(components),
66
+ canonical_source_totals: totals(canonicalSources),
67
+ };
68
+ }
69
+ function enrichComponent(component, coordRoot) {
70
+ const bytes = readFileSync(resolve(coordRoot, component.path));
71
+ const text = bytes.includes(0) ? null : bytes.toString("utf8");
72
+ return {
73
+ ...component,
74
+ chars: text?.length ?? null,
75
+ tokens: text === null ? null : countTokens(text),
76
+ };
77
+ }
78
+ function totals(components) {
79
+ const roles = ["always_loaded", "just_in_time", "runtime_config"];
80
+ return {
81
+ files: components.length,
82
+ bytes: components.reduce((sum, component) => sum + component.bytes, 0),
83
+ tokens: components.reduce((sum, component) => sum + (component.tokens ?? 0), 0),
84
+ by_role: Object.fromEntries(roles.map((role) => {
85
+ const selected = components.filter((component) => component.role === role);
86
+ return [
87
+ role,
88
+ {
89
+ files: selected.length,
90
+ bytes: selected.reduce((sum, component) => sum + component.bytes, 0),
91
+ tokens: selected.reduce((sum, component) => sum + (component.tokens ?? 0), 0),
92
+ },
93
+ ];
94
+ })),
95
+ };
96
+ }
97
+ function gitHead(root) {
98
+ const result = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" });
99
+ return result.status === 0 ? result.stdout.trim() : null;
100
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwKjF"}
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8KjF"}
@@ -133,7 +133,10 @@ export function registerPresenceCommand(program, emit) {
133
133
  for (const a of m.agents) {
134
134
  const task = a.task ? ` "${a.task.slice(0, 70)}"` : "";
135
135
  const files = a.files_touched?.length ? ` [${a.files_touched.length} files]` : "";
136
- emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files}\n`);
136
+ const lifecycle = a.task_state === "blocked" && a.task_state_reason
137
+ ? `${a.task_state}: ${a.task_state_reason}`
138
+ : a.task_state;
139
+ emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files} activity=${a.activity} lifecycle=${lifecycle}\n`);
137
140
  }
138
141
  }
139
142
  });
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1E,OAAO,EAYL,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AA+rBhC;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE7F;AAgND,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA4EN"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1E,OAAO,EAYL,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AA8rBhC;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE7F;AA+MD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA2EN"}
@@ -763,7 +763,7 @@ function status(opts) {
763
763
  return;
764
764
  }
765
765
  if (states.length === 1) {
766
- // Single tunnel: show the full detail block (backward-compatible).
766
+ // A single tunnel is clearer as a detail block than as a one-row table.
767
767
  statusDetail(states[0]);
768
768
  return;
769
769
  }
@@ -794,7 +794,7 @@ function logs(opts) {
794
794
  const name = resolveName(opts.name);
795
795
  const state = readState(name);
796
796
  const provider = state?.provider ?? "cloudflare";
797
- const which = opts.cloudflared || opts.provider ? providerLogFile(name, provider) : gateLogFile(name);
797
+ const which = opts.provider ? providerLogFile(name, provider) : gateLogFile(name);
798
798
  const path = cachePath("tunnel", which);
799
799
  if (!existsSync(path)) {
800
800
  emit.error({ code: "tunnel_no_log", message: `No log file at ${path}` });
@@ -898,7 +898,6 @@ export function registerTunnelCommand(program, emitParam, contextParam) {
898
898
  .option("-f, --follow", "follow the log")
899
899
  .option("--gate", "tail the gate log (default)")
900
900
  .option("--provider", "tail the provider log instead")
901
- .option("--cloudflared", "tail the Cloudflare provider log instead (legacy alias)")
902
901
  .action(logs);
903
902
  const allow = cmd
904
903
  .command("allow")
@@ -28,10 +28,9 @@ export interface CanonicalEmitInput {
28
28
  * `agents status`, and no sequence of CLI commands can satisfy it.
29
29
  */
30
30
  export declare function resolveEmitRoot(start?: string): string | null;
31
- export declare function emitCanonical(input: CanonicalEmitInput): void;
31
+ export declare function emitCanonical(input: CanonicalEmitInput): boolean;
32
32
  /**
33
- * Normalize the heartbeat's `platform` field (which uses snake_case
34
- * `claude_code`) to the canonical kebab-case Adapter type.
33
+ * Normalize the heartbeat's `platform` field to the canonical Adapter type.
35
34
  */
36
35
  export declare function normalizeAdapter(platform: string | undefined): "claude-code" | "cursor" | "codex";
37
36
  //# sourceMappingURL=canonical-emit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAsC7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
1
+ {"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAwChE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
@@ -26,10 +26,10 @@ export function resolveEmitRoot(start = process.cwd()) {
26
26
  export function emitCanonical(input) {
27
27
  const root = resolveEmitRoot();
28
28
  if (!root)
29
- return;
29
+ return false;
30
30
  const binary = coordBinPath("agent-coord", root);
31
31
  if (!binary)
32
- return;
32
+ return false;
33
33
  try {
34
34
  const args = [
35
35
  "emit-event",
@@ -62,15 +62,16 @@ export function emitCanonical(input) {
62
62
  if (result.error || result.status !== 0) {
63
63
  const why = result.error ? result.error.message : `exit ${result.status}`;
64
64
  process.stderr.write(`emitCanonical: ${input.type} emit failed (${why})\n`); // lint-ok-emission: soft-fail diagnostic promised by the module doc; silent drops cost blocked turns
65
+ return false;
65
66
  }
67
+ return true;
66
68
  }
67
69
  catch {
68
- /* never break the caller */
70
+ return false;
69
71
  }
70
72
  }
71
73
  /**
72
- * Normalize the heartbeat's `platform` field (which uses snake_case
73
- * `claude_code`) to the canonical kebab-case Adapter type.
74
+ * Normalize the heartbeat's `platform` field to the canonical Adapter type.
74
75
  */
75
76
  export function normalizeAdapter(platform) {
76
77
  if (platform === "cursor")