harnery 0.41.0 → 0.42.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 (212) hide show
  1. package/dist/commander.js +3 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +9 -1
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +113 -12
  6. package/dist/commands/browse.d.ts +164 -0
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +263 -51
  9. package/dist/commands/claude-desktop.d.ts.map +1 -1
  10. package/dist/commands/claude-desktop.js +52 -0
  11. package/dist/commands/disk.d.ts +6 -0
  12. package/dist/commands/disk.d.ts.map +1 -0
  13. package/dist/commands/disk.js +80 -0
  14. package/dist/commands/doctor.d.ts +8 -0
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +48 -1
  17. package/dist/commands/files.d.ts +16 -0
  18. package/dist/commands/files.d.ts.map +1 -1
  19. package/dist/commands/files.js +54 -2
  20. package/dist/commands/init.d.ts +2 -2
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +43 -9
  23. package/dist/commands/rm.d.ts +4 -0
  24. package/dist/commands/rm.d.ts.map +1 -0
  25. package/dist/commands/rm.js +87 -0
  26. package/dist/commands/servers.d.ts +7 -0
  27. package/dist/commands/servers.d.ts.map +1 -0
  28. package/dist/commands/servers.js +394 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +82 -2
  31. package/dist/core/adapters/profiles.d.ts +9 -0
  32. package/dist/core/adapters/profiles.d.ts.map +1 -1
  33. package/dist/core/adapters/profiles.js +22 -1
  34. package/dist/core/adapters/types.d.ts +9 -0
  35. package/dist/core/adapters/types.d.ts.map +1 -1
  36. package/dist/core/agents/cli.js +13 -6
  37. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  38. package/dist/core/agents/render/session-context.js +2 -0
  39. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  40. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  41. package/dist/core/agents/state/live-coordination-view.js +60 -0
  42. package/dist/core/artifacts/delivery-card.js +9 -2
  43. package/dist/core/artifacts/index.d.ts +140 -4
  44. package/dist/core/artifacts/index.d.ts.map +1 -1
  45. package/dist/core/artifacts/index.js +522 -49
  46. package/dist/core/config.d.ts +68 -11
  47. package/dist/core/config.d.ts.map +1 -1
  48. package/dist/core/config.js +128 -3
  49. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  50. package/dist/core/diagnostics/bundle.js +2 -2
  51. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  52. package/dist/core/events/v3/producers/recorder.js +16 -0
  53. package/dist/core/events/v3/projection.js +9 -1
  54. package/dist/core/hooks/adapter/events.d.ts +17 -0
  55. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  56. package/dist/core/hooks/adapter/events.js +5 -0
  57. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  58. package/dist/core/hooks/adapter/wiring.d.ts +37 -7
  59. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  60. package/dist/core/hooks/adapter/wiring.js +62 -4
  61. package/dist/core/hooks/cli.js +45 -6
  62. package/dist/core/hooks/run-markers.d.ts +37 -0
  63. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  64. package/dist/core/hooks/run-markers.js +109 -0
  65. package/dist/core/servers/index.d.ts +211 -0
  66. package/dist/core/servers/index.d.ts.map +1 -0
  67. package/dist/core/servers/index.js +560 -0
  68. package/dist/core/servers/net.d.ts +58 -0
  69. package/dist/core/servers/net.d.ts.map +1 -0
  70. package/dist/core/servers/net.js +270 -0
  71. package/dist/core/servers/tunnels.d.ts +14 -0
  72. package/dist/core/servers/tunnels.d.ts.map +1 -0
  73. package/dist/core/servers/tunnels.js +33 -0
  74. package/dist/core/storage/builtins.js +1 -0
  75. package/dist/core/workflow/engine.d.ts.map +1 -1
  76. package/dist/core/workflow/engine.js +30 -1
  77. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  78. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  79. package/dist/core/workflow/sandbox-projection.js +23 -0
  80. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  81. package/dist/core/workflow/spawn-claude.js +6 -1
  82. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  83. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  84. package/dist/core/workflow/spawn-codex.js +20 -1
  85. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  86. package/dist/core/workflow/spawn-cursor.js +6 -1
  87. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  88. package/dist/core/workflow/spawn-opencode.js +4 -1
  89. package/dist/core/workflow/types.d.ts +47 -2
  90. package/dist/core/workflow/types.d.ts.map +1 -1
  91. package/dist/core/workflow/worker-access.d.ts +92 -0
  92. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  93. package/dist/core/workflow/worker-access.js +450 -0
  94. package/dist/lib/browser/batch-steps.d.ts +18 -0
  95. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  96. package/dist/lib/browser/batch-steps.js +82 -0
  97. package/dist/lib/browser/collect/collector.d.ts +99 -0
  98. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  99. package/dist/lib/browser/collect/collector.js +202 -0
  100. package/dist/lib/browser/collect/fields.d.ts +31 -0
  101. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  102. package/dist/lib/browser/collect/fields.js +75 -0
  103. package/dist/lib/browser/collect/index.d.ts +6 -0
  104. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  105. package/dist/lib/browser/collect/index.js +5 -0
  106. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  107. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  108. package/dist/lib/browser/collect/page-driver.js +306 -0
  109. package/dist/lib/browser/collect/presets.d.ts +52 -0
  110. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  111. package/dist/lib/browser/collect/presets.js +176 -0
  112. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  113. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  114. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  115. package/dist/lib/browser/index.d.ts +1 -1
  116. package/dist/lib/browser/index.d.ts.map +1 -1
  117. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  118. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  119. package/dist/lib/browser/page-review-judge.js +13 -0
  120. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  121. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  122. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  123. package/dist/lib/browser/qa-run.js +1 -0
  124. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  125. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  126. package/dist/lib/browser/windows-chrome.js +179 -0
  127. package/dist/lib/claude-desktop-share.d.ts +136 -0
  128. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  129. package/dist/lib/claude-desktop-share.js +394 -0
  130. package/dist/lib/disk-usage.d.ts +50 -0
  131. package/dist/lib/disk-usage.d.ts.map +1 -0
  132. package/dist/lib/disk-usage.js +303 -0
  133. package/dist/lib/docs-lint.d.ts +6 -0
  134. package/dist/lib/docs-lint.d.ts.map +1 -1
  135. package/dist/lib/docs-lint.js +8 -3
  136. package/dist/lib/guarded-remove.d.ts +34 -0
  137. package/dist/lib/guarded-remove.d.ts.map +1 -0
  138. package/dist/lib/guarded-remove.js +310 -0
  139. package/dist/lib/instructions/templates.js +8 -8
  140. package/dist/lib/tunnel/error-page.d.ts +1 -1
  141. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  142. package/dist/lib/tunnel/error-page.js +19 -8
  143. package/dist/lib/tunnel/gate.js +20 -1
  144. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  145. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  146. package/dist/lib/tunnel/path-scope.js +62 -0
  147. package/dist/lib/tunnel/state.d.ts +5 -0
  148. package/dist/lib/tunnel/state.d.ts.map +1 -1
  149. package/dist/lib/tunnel/state.js +17 -3
  150. package/package.json +6 -1
  151. package/schemas/config.schema.json +91 -0
  152. package/src/commander.ts +19 -0
  153. package/src/commands/agents.ts +14 -0
  154. package/src/commands/artifacts.ts +171 -19
  155. package/src/commands/browse.ts +400 -57
  156. package/src/commands/claude-desktop.ts +61 -0
  157. package/src/commands/disk.ts +114 -0
  158. package/src/commands/doctor.ts +52 -1
  159. package/src/commands/files.ts +93 -2
  160. package/src/commands/init.ts +55 -5
  161. package/src/commands/rm.ts +115 -0
  162. package/src/commands/servers.ts +506 -0
  163. package/src/commands/tunnel.ts +102 -2
  164. package/src/core/adapters/profiles.ts +22 -1
  165. package/src/core/adapters/types.ts +10 -0
  166. package/src/core/agents/cli.ts +14 -6
  167. package/src/core/agents/render/session-context.ts +1 -0
  168. package/src/core/agents/state/live-coordination-view.ts +73 -0
  169. package/src/core/artifacts/delivery-card.ts +10 -2
  170. package/src/core/artifacts/index.ts +697 -44
  171. package/src/core/config.ts +183 -4
  172. package/src/core/diagnostics/bundle.ts +2 -1
  173. package/src/core/events/v3/producers/recorder.ts +16 -0
  174. package/src/core/events/v3/projection.ts +11 -1
  175. package/src/core/hooks/adapter/events.ts +19 -0
  176. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  177. package/src/core/hooks/adapter/wiring.ts +116 -8
  178. package/src/core/hooks/cli.ts +47 -6
  179. package/src/core/hooks/run-markers.ts +132 -0
  180. package/src/core/servers/index.ts +712 -0
  181. package/src/core/servers/net.ts +283 -0
  182. package/src/core/servers/tunnels.ts +39 -0
  183. package/src/core/storage/builtins.ts +1 -0
  184. package/src/core/workflow/engine.ts +38 -1
  185. package/src/core/workflow/sandbox-projection.ts +38 -2
  186. package/src/core/workflow/spawn-claude.ts +5 -1
  187. package/src/core/workflow/spawn-codex.ts +18 -1
  188. package/src/core/workflow/spawn-cursor.ts +5 -1
  189. package/src/core/workflow/spawn-opencode.ts +4 -1
  190. package/src/core/workflow/types.ts +62 -2
  191. package/src/core/workflow/worker-access.ts +525 -0
  192. package/src/lib/browser/batch-steps.ts +79 -0
  193. package/src/lib/browser/collect/collector.ts +306 -0
  194. package/src/lib/browser/collect/fields.ts +87 -0
  195. package/src/lib/browser/collect/index.ts +47 -0
  196. package/src/lib/browser/collect/page-driver.ts +327 -0
  197. package/src/lib/browser/collect/presets.ts +237 -0
  198. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  199. package/src/lib/browser/index.ts +1 -0
  200. package/src/lib/browser/page-review-judge.ts +16 -0
  201. package/src/lib/browser/qa-run-contracts.ts +11 -0
  202. package/src/lib/browser/qa-run.ts +1 -0
  203. package/src/lib/browser/windows-chrome.ts +227 -0
  204. package/src/lib/claude-desktop-share.ts +511 -0
  205. package/src/lib/disk-usage.ts +347 -0
  206. package/src/lib/docs-lint.ts +8 -3
  207. package/src/lib/guarded-remove.ts +376 -0
  208. package/src/lib/instructions/templates.ts +8 -8
  209. package/src/lib/tunnel/error-page.ts +25 -9
  210. package/src/lib/tunnel/gate.ts +22 -1
  211. package/src/lib/tunnel/path-scope.ts +73 -0
  212. package/src/lib/tunnel/state.ts +20 -3
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import type { Command } from "commander";
2
+ import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
+ import { type ServersReport } from "../core/servers/index.js";
4
+ export declare function registerServersCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
5
+ /** Plain-text table grouped by kind; exported for tests. */
6
+ export declare function renderReport(report: ServersReport, bin: string): string;
7
+ //# sourceMappingURL=servers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"servers.d.ts","sourceRoot":"","sources":["../../src/commands/servers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG1E,OAAO,EAWL,KAAK,aAAa,EAMnB,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA0YN;AA8BD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAmDvE"}