harnery 0.41.0 → 0.43.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 (269) hide show
  1. package/dist/commander.d.ts +16 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +21 -0
  4. package/dist/commands/agents.d.ts.map +1 -1
  5. package/dist/commands/agents.js +63 -5
  6. package/dist/commands/artifacts.d.ts.map +1 -1
  7. package/dist/commands/artifacts.js +113 -12
  8. package/dist/commands/browse.d.ts +164 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +263 -51
  11. package/dist/commands/claude-desktop.d.ts.map +1 -1
  12. package/dist/commands/claude-desktop.js +52 -0
  13. package/dist/commands/disk.d.ts +6 -0
  14. package/dist/commands/disk.d.ts.map +1 -0
  15. package/dist/commands/disk.js +80 -0
  16. package/dist/commands/doctor.d.ts +8 -0
  17. package/dist/commands/doctor.d.ts.map +1 -1
  18. package/dist/commands/doctor.js +48 -1
  19. package/dist/commands/files.d.ts +16 -0
  20. package/dist/commands/files.d.ts.map +1 -1
  21. package/dist/commands/files.js +54 -2
  22. package/dist/commands/init.d.ts +2 -2
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +43 -9
  25. package/dist/commands/rm.d.ts +4 -0
  26. package/dist/commands/rm.d.ts.map +1 -0
  27. package/dist/commands/rm.js +87 -0
  28. package/dist/commands/servers.d.ts +7 -0
  29. package/dist/commands/servers.d.ts.map +1 -0
  30. package/dist/commands/servers.js +394 -0
  31. package/dist/commands/tunnel.d.ts +65 -0
  32. package/dist/commands/tunnel.d.ts.map +1 -1
  33. package/dist/commands/tunnel.js +372 -103
  34. package/dist/commands/web-fetch.d.ts +3 -2
  35. package/dist/commands/web-fetch.d.ts.map +1 -1
  36. package/dist/commands/web-fetch.js +25 -19
  37. package/dist/commands/web.d.ts +7 -1
  38. package/dist/commands/web.d.ts.map +1 -1
  39. package/dist/commands/web.js +9 -3
  40. package/dist/core/adapters/profiles.d.ts +9 -0
  41. package/dist/core/adapters/profiles.d.ts.map +1 -1
  42. package/dist/core/adapters/profiles.js +22 -1
  43. package/dist/core/adapters/types.d.ts +9 -0
  44. package/dist/core/adapters/types.d.ts.map +1 -1
  45. package/dist/core/agents/cli.js +13 -6
  46. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  47. package/dist/core/agents/render/prompt-context.js +11 -5
  48. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/session-context.js +59 -13
  50. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  51. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  52. package/dist/core/agents/state/live-coordination-view.js +60 -0
  53. package/dist/core/agents/state/names.d.ts +43 -3
  54. package/dist/core/agents/state/names.d.ts.map +1 -1
  55. package/dist/core/agents/state/names.js +87 -3
  56. package/dist/core/artifacts/delivery-card.js +9 -2
  57. package/dist/core/artifacts/index.d.ts +140 -4
  58. package/dist/core/artifacts/index.d.ts.map +1 -1
  59. package/dist/core/artifacts/index.js +522 -49
  60. package/dist/core/config.d.ts +68 -11
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +128 -3
  63. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  64. package/dist/core/diagnostics/bundle.js +2 -2
  65. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  66. package/dist/core/events/v3/producers/recorder.js +16 -0
  67. package/dist/core/events/v3/projection.js +9 -1
  68. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  69. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  70. package/dist/core/hooks/adapter/events.d.ts +17 -0
  71. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  72. package/dist/core/hooks/adapter/events.js +5 -0
  73. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  74. package/dist/core/hooks/adapter/wiring.d.ts +37 -7
  75. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  76. package/dist/core/hooks/adapter/wiring.js +62 -4
  77. package/dist/core/hooks/cli.js +131 -23
  78. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  79. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  80. package/dist/core/hooks/fork-lineage.js +62 -0
  81. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  82. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  83. package/dist/core/hooks/resolve/transcript.js +84 -0
  84. package/dist/core/hooks/run-markers.d.ts +37 -0
  85. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  86. package/dist/core/hooks/run-markers.js +109 -0
  87. package/dist/core/hooks/shell-rm.d.ts +2 -0
  88. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  89. package/dist/core/hooks/shell-rm.js +105 -0
  90. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  91. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  92. package/dist/core/hooks/shell-tokens.js +97 -0
  93. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  94. package/dist/core/hooks/shell-waiter.js +3 -93
  95. package/dist/core/semantic/contract.d.ts +5 -5
  96. package/dist/core/servers/index.d.ts +211 -0
  97. package/dist/core/servers/index.d.ts.map +1 -0
  98. package/dist/core/servers/index.js +560 -0
  99. package/dist/core/servers/net.d.ts +58 -0
  100. package/dist/core/servers/net.d.ts.map +1 -0
  101. package/dist/core/servers/net.js +270 -0
  102. package/dist/core/servers/tunnels.d.ts +14 -0
  103. package/dist/core/servers/tunnels.d.ts.map +1 -0
  104. package/dist/core/servers/tunnels.js +33 -0
  105. package/dist/core/storage/builtins.js +1 -0
  106. package/dist/core/storage/process-log.d.ts +12 -0
  107. package/dist/core/storage/process-log.d.ts.map +1 -1
  108. package/dist/core/storage/process-log.js +10 -2
  109. package/dist/core/workflow/engine.d.ts.map +1 -1
  110. package/dist/core/workflow/engine.js +30 -1
  111. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  112. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  113. package/dist/core/workflow/sandbox-projection.js +23 -0
  114. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  115. package/dist/core/workflow/spawn-claude.js +6 -1
  116. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  117. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  118. package/dist/core/workflow/spawn-codex.js +20 -1
  119. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-cursor.js +6 -1
  121. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  122. package/dist/core/workflow/spawn-opencode.js +4 -1
  123. package/dist/core/workflow/types.d.ts +47 -2
  124. package/dist/core/workflow/types.d.ts.map +1 -1
  125. package/dist/core/workflow/worker-access.d.ts +92 -0
  126. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  127. package/dist/core/workflow/worker-access.js +450 -0
  128. package/dist/lib/browser/batch-steps.d.ts +18 -0
  129. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  130. package/dist/lib/browser/batch-steps.js +82 -0
  131. package/dist/lib/browser/collect/collector.d.ts +99 -0
  132. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  133. package/dist/lib/browser/collect/collector.js +202 -0
  134. package/dist/lib/browser/collect/fields.d.ts +31 -0
  135. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  136. package/dist/lib/browser/collect/fields.js +75 -0
  137. package/dist/lib/browser/collect/index.d.ts +6 -0
  138. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  139. package/dist/lib/browser/collect/index.js +5 -0
  140. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  141. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  142. package/dist/lib/browser/collect/page-driver.js +306 -0
  143. package/dist/lib/browser/collect/presets.d.ts +52 -0
  144. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  145. package/dist/lib/browser/collect/presets.js +176 -0
  146. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  147. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  148. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  149. package/dist/lib/browser/index.d.ts +1 -1
  150. package/dist/lib/browser/index.d.ts.map +1 -1
  151. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  152. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  153. package/dist/lib/browser/page-review-judge.js +13 -0
  154. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  155. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  156. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  157. package/dist/lib/browser/qa-run.js +1 -0
  158. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  159. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  160. package/dist/lib/browser/windows-chrome.js +179 -0
  161. package/dist/lib/claude-desktop-share.d.ts +136 -0
  162. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  163. package/dist/lib/claude-desktop-share.js +394 -0
  164. package/dist/lib/disk-usage.d.ts +50 -0
  165. package/dist/lib/disk-usage.d.ts.map +1 -0
  166. package/dist/lib/disk-usage.js +303 -0
  167. package/dist/lib/docs-lint.d.ts +6 -0
  168. package/dist/lib/docs-lint.d.ts.map +1 -1
  169. package/dist/lib/docs-lint.js +8 -3
  170. package/dist/lib/guarded-remove.d.ts +34 -0
  171. package/dist/lib/guarded-remove.d.ts.map +1 -0
  172. package/dist/lib/guarded-remove.js +310 -0
  173. package/dist/lib/instructions/templates.js +8 -8
  174. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  175. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  176. package/dist/lib/tunnel/allowlist.js +204 -0
  177. package/dist/lib/tunnel/current-address.d.ts +41 -0
  178. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  179. package/dist/lib/tunnel/current-address.js +123 -0
  180. package/dist/lib/tunnel/error-page.d.ts +1 -1
  181. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  182. package/dist/lib/tunnel/error-page.js +19 -8
  183. package/dist/lib/tunnel/gate.js +33 -7
  184. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  185. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  186. package/dist/lib/tunnel/path-scope.js +62 -0
  187. package/dist/lib/tunnel/processes.d.ts +39 -0
  188. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  189. package/dist/lib/tunnel/processes.js +121 -0
  190. package/dist/lib/tunnel/state.d.ts +33 -3
  191. package/dist/lib/tunnel/state.d.ts.map +1 -1
  192. package/dist/lib/tunnel/state.js +115 -30
  193. package/package.json +7 -1
  194. package/schemas/config.schema.json +91 -0
  195. package/src/commander.ts +51 -0
  196. package/src/commands/agents.ts +111 -11
  197. package/src/commands/artifacts.ts +171 -19
  198. package/src/commands/browse.ts +400 -57
  199. package/src/commands/claude-desktop.ts +61 -0
  200. package/src/commands/disk.ts +114 -0
  201. package/src/commands/doctor.ts +52 -1
  202. package/src/commands/files.ts +93 -2
  203. package/src/commands/init.ts +55 -5
  204. package/src/commands/rm.ts +115 -0
  205. package/src/commands/servers.ts +506 -0
  206. package/src/commands/tunnel.ts +494 -116
  207. package/src/commands/web-fetch.ts +30 -19
  208. package/src/commands/web.ts +9 -3
  209. package/src/core/adapters/profiles.ts +22 -1
  210. package/src/core/adapters/types.ts +10 -0
  211. package/src/core/agents/cli.ts +14 -6
  212. package/src/core/agents/render/prompt-context.ts +25 -8
  213. package/src/core/agents/render/session-context.ts +84 -16
  214. package/src/core/agents/state/live-coordination-view.ts +73 -0
  215. package/src/core/agents/state/names.ts +117 -6
  216. package/src/core/artifacts/delivery-card.ts +10 -2
  217. package/src/core/artifacts/index.ts +697 -44
  218. package/src/core/config.ts +183 -4
  219. package/src/core/diagnostics/bundle.ts +2 -1
  220. package/src/core/events/v3/producers/recorder.ts +16 -0
  221. package/src/core/events/v3/projection.ts +11 -1
  222. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  223. package/src/core/hooks/adapter/events.ts +19 -0
  224. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  225. package/src/core/hooks/adapter/wiring.ts +116 -8
  226. package/src/core/hooks/cli.ts +152 -22
  227. package/src/core/hooks/fork-lineage.ts +82 -0
  228. package/src/core/hooks/resolve/transcript.ts +88 -0
  229. package/src/core/hooks/run-markers.ts +132 -0
  230. package/src/core/hooks/shell-rm.ts +103 -0
  231. package/src/core/hooks/shell-tokens.ts +96 -0
  232. package/src/core/hooks/shell-waiter.ts +4 -91
  233. package/src/core/servers/index.ts +712 -0
  234. package/src/core/servers/net.ts +283 -0
  235. package/src/core/servers/tunnels.ts +39 -0
  236. package/src/core/storage/builtins.ts +1 -0
  237. package/src/core/storage/process-log.ts +17 -2
  238. package/src/core/workflow/engine.ts +38 -1
  239. package/src/core/workflow/sandbox-projection.ts +38 -2
  240. package/src/core/workflow/spawn-claude.ts +5 -1
  241. package/src/core/workflow/spawn-codex.ts +18 -1
  242. package/src/core/workflow/spawn-cursor.ts +5 -1
  243. package/src/core/workflow/spawn-opencode.ts +4 -1
  244. package/src/core/workflow/types.ts +62 -2
  245. package/src/core/workflow/worker-access.ts +525 -0
  246. package/src/lib/browser/batch-steps.ts +79 -0
  247. package/src/lib/browser/collect/collector.ts +306 -0
  248. package/src/lib/browser/collect/fields.ts +87 -0
  249. package/src/lib/browser/collect/index.ts +47 -0
  250. package/src/lib/browser/collect/page-driver.ts +327 -0
  251. package/src/lib/browser/collect/presets.ts +237 -0
  252. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  253. package/src/lib/browser/index.ts +1 -0
  254. package/src/lib/browser/page-review-judge.ts +16 -0
  255. package/src/lib/browser/qa-run-contracts.ts +11 -0
  256. package/src/lib/browser/qa-run.ts +1 -0
  257. package/src/lib/browser/windows-chrome.ts +227 -0
  258. package/src/lib/claude-desktop-share.ts +511 -0
  259. package/src/lib/disk-usage.ts +347 -0
  260. package/src/lib/docs-lint.ts +8 -3
  261. package/src/lib/guarded-remove.ts +376 -0
  262. package/src/lib/instructions/templates.ts +8 -8
  263. package/src/lib/tunnel/allowlist.ts +208 -0
  264. package/src/lib/tunnel/current-address.ts +158 -0
  265. package/src/lib/tunnel/error-page.ts +25 -9
  266. package/src/lib/tunnel/gate.ts +38 -9
  267. package/src/lib/tunnel/path-scope.ts +73 -0
  268. package/src/lib/tunnel/processes.ts +149 -0
  269. package/src/lib/tunnel/state.ts +143 -30
@@ -1,4 +1,6 @@
1
1
  import { applyMirror, findDesktopDataDirs, listAccounts, planMirror, readCliAccount, } from "../lib/claude-desktop.js";
2
+ import { applyShare, defaultShareDir, planShare, shareSummary, } from "../lib/claude-desktop-share.js";
3
+ import { resolveMachineLabel } from "../lib/machine.js";
2
4
  /**
3
5
  * `claude-desktop`: make Claude desktop-app sessions survive account
4
6
  * switches.
@@ -141,6 +143,56 @@ export function registerClaudeDesktopCommand(program, emit, _context) {
141
143
  });
142
144
  }
143
145
  });
146
+ cmd
147
+ .command("share")
148
+ .description("Share sessions with your other computers through a synced folder (iCloud Drive by default): " +
149
+ "publish this machine's recent sessions and list + resume every other machine's here. " +
150
+ "Dry-run by default; --yes applies. Restart the desktop app afterward to see new entries.")
151
+ .option("--dir <path>", "Shared folder (default: iCloud Drive/Harnery/claude-sessions)")
152
+ .option("--days <n>", "Only sessions active in the last N days", "14")
153
+ .option("--data-dir <path>", "Explicit desktop-app data directory")
154
+ .option("--yes", "Apply the plan (default is dry-run)")
155
+ .action((opts) => {
156
+ const shareDir = opts.dir ?? process.env.HARNERY_SESSION_SHARE_DIR ?? defaultShareDir();
157
+ if (!shareDir) {
158
+ emit.error({
159
+ code: "no_share_dir",
160
+ message: "no shared folder: pass --dir <path> or set HARNERY_SESSION_SHARE_DIR",
161
+ });
162
+ process.exit(2);
163
+ }
164
+ const cli = readCliAccount();
165
+ if (!cli) {
166
+ emit.error({
167
+ code: "no_account",
168
+ message: "cannot read the signed-in account from ~/.claude.json",
169
+ });
170
+ process.exit(2);
171
+ }
172
+ const [dataDir] = requireDataDirs(emit, opts.dataDir);
173
+ const shareOpts = {
174
+ shareDir,
175
+ machine: resolveMachineLabel(),
176
+ days: Number(opts.days) || 14,
177
+ dataDir: dataDir,
178
+ targetAccountUuid: cli.accountUuid,
179
+ };
180
+ const plan = planShare(shareOpts);
181
+ const summary = { share_dir: shareDir, machine: shareOpts.machine, ...shareSummary(plan) };
182
+ if (!opts.yes) {
183
+ emit.data({ dry_run: true, ...summary, hint: "re-run with --yes to apply" });
184
+ return;
185
+ }
186
+ const result = applyShare(plan, shareOpts);
187
+ emit.data({
188
+ dry_run: false,
189
+ ...summary,
190
+ ...result,
191
+ hint: result.imported > 0
192
+ ? "quit the Claude desktop app fully (Cmd+Q) and reopen it to see the new sessions"
193
+ : "sidebar already current",
194
+ });
195
+ });
144
196
  }
145
197
  function collect(value, previous) {
146
198
  return previous.concat([value]);
@@ -0,0 +1,6 @@
1
+ import { type Command } from "commander";
2
+ import type { EmitContext } from "../commander.js";
3
+ import { type DiskReport } from "../lib/disk-usage.js";
4
+ export declare function registerDiskCommand(program: Command, emit: EmitContext): void;
5
+ export declare function renderDiskUsage(report: DiskReport): string;
6
+ //# sourceMappingURL=disk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../src/commands/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAwB,MAAM,WAAW,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,sBAAsB,CAAC;AAQ9B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA+C7E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAgD1D"}
@@ -0,0 +1,80 @@
1
+ import { InvalidArgumentError } from "commander";
2
+ import { diskUsage, parseDiskSize, } from "../lib/disk-usage.js";
3
+ function positive(value) {
4
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1)
5
+ throw new InvalidArgumentError("requires a positive integer");
6
+ return Number(value);
7
+ }
8
+ export function registerDiskCommand(program, emit) {
9
+ program
10
+ .command("disk [path]")
11
+ .description("Measure checkout disk usage, including nested repositories and ignored files")
12
+ .option("--git <states>", "Filter: tracked, untracked, ignored, git-metadata, non-repository, unknown (comma-separated)")
13
+ .option("--type <types>", "File extensions or video, audio, image, media, archive (comma-separated)")
14
+ .option("--exclude <paths>", "Skip directory names at any depth or root-relative paths; repeatable, comma-separated", (value, previous) => [...previous, value], [])
15
+ .option("--group <by>", "Group by directory, repository, extension, git, or files", "directory")
16
+ .option("--depth <n>", "Directory grouping depth; does not limit the scan", positive, 1)
17
+ .option("--top <n>", "Maximum groups and largest files to display", positive, 20)
18
+ .option("--min-size <size>", "Include individual files at least this size (e.g. 10MB, 1GiB)", parseDiskSize)
19
+ .option("--apparent", "Rank and filter by file lengths instead of allocated blocks")
20
+ .option("--json", "Emit the versioned disk usage report")
21
+ .action((path, options) => {
22
+ try {
23
+ const { json, ...filters } = options;
24
+ const report = diskUsage(path, filters);
25
+ if (json) {
26
+ emit.config({ format: "json" });
27
+ emit.data(report);
28
+ }
29
+ else
30
+ emit.text(renderDiskUsage(report));
31
+ if (!report.complete)
32
+ emit.setExitCode(1);
33
+ }
34
+ catch (error) {
35
+ emit.error({
36
+ code: "disk_usage_failed",
37
+ message: error instanceof Error ? error.message : String(error),
38
+ });
39
+ emit.setExitCode(1);
40
+ }
41
+ });
42
+ }
43
+ export function renderDiskUsage(report) {
44
+ const bytes = (value) => value >= 1e9
45
+ ? `${(value / 1e9).toFixed(2)} GB`
46
+ : value >= 1e6
47
+ ? `${(value / 1e6).toFixed(2)} MB`
48
+ : value >= 1e3
49
+ ? `${(value / 1e3).toFixed(2)} KB`
50
+ : `${value} B`;
51
+ const size = (row) => bytes(report.measurement === "allocated"
52
+ ? (row.allocated_bytes ?? row.apparent_bytes)
53
+ : row.apparent_bytes);
54
+ const lines = [
55
+ `Disk usage: ${report.root}`,
56
+ `${size(report.totals)} in ${report.totals.files.toLocaleString()} regular files (${report.measurement})`,
57
+ `File lengths: ${bytes(report.totals.apparent_bytes)}. Directory metadata and symlinks are excluded.`,
58
+ "",
59
+ "Git state:",
60
+ ];
61
+ for (const [state, totals] of Object.entries(report.git))
62
+ if (totals.files)
63
+ lines.push(`${size(totals).padStart(12)} ${String(totals.files).padStart(8)} files ${state}`);
64
+ lines.push("", `Largest ${report.filters.group} groups (${report.groups.length} of ${report.group_count}):`);
65
+ for (const row of report.groups)
66
+ lines.push(`${size(row).padStart(12)} ${String(row.files).padStart(8)} files ${row.path}`);
67
+ if (report.filters.group !== "files") {
68
+ lines.push("", "Largest files:");
69
+ for (const row of report.largest_files)
70
+ lines.push(`${size(row).padStart(12)} ${row.git.padEnd(14)} ${row.path}`);
71
+ }
72
+ if (report.skipped.symlinks || report.skipped.special_files || report.skipped.excluded)
73
+ lines.push("", `Skipped: ${report.skipped.symlinks} symlinks, ${report.skipped.special_files} special files, ${report.skipped.excluded} excluded entries.`);
74
+ if (report.issues.length) {
75
+ lines.push("", "Incomplete scan:");
76
+ for (const issue of report.issues)
77
+ lines.push(`${issue.path}: ${issue.message}`);
78
+ }
79
+ return `${lines.join("\n")}\n`;
80
+ }
@@ -24,6 +24,14 @@ export interface Check {
24
24
  export declare function registerDoctorCommand(program: Command, emit: EmitContext): void;
25
25
  export declare function runChecks(): Promise<Check[]>;
26
26
  export declare function codexWslBridgeCheck(status: CodexWslBridgeStatus | null, recentMidFlightCount?: number): Check | null;
27
+ /**
28
+ * Hook runs that started but never finished (ADR 0197). Codex kills a hook at
29
+ * its timeout and the tool proceeds without that hook's checks; the start
30
+ * marker is the only trace such a run leaves.
31
+ */
32
+ export declare function codexHookRunsCheck(coordRoot: string | null, nowMs?: number, isAlive?: (pid: number) => boolean): Check | null;
33
+ /** Report the per-project Windows bridge opt-in, or an invalid value init ignored. */
34
+ export declare function codexWindowsBridgeCheck(coordRoot: string | null): Check | null;
27
35
  /** Count bounded, privacy-safe recovery diagnostics so a healthy environment
28
36
  * now does not hide an intermittent Codex-to-WSL identity drop. */
29
37
  export declare function countRecentCodexMidFlightOnboardings(coordRoot: string, nowMs?: number, windowMs?: number): number;
@@ -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;AASnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAW3C,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,CA2BlD;AAgCD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,oBAAoB,SAAI,GACvB,KAAK,GAAG,IAAI,CAed;AAID;mEACmE;AACnE,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,MAAM,EACjB,KAAK,SAAa,EAClB,QAAQ,SAAqC,GAC5C,MAAM,CA0CR;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAoBnF"}
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;AASnD,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,mCAAmC,CAAC;AAY3C,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,CA+BlD;AAgCD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,oBAAoB,GAAG,IAAI,EACnC,oBAAoB,SAAI,GACvB,KAAK,GAAG,IAAI,CAed;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,SAAa,EAClB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACjC,KAAK,GAAG,IAAI,CAad;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAmB9E;AAID;mEACmE;AACnE,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,MAAM,EACjB,KAAK,SAAa,EAClB,QAAQ,SAAqC,GAC5C,MAAM,CA0CR;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,4BAA4B,GAAG,KAAK,CAoBnF"}
@@ -15,12 +15,13 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
15
15
  import os from "node:os";
16
16
  import path from "node:path";
17
17
  import { BUILTIN_ADAPTER_IDS } from "../core/adapters/index.js";
18
- import { resolveBinName, ripgrepAutoInstall } from "../core/config.js";
18
+ import { codexWindowsBridgeConfig, resolveBinName, ripgrepAutoInstall } from "../core/config.js";
19
19
  import { countSummarizedSinceV3, listDiagnosticSummariesV3, } from "../core/events/v3/producers/diagnostic-summaries.js";
20
20
  import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
21
21
  import { loadAdapterWiring, summarizeAdapterWiring } from "../core/hooks/adapter/wiring.js";
22
22
  import { probeCodexHookAuthorization, } from "../core/hooks/codex-authorization.js";
23
23
  import { inspectCodexWslBridge, } from "../core/hooks/codex-wsl-bridge.js";
24
+ import { findUnfinishedHookRuns } from "../core/hooks/run-markers.js";
24
25
  import { ADAPTER_BINARIES, ADAPTER_INSTALL_HINTS, ADAPTER_LOGIN_HINTS, } from "../core/workflow/adapters.js";
25
26
  import { probeBilling } from "../core/workflow/billing.js";
26
27
  import { checkGitHooks, gitHooksInstalled } from "../lib/instructions/git-hooks.js";
@@ -74,6 +75,8 @@ export async function runChecks() {
74
75
  const codexWslStatus = inspectCodexWslBridge();
75
76
  const recentCodexMidFlightCount = root ? countRecentCodexMidFlightOnboardings(root) : 0;
76
77
  const codexWslBridge = codexWslBridgeCheck(codexWslStatus, recentCodexMidFlightCount);
78
+ const codexHookRuns = codexHookRunsCheck(root);
79
+ const codexWindowsRoute = codexWindowsBridgeCheck(root);
77
80
  // Probe the adapter CLIs before the hook check runs, not in output order:
78
81
  // an unwired adapter only matters if its CLI is actually installed, and
79
82
  // probing once here keeps that join to one spawn per CLI.
@@ -90,6 +93,8 @@ export async function runChecks() {
90
93
  ...(codexAuthorization ? [codexAuthorization] : []),
91
94
  checkGitHookRegions(),
92
95
  ...(codexWslBridge ? [codexWslBridge] : []),
96
+ ...(codexWindowsRoute ? [codexWindowsRoute] : []),
97
+ ...(codexHookRuns ? [codexHookRuns] : []),
93
98
  ...workflow.map((w) => w.check),
94
99
  checkRestic(),
95
100
  checkRclone(),
@@ -141,6 +146,48 @@ export function codexWslBridgeCheck(status, recentMidFlightCount = 0) {
141
146
  : "forward CODEX_THREAD_ID through WSLENV in the Codex shell environment policy, then start a fresh task",
142
147
  };
143
148
  }
149
+ /**
150
+ * Hook runs that started but never finished (ADR 0197). Codex kills a hook at
151
+ * its timeout and the tool proceeds without that hook's checks; the start
152
+ * marker is the only trace such a run leaves.
153
+ */
154
+ export function codexHookRunsCheck(coordRoot, nowMs = Date.now(), isAlive) {
155
+ if (!coordRoot)
156
+ return null;
157
+ const runs = findUnfinishedHookRuns(coordRoot, nowMs, undefined, isAlive);
158
+ if (runs.count === 0)
159
+ return null;
160
+ const events = runs.events.map((e) => `${e.event} ×${e.count}`).join(", ");
161
+ return {
162
+ name: "codex:hook runs",
163
+ severity: "warn",
164
+ detail: `${runs.count} hook run${runs.count === 1 ? "" : "s"} started but never finished in the last 24h ` +
165
+ `(${events}; latest ${runs.latest}); their tools proceeded without those hooks' checks`,
166
+ hint: "a hook that outlives its timeout is killed; look for a stalled WSL session or a slow hook",
167
+ };
168
+ }
169
+ /** Report the per-project Windows bridge opt-in, or an invalid value init ignored. */
170
+ export function codexWindowsBridgeCheck(coordRoot) {
171
+ if (!coordRoot)
172
+ return null;
173
+ const bridge = codexWindowsBridgeConfig(coordRoot);
174
+ if (bridge.error) {
175
+ return {
176
+ name: "codex:Windows hook route",
177
+ severity: "warn",
178
+ detail: `${bridge.error}; Codex hooks are wired without commandWindows`,
179
+ hint: "set hooks.codexWindowsBridge.entryPoint to a plain command name in .harnery/config.jsonc, then re-run init",
180
+ };
181
+ }
182
+ if (!bridge.entryPoint)
183
+ return null;
184
+ return {
185
+ name: "codex:Windows hook route",
186
+ severity: "ok",
187
+ detail: `opted in: Windows-native Codex hooks hand off to \`${bridge.entryPoint}\` from WSL paths ` +
188
+ `when it is on PATH, otherwise run the usual command; bridge health is reported by the bridge itself`,
189
+ };
190
+ }
144
191
  const CODEX_MID_FLIGHT_HISTORY_WINDOW_MS = 48 * 60 * 60 * 1000;
145
192
  /** Count bounded, privacy-safe recovery diagnostics so a healthy environment
146
193
  * now does not hide an intermittent Codex-to-WSL identity drop. */
@@ -1,5 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
+ import { type ProcessAliveCheck, type TunnelState } from "../lib/tunnel/state.js";
3
4
  export { FILES_ORIGIN_HOST } from "../lib/local-file-url.js";
4
5
  export type LocalFileUrlMode = "auto" | "raw" | "viewer";
5
6
  export interface LocalFileUrlOptions {
@@ -30,5 +31,20 @@ export declare function verifyDashboardRoot(coordRoot: string, port: number, fet
30
31
  * work. Other files default to the dashboard viewer.
31
32
  */
32
33
  export declare function mintLocalFileUrl(input: string, options: LocalFileUrlOptions): LocalFileUrl;
34
+ export declare class TunnelLinkError extends Error {
35
+ readonly hint: string;
36
+ constructor(message: string, hint: string);
37
+ }
38
+ /**
39
+ * Turn a minted local URL into the same file's URL on a live tunnel.
40
+ *
41
+ * HTML needs a tunnel in front of the isolated files origin (its vhost is
42
+ * FILES_ORIGIN_HOST), because only that origin renders the page and serves its
43
+ * relative assets; a dashboard tunnel can only show the viewer, which displays
44
+ * HTML as source. Other files need a dashboard tunnel that serves the viewer.
45
+ * In both cases the tunnel's path scope must forward the request, or its gate
46
+ * would refuse the link, so a tunnel that does not cover the file is skipped.
47
+ */
48
+ export declare function tunnelFileUrl(local: LocalFileUrl, port: number, states: TunnelState[], processAlive?: ProcessAliveCheck): string;
33
49
  export declare function registerFilesCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
34
50
  //# sourceMappingURL=files.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/commands/files.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAM1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,gCAAgC,GAChC,yBAAyB,CAAC;AAE9B,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,IAAI,EAAE,sBAAsB;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAFZ,IAAI,EAAE,sBAAsB,EACrC,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM;CAKxB;AAMD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,kBAA0B,GACpC,OAAO,CAAC,IAAI,CAAC,CAmDf;AAuCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAS1F;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA0EN"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/commands/files.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAY1E,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAEjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,gCAAgC,GAChC,yBAAyB,CAAC;AAE9B,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,IAAI,EAAE,sBAAsB;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAFZ,IAAI,EAAE,sBAAsB,EACrC,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM;CAKxB;AAMD,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,kBAA0B,GACpC,OAAO,CAAC,IAAI,CAAC,CAmDf;AAuCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAS1F;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGtC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADrB,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,MAAM;CAKxB;AAUD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EAAE,EACrB,YAAY,CAAC,EAAE,iBAAiB,GAC/B,MAAM,CA+BR;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAqFN"}
@@ -3,7 +3,9 @@ import path from "node:path";
3
3
  import { resolveCoordRoot } from "../core/agents/coord-client.js";
4
4
  import { DEFAULT_WEB_PORT, resolveWebPort } from "../core/config.js";
5
5
  import { coordRootId, localCoordRootIdUrl } from "../lib/coord-root-id.js";
6
- import { localFilesOriginUrl, localFileViewerUrl } from "../lib/local-file-url.js";
6
+ import { encodedRepoPath, encodeLinkSafeComponent, FILES_ORIGIN_HOST, localFilesOriginUrl, localFileViewerUrl, } from "../lib/local-file-url.js";
7
+ import { isPathAllowed } from "../lib/tunnel/path-scope.js";
8
+ import { isTunnelStateLive, listStates, tunnelTargetPort, } from "../lib/tunnel/state.js";
7
9
  export { FILES_ORIGIN_HOST } from "../lib/local-file-url.js";
8
10
  export class DashboardRootError extends Error {
9
11
  code;
@@ -99,6 +101,49 @@ export function mintLocalFileUrl(input, options) {
99
101
  : localFileViewerUrl(relPath, options.port);
100
102
  return { url, relPath, mode };
101
103
  }
104
+ export class TunnelLinkError extends Error {
105
+ hint;
106
+ constructor(message, hint) {
107
+ super(message);
108
+ this.hint = hint;
109
+ this.name = "TunnelLinkError";
110
+ }
111
+ }
112
+ function vhostName(vhost) {
113
+ return vhost.replace(/:\d+$/, "").toLowerCase();
114
+ }
115
+ function newestFirst(states) {
116
+ return [...states].sort((a, b) => b.started_at.localeCompare(a.started_at));
117
+ }
118
+ /**
119
+ * Turn a minted local URL into the same file's URL on a live tunnel.
120
+ *
121
+ * HTML needs a tunnel in front of the isolated files origin (its vhost is
122
+ * FILES_ORIGIN_HOST), because only that origin renders the page and serves its
123
+ * relative assets; a dashboard tunnel can only show the viewer, which displays
124
+ * HTML as source. Other files need a dashboard tunnel that serves the viewer.
125
+ * In both cases the tunnel's path scope must forward the request, or its gate
126
+ * would refuse the link, so a tunnel that does not cover the file is skipped.
127
+ */
128
+ export function tunnelFileUrl(local, port, states, processAlive) {
129
+ const live = states.filter((state) => tunnelTargetPort(state) === port && isTunnelStateLive(state, processAlive));
130
+ if (local.mode === "raw") {
131
+ const path = `/${encodedRepoPath(local.relPath)}`;
132
+ const tunnel = newestFirst(live).find((state) => vhostName(state.vhost) === FILES_ORIGIN_HOST && isPathAllowed(path, state.allow_paths));
133
+ if (tunnel)
134
+ return `${tunnel.url.replace(/\/+$/, "")}${path}`;
135
+ const folder = local.relPath.includes("/")
136
+ ? `/${local.relPath.slice(0, local.relPath.lastIndexOf("/"))}`
137
+ : "/";
138
+ throw new TunnelLinkError(`no live tunnel serves the files origin for /${local.relPath}`, `Start one scoped to the page's folder, for example: tunnel up --name pages --target 127.0.0.1:${port} --vhost ${FILES_ORIGIN_HOST} --allow-path ${folder}`);
139
+ }
140
+ const tunnel = newestFirst(live).find((state) => vhostName(state.vhost) !== FILES_ORIGIN_HOST &&
141
+ ["/files", "/api/file", "/_next"].every((path) => isPathAllowed(path, state.allow_paths)));
142
+ if (tunnel) {
143
+ return `${tunnel.url.replace(/\/+$/, "")}/files?path=${encodeLinkSafeComponent(local.relPath)}`;
144
+ }
145
+ throw new TunnelLinkError(`no live tunnel serves the dashboard file viewer on port ${port}`, `Start one, for example: tunnel up --name files --target 127.0.0.1:${port} --allow-path /files --allow-path /api/file --allow-path /_next`);
146
+ }
102
147
  export function registerFilesCommand(program, emit, context) {
103
148
  const files = program
104
149
  .command("files")
@@ -112,6 +157,7 @@ export function registerFilesCommand(program, emit, context) {
112
157
  .option("--raw", "Use the isolated files origin, regardless of extension")
113
158
  .option("--viewer", "Use the dashboard file viewer, regardless of extension")
114
159
  .option("--no-verify", "Mint without checking the running dashboard's repository identity")
160
+ .option("--tunnel", "Print the file's URL on a live tunnel instead of localhost (HTML uses a files-origin tunnel)")
115
161
  .action(async (filePath, opts) => {
116
162
  if (opts.raw && opts.viewer) {
117
163
  emit.error({
@@ -147,9 +193,15 @@ export function registerFilesCommand(program, emit, context) {
147
193
  });
148
194
  if (opts.verify !== false)
149
195
  await verifyDashboardRoot(coordRoot, port);
150
- emit.text(`${result.url}\n`);
196
+ const url = opts.tunnel ? tunnelFileUrl(result, port, listStates(coordRoot)) : result.url;
197
+ emit.text(`${url}\n`);
151
198
  }
152
199
  catch (error) {
200
+ if (error instanceof TunnelLinkError) {
201
+ emit.error({ code: "no_tunnel_for_file", message: error.message, hint: error.hint });
202
+ emit.setExitCode(1);
203
+ return;
204
+ }
153
205
  if (error instanceof DashboardRootError) {
154
206
  emit.error({ code: error.code, message: error.message, hint: error.hint });
155
207
  emit.setExitCode(1);
@@ -23,7 +23,7 @@ import type { Command } from "commander";
23
23
  import type { EmitContext } from "../commander.js";
24
24
  import type { EventV3ControlState } from "../core/events/v3/control.js";
25
25
  import { type AdapterId, type AdapterSpec } from "../core/hooks/adapter/events.js";
26
- import { type SettingsFile } from "../core/hooks/adapter/wiring.js";
26
+ import { type SettingsFile, type WiringOptions } from "../core/hooks/adapter/wiring.js";
27
27
  /**
28
28
  * Which adapters `init` should wire. An explicit `--adapter` wins. Without one:
29
29
  * every adapter whose CLI is installed on this machine, plus every adapter the
@@ -55,7 +55,7 @@ export declare function codexHookReviewAction(adapter: AdapterId): string | null
55
55
  *
56
56
  * Token-aware matching keeps `stop` from matching `stop-failure`.
57
57
  */
58
- export declare function wireHooks(settings: SettingsFile, spec: AdapterSpec, agentHookPath: string, adapter: AdapterId): {
58
+ export declare function wireHooks(settings: SettingsFile, spec: AdapterSpec, agentHookPath: string, adapter: AdapterId, options?: WiringOptions): {
59
59
  wired: number;
60
60
  already: number;
61
61
  removed: number;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAOxE,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAKlG,OAAO,EAWL,KAAK,YAAY,EAElB,MAAM,iCAAiC,CAAC;AA0CzC;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,CAAC,EAAE,SAAS,EAAE,GACtB;IAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAM3C;AAyFD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CA8T/F;AAiBD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAiBjF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAOvE;AAED;;;;;;;;;;GAUG;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,CAsDvE;AAyFD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAa1F;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;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAOxE,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAKlG,OAAO,EAcL,KAAK,YAAY,EAEjB,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AA0CzC;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,CAAC,EAAE,SAAS,EAAE,GACtB;IAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAM3C;AA6FD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAmU/F;AAiBD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAiBjF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAOvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE,aAAa,GACtB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAmEvE;AA2GD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAa1F;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"}
@@ -24,14 +24,14 @@ import { createHash } from "node:crypto";
24
24
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
25
25
  import { dirname, relative, resolve } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
- import { DEFAULT_BIN_NAME, pinnedBinName, stripJsonComments } from "../core/config.js";
27
+ import { codexWindowsBridgeConfig, DEFAULT_BIN_NAME, pinnedBinName, stripJsonComments, } from "../core/config.js";
28
28
  import { canonicalJsonV3 } from "../core/events/v3/canonical.js";
29
29
  import { ADAPTER_CAPABILITY_PROFILES_V3 } from "../core/events/v3/capabilities.js";
30
30
  import { EVENT_V3_SCHEMA_DIGEST } from "../core/events/v3/generated.js";
31
31
  import { initializeEventLedgerV3, readEventV3ControlState, sha256V3, } from "../core/events/v3/index.js";
32
32
  import { ADAPTER_SPECS } from "../core/hooks/adapter/events.js";
33
33
  import { checkOpenCodePlugin, installOpenCodePlugin, } from "../core/hooks/adapter/opencode-plugin.js";
34
- import { agentHookPathForProject, applyCursorStopLoopLimit, CURSOR_STOP_FOLLOWUP_LOOP_LIMIT, commandWiresSubcommand, diffWiring, groupCommands, hookCommand, isAgentHookCommand, makeEntry, summarizeAdapterWiring, } from "../core/hooks/adapter/wiring.js";
34
+ import { agentHookPathForProject, applyCursorStopLoopLimit, CURSOR_STOP_FOLLOWUP_LOOP_LIMIT, commandWiresSubcommand, diffWiring, expectedCommandWindows, groupCommands, hookCommand, hookTimeoutFor, isAgentHookCommand, makeEntry, orderHandlerKeys, summarizeAdapterWiring, } from "../core/hooks/adapter/wiring.js";
35
35
  import { detectInstalledAdapters } from "../core/workflow/adapters.js";
36
36
  import { applyIndexerExclusions } from "../lib/indexer-exclusions.js";
37
37
  import { applyInstructions, checkInstructions, } from "../lib/instructions/apply.js";
@@ -102,7 +102,11 @@ function checkAdapterHooks(projectRoot, adapter, bin) {
102
102
  let drift = false;
103
103
  try {
104
104
  const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
105
- const hookDiff = diffWiring(settings, spec, { agentHookPath: agentHook, adapter });
105
+ const hookDiff = diffWiring(settings, spec, {
106
+ agentHookPath: agentHook,
107
+ adapter,
108
+ windowsBridgeEntryPoint: codexWindowsBridgeConfig(projectRoot).entryPoint,
109
+ });
106
110
  drift =
107
111
  hookDiff.missing.length > 0 ||
108
112
  hookDiff.stale.length > 0 ||
@@ -379,7 +383,11 @@ export function registerInitCommand(program, emit, binName) {
379
383
  else {
380
384
  settings = {};
381
385
  }
382
- const { wired, already, removed, upgraded } = wireHooks(settings, spec, agentHook, adapter);
386
+ const bridge = codexWindowsBridgeConfig(projectRoot);
387
+ if (bridge.error && spec.windowsBridgeHooks) {
388
+ actions.push(`⚠ ${bridge.error}; writing ${adapter} hooks without commandWindows`);
389
+ }
390
+ const { wired, already, removed, upgraded } = wireHooks(settings, spec, agentHook, adapter, { windowsBridgeEntryPoint: bridge.entryPoint });
383
391
  if (wired === 0 && removed === 0 && upgraded === 0) {
384
392
  actions.push(`· all ${spec.events.length} ${adapter} hooks already wired in ${rel(projectRoot, settingsPath)}`);
385
393
  }
@@ -481,7 +489,7 @@ export function codexHookReviewAction(adapter) {
481
489
  *
482
490
  * Token-aware matching keeps `stop` from matching `stop-failure`.
483
491
  */
484
- export function wireHooks(settings, spec, agentHookPath, adapter) {
492
+ export function wireHooks(settings, spec, agentHookPath, adapter, options) {
485
493
  if (spec.rootVersion !== undefined && settings.version === undefined) {
486
494
  settings.version = spec.rootVersion;
487
495
  }
@@ -498,6 +506,13 @@ export function wireHooks(settings, spec, agentHookPath, adapter) {
498
506
  }
499
507
  for (const { settingsKey, subcommand } of spec.events) {
500
508
  const command = hookCommand(spec, agentHookPath, subcommand, adapter);
509
+ const timeout = hookTimeoutFor(spec, subcommand);
510
+ const commandWindows = expectedCommandWindows(spec, agentHookPath, subcommand, adapter, options);
511
+ const handlerFields = {
512
+ timeout,
513
+ commandWindows,
514
+ windowsBridge: spec.windowsBridgeHooks === true,
515
+ };
501
516
  let present = false;
502
517
  for (const key of Object.keys(settings.hooks)) {
503
518
  if (key === settingsKey)
@@ -507,7 +522,7 @@ export function wireHooks(settings, spec, agentHookPath, adapter) {
507
522
  const groups = settings.hooks[settingsKey] ?? [];
508
523
  const nextGroups = [];
509
524
  for (const group of groups) {
510
- const normalized = normalizeEventGroup(group, subcommand, command, present);
525
+ const normalized = normalizeEventGroup(group, subcommand, command, handlerFields, present);
511
526
  if (normalized.found && !present)
512
527
  present = true;
513
528
  upgraded += normalized.upgraded;
@@ -526,7 +541,7 @@ export function wireHooks(settings, spec, agentHookPath, adapter) {
526
541
  const current = settings.hooks[settingsKey] ?? [];
527
542
  current.push(makeEntry(spec.entryShape, command, spec.entryShape === "cursor" && subcommand === "stop"
528
543
  ? { loop_limit: CURSOR_STOP_FOLLOWUP_LOOP_LIMIT }
529
- : undefined));
544
+ : { timeout, commandWindows }));
530
545
  settings.hooks[settingsKey] = current;
531
546
  wired++;
532
547
  }
@@ -542,7 +557,7 @@ export function wireHooks(settings, spec, agentHookPath, adapter) {
542
557
  * that don't set the var. Only the env expansion is quoted so the
543
558
  * `agent-hook <subcommand> ` wiring match stays byte-identical.
544
559
  */
545
- function normalizeEventGroup(group, subcommand, canonical, alreadyFound) {
560
+ function normalizeEventGroup(group, subcommand, canonical, fields, alreadyFound) {
546
561
  let found = false;
547
562
  let upgraded = 0;
548
563
  let removed = 0;
@@ -561,7 +576,8 @@ function normalizeEventGroup(group, subcommand, canonical, alreadyFound) {
561
576
  }
562
577
  if ("hooks" in group && Array.isArray(group.hooks)) {
563
578
  const kept = [];
564
- for (const hook of group.hooks) {
579
+ for (const original of group.hooks) {
580
+ let hook = original;
565
581
  if (typeof hook.command !== "string" || !commandWiresSubcommand(hook.command, subcommand)) {
566
582
  kept.push(hook);
567
583
  continue;
@@ -571,8 +587,26 @@ function normalizeEventGroup(group, subcommand, canonical, alreadyFound) {
571
587
  continue;
572
588
  }
573
589
  found = true;
590
+ let changed = false;
574
591
  if (hook.command !== canonical) {
575
592
  hook.command = canonical;
593
+ changed = true;
594
+ }
595
+ if (fields.timeout !== undefined && hook.timeout !== fields.timeout) {
596
+ hook.timeout = fields.timeout;
597
+ changed = true;
598
+ }
599
+ // Harnery owns commandWindows on its own handlers for adapters that run
600
+ // it: write the opted-in command, or remove one a project opted out of.
601
+ if (fields.windowsBridge && hook.commandWindows !== fields.commandWindows) {
602
+ if (fields.commandWindows === undefined)
603
+ delete hook.commandWindows;
604
+ else
605
+ hook.commandWindows = fields.commandWindows;
606
+ changed = true;
607
+ }
608
+ if (changed) {
609
+ hook = orderHandlerKeys(hook);
576
610
  upgraded++;
577
611
  }
578
612
  kept.push(hook);
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
+ export declare function registerRmCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
4
+ //# sourceMappingURL=rm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rm.d.ts","sourceRoot":"","sources":["../../src/commands/rm.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAU1E,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAgFN"}
@@ -0,0 +1,87 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { resolveCoordRoot } from "../core/agents/coord-client.js";
5
+ import { readCoordinationViewV3, requireAuthoritySafeCoordinationViewV3, } from "../core/events/v3/coordination-view.js";
6
+ import { liveInstanceIdV3 } from "../core/events/v3/live-route-observer.js";
7
+ import { resolveOwner } from "../core/hooks/resolve/owner.js";
8
+ import { guardedRemove } from "../lib/guarded-remove.js";
9
+ export function registerRmCommand(program, emit, context) {
10
+ program
11
+ .command("rm <paths...>")
12
+ .description("Preview guarded removal of untracked files; --yes applies permanent deletion")
13
+ .requiredOption("--root <directory>", "Allowed directory; each target must be strictly inside it")
14
+ .option("-r, --recursive", "Permit directory removal after inspecting its complete contents")
15
+ .option("--allow-protected <path>", "Remove this one protected path (Git metadata, credentials, Harnery state, keys) inside a target; repeat per path", (value, previous = []) => [...previous, value])
16
+ .option("--yes", "Permanently remove validated targets")
17
+ .option("--dry-run", "Preview only, even with --yes")
18
+ .option("--json", "Emit the versioned removal report")
19
+ .action((paths, options) => {
20
+ try {
21
+ const coordRoot = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? resolveCoordRoot();
22
+ const checkClaims = (targets) => {
23
+ if (!coordRoot || !existsSync(resolve(coordRoot, ".harnery")))
24
+ return;
25
+ const view = requireAuthoritySafeCoordinationViewV3(readCoordinationViewV3(coordRoot));
26
+ const owner = resolveOwner({ payload: null, coordRoot });
27
+ const ownId = owner ? liveInstanceIdV3(owner.instance_id) : undefined;
28
+ const ownSession = ownId ? view.instances[ownId]?.session_id : undefined;
29
+ for (const peer of Object.values(view.instances)) {
30
+ if (!peer.authority_eligible ||
31
+ peer.instance_id === ownId ||
32
+ (ownSession && peer.session_id === ownSession))
33
+ continue;
34
+ for (const claim of peer.files_touched) {
35
+ const claimed = resolve(coordRoot, claim);
36
+ for (const target of targets) {
37
+ if (overlaps(target, claimed))
38
+ throw new Error(`Target overlaps another agent's claim: ${claim} (${peer.instance_id})`);
39
+ }
40
+ }
41
+ }
42
+ };
43
+ // The workspace this command coordinates, and the checkout it runs in:
44
+ // their own .git and .harnery can never be allowed.
45
+ const liveRoots = [coordRoot, gitTopLevel(process.cwd())].filter((dir) => Boolean(dir));
46
+ const report = guardedRemove(paths, { ...options, liveRoots }, checkClaims);
47
+ if (options.json) {
48
+ emit.config({ format: "json" });
49
+ emit.data(report);
50
+ }
51
+ else {
52
+ const lines = [
53
+ `${report.applied ? "Removed" : "Would remove"} ${report.targets.length} target(s), ${report.entries} entries, ${report.bytes} bytes:`,
54
+ ...report.targets,
55
+ ];
56
+ if (report.protected.length)
57
+ lines.push("Protected paths named with --allow-protected:", ...report.protected.map((path) => ` ${path}`));
58
+ if (report.links.length)
59
+ lines.push("Links removed as links (their targets are untouched):", ...report.links.map((link) => ` ${link.path} -> ${link.target}`));
60
+ if (!report.applied)
61
+ lines.push("Preview only. Add --yes to permanently delete these targets.");
62
+ emit.text(lines.join("\n"));
63
+ }
64
+ }
65
+ catch (error) {
66
+ emit.error({
67
+ code: "removal_refused",
68
+ message: error instanceof Error ? error.message : String(error),
69
+ });
70
+ emit.setExitCode(1);
71
+ }
72
+ });
73
+ }
74
+ function gitTopLevel(dir) {
75
+ const result = spawnSync("git", ["-C", dir, "rev-parse", "--show-toplevel"], {
76
+ encoding: "utf8",
77
+ timeout: 10_000,
78
+ });
79
+ return result.status === 0 ? result.stdout.trim() || undefined : undefined;
80
+ }
81
+ function overlaps(a, b) {
82
+ const inside = (parent, child) => {
83
+ const rel = relative(parent, child);
84
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
85
+ };
86
+ return inside(a, b) || inside(b, a);
87
+ }