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
@@ -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"}
@@ -0,0 +1,394 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { resolveCoordRoot } from "../core/agents/coord-client.js";
4
+ import { resolveBinName, serverGcConfig } from "../core/config.js";
5
+ import { adoptServer, allocateServerPort, isValidServerId, listServers, planServerGc, readServer, registerServer, SERVER_DEFAULT_IDLE_HOURS, SERVER_DEFAULT_OWNER_STALE_HOURS, stopServer, touchServer, unregisterServer, } from "../core/servers/index.js";
6
+ export function registerServersCommand(program, emit, context) {
7
+ const root = () => {
8
+ const resolved = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? resolveCoordRoot();
9
+ if (!resolved)
10
+ throw new Error("not inside a Harnery project");
11
+ return resolved;
12
+ };
13
+ const fail = (error, code = "servers_error") => {
14
+ emit.error({ code, message: error instanceof Error ? error.message : String(error) });
15
+ emit.setExitCode(1);
16
+ };
17
+ const cmd = program
18
+ .command("servers")
19
+ .description("Every local server started in this project: list, stop, clean up idle session servers, read logs");
20
+ cmd
21
+ .command("list", { isDefault: true })
22
+ .alias("status")
23
+ .description("List registered servers and any unregistered listener running inside the project")
24
+ .option("--no-scan", "Skip the scan for unregistered listeners")
25
+ .option("--json", "Emit the versioned report")
26
+ .action((options) => {
27
+ try {
28
+ const report = listServers({ coordRoot: root(), scan: options.scan !== false });
29
+ if (options.json) {
30
+ emit.config({ format: "json" });
31
+ emit.data(report);
32
+ }
33
+ else
34
+ emit.text(renderReport(report, resolveBinName(report.coord_root)));
35
+ }
36
+ catch (error) {
37
+ fail(error);
38
+ }
39
+ });
40
+ cmd
41
+ .command("stop [ids...]")
42
+ .description("Stop named servers now; --type selects several and previews unless --yes")
43
+ .option("--type <type>", "Select every running server of this type")
44
+ .option("--pid <pid>", "Stop an unregistered listener found by the list scan (repeatable)", (value, previous = []) => [...previous, value])
45
+ .option("--kind <kind>", "With --type, limit to session or service")
46
+ .option("--yes", "Apply a --type selection")
47
+ .option("--json", "Emit JSON")
48
+ .action(async (ids, options) => {
49
+ try {
50
+ const coordRoot = root();
51
+ const pids = (options.pid ?? []).map((value) => integer(value, "--pid"));
52
+ if (!ids.length && !options.type && !pids.length)
53
+ throw new Error("name server ids, or pass --type or --pid");
54
+ const report = listServers({ coordRoot, scan: pids.length > 0 });
55
+ const strays = pids.map((pid) => {
56
+ const found = report.unregistered.find((item) => item.pid === pid);
57
+ if (!found)
58
+ throw new Error(`pid ${pid} is not an unregistered listener inside this project; only pids shown by "servers list" can be stopped this way`);
59
+ return found;
60
+ });
61
+ const byId = new Map(report.servers.map((view) => [view.record.id, view]));
62
+ const missing = ids.filter((id) => !byId.has(id));
63
+ if (missing.length)
64
+ throw new Error(`no running server named: ${missing.join(", ")}`);
65
+ const selected = [
66
+ ...ids.map((id) => byId.get(id)),
67
+ ...(options.type
68
+ ? report.servers.filter((view) => view.record.type === options.type &&
69
+ view.state === "running" &&
70
+ (!options.kind || view.record.kind === options.kind) &&
71
+ !ids.includes(view.record.id))
72
+ : []),
73
+ ];
74
+ const preview = Boolean(options.type) && !options.yes;
75
+ const results = [];
76
+ for (const stray of strays) {
77
+ results.push(await stopServer({
78
+ schema_version: 1,
79
+ id: `pid-${stray.pid}`,
80
+ kind: "session",
81
+ type: "unregistered",
82
+ label: stray.command,
83
+ pid: stray.pid,
84
+ host: hostname(),
85
+ started_at: new Date().toISOString(),
86
+ registered_at: new Date().toISOString(),
87
+ }, { coordRoot }));
88
+ }
89
+ for (const view of selected) {
90
+ if (preview && !ids.includes(view.record.id))
91
+ results.push({ id: view.record.id, would_stop: true });
92
+ else
93
+ results.push(await stopServer(view.record, { coordRoot }));
94
+ }
95
+ if (options.json) {
96
+ emit.config({ format: "json" });
97
+ emit.data({ applied: !preview, results });
98
+ }
99
+ else {
100
+ emit.text(results.length
101
+ ? results
102
+ .map((r) => "would_stop" in r
103
+ ? `would stop ${r.id}`
104
+ : `${r.stopped ? "stopped" : r.method === "not-running" ? "already stopped" : "could not stop"} ${r.id}${r.detail ? ` (${r.detail})` : ""}`)
105
+ .join("\n") +
106
+ (preview ? `\nPreview only. Re-run with --yes to stop these.` : "")
107
+ : "No matching running servers.");
108
+ }
109
+ if (results.some((r) => "stopped" in r && !r.stopped && r.method === "refused"))
110
+ emit.setExitCode(1);
111
+ }
112
+ catch (error) {
113
+ fail(error);
114
+ }
115
+ });
116
+ cmd
117
+ .command("gc")
118
+ .description(`Stop idle session servers whose starting agent ended (preview unless --yes); services are never touched`)
119
+ .option("--idle-hours <hours>", `Idle window before a server may stop (default ${SERVER_DEFAULT_IDLE_HOURS})`)
120
+ .option("--owner-stale-hours <hours>", `Treat a starting agent unobserved this long as gone (default ${SERVER_DEFAULT_OWNER_STALE_HOURS})`)
121
+ .option("--yes", "Stop the selected servers")
122
+ .option("--json", "Emit JSON")
123
+ .action(async (options) => {
124
+ try {
125
+ const coordRoot = root();
126
+ const configured = serverGcConfig(coordRoot);
127
+ const idleHours = positive(options.idleHours, configured.idle_hours ?? SERVER_DEFAULT_IDLE_HOURS, "--idle-hours");
128
+ const ownerStaleHours = positive(options.ownerStaleHours, configured.owner_stale_hours ?? SERVER_DEFAULT_OWNER_STALE_HOURS, "--owner-stale-hours");
129
+ const report = listServers({ coordRoot, scan: false, ownerStaleHours });
130
+ const decisions = planServerGc(report.servers, { idleHours, ownerStaleHours });
131
+ const stops = decisions.filter((d) => d.action === "stop");
132
+ const results = [];
133
+ if (options.yes) {
134
+ for (const decision of stops) {
135
+ const view = report.servers.find((v) => v.record.id === decision.id);
136
+ results.push(await stopServer(view.record, { coordRoot }));
137
+ }
138
+ }
139
+ if (options.json) {
140
+ emit.config({ format: "json" });
141
+ emit.data({ applied: Boolean(options.yes), pruned: report.pruned, decisions, results });
142
+ }
143
+ else {
144
+ const lines = decisions.map((d) => `${d.action === "stop" ? (options.yes ? "stopped" : "would stop") : "keep"} ${d.id} ${d.reason}`);
145
+ if (report.pruned.length)
146
+ lines.push(`Removed records of exited servers: ${report.pruned.join(", ")}`);
147
+ if (!decisions.length)
148
+ lines.push("No registered servers.");
149
+ if (stops.length && !options.yes)
150
+ lines.push("Preview only. Re-run with --yes to stop.");
151
+ emit.text(lines.join("\n"));
152
+ }
153
+ }
154
+ catch (error) {
155
+ fail(error);
156
+ }
157
+ });
158
+ cmd
159
+ .command("adopt")
160
+ .description("Register running servers that never registered (only pids the list reports as unregistered)")
161
+ .option("--pid <pid>", "Adopt this unregistered listener (repeatable)", (value, previous = []) => [...previous, value])
162
+ .option("--all", "Adopt every unregistered listener the scan reports")
163
+ .option("--type <type>", "Server family to record", "adopted")
164
+ .option("--kind <kind>", "session (cleaned up when idle) or service", "session")
165
+ .option("--label <label>", "Human-readable name (single --pid only)")
166
+ .option("--scope <path>", "What the server serves (default: its working directory)")
167
+ .option("--no-owner", "Record no owner, so gc never stops it")
168
+ .option("--json", "Emit JSON")
169
+ .action((options) => {
170
+ try {
171
+ const coordRoot = root();
172
+ if (options.kind !== "session" && options.kind !== "service")
173
+ throw new Error("--kind must be session or service");
174
+ const pids = options.all
175
+ ? [...new Set(listServers({ coordRoot, sample: false }).unregistered.map((u) => u.pid))]
176
+ : (options.pid ?? []).map((value) => integer(value, "--pid"));
177
+ if (!pids.length)
178
+ throw new Error(options.all ? "no unregistered listeners" : "pass --pid or --all");
179
+ if (options.label && pids.length > 1)
180
+ throw new Error("--label needs a single --pid");
181
+ const records = pids.map((pid) => adoptServer(pid, {
182
+ coordRoot,
183
+ type: options.type,
184
+ kind: options.kind,
185
+ ...(options.label ? { label: options.label } : {}),
186
+ ...(options.scope ? { scope: options.scope } : {}),
187
+ owner: options.owner === false ? "none" : "self",
188
+ }));
189
+ if (options.json) {
190
+ emit.config({ format: "json" });
191
+ emit.data({ adopted: records });
192
+ }
193
+ else
194
+ emit.text(records.map((r) => `adopted ${r.id} ${r.url ?? ""}`).join("\n"));
195
+ }
196
+ catch (error) {
197
+ fail(error);
198
+ }
199
+ });
200
+ cmd
201
+ .command("port <type>")
202
+ .description("Print a free port from the type's configured range (.harnery/config.jsonc servers.port_ranges)")
203
+ .action((type) => {
204
+ try {
205
+ const port = allocateServerPort(type, { coordRoot: root() });
206
+ if (port === null)
207
+ throw new Error(`no port range configured for ${type}`);
208
+ emit.text(String(port));
209
+ }
210
+ catch (error) {
211
+ fail(error);
212
+ }
213
+ });
214
+ cmd
215
+ .command("logs <id>")
216
+ .description("Print the end of a server's log file")
217
+ .option("-n, --lines <n>", "Lines to print", "60")
218
+ .action((id, options) => {
219
+ try {
220
+ const record = readServer(id, { coordRoot: root() });
221
+ if (!record)
222
+ throw new Error(`no server named ${id}`);
223
+ if (!record.log || !existsSync(record.log))
224
+ throw new Error(`${id} did not record a log file`);
225
+ const lines = positive(options.lines, 60, "--lines");
226
+ const size = statSync(record.log).size;
227
+ const content = readFileSync(record.log, "utf8").slice(Math.max(0, size - 512 * 1024));
228
+ emit.text(content
229
+ .split("\n")
230
+ .slice(-Math.floor(lines) - 1)
231
+ .join("\n"));
232
+ }
233
+ catch (error) {
234
+ fail(error);
235
+ }
236
+ });
237
+ cmd
238
+ .command("open <id>")
239
+ .description("Print a server's URL")
240
+ .action((id) => {
241
+ try {
242
+ const record = readServer(id, { coordRoot: root() });
243
+ if (!record)
244
+ throw new Error(`no server named ${id}`);
245
+ if (!record.url)
246
+ throw new Error(`${id} did not record a URL`);
247
+ emit.text(record.url);
248
+ }
249
+ catch (error) {
250
+ fail(error);
251
+ }
252
+ });
253
+ cmd
254
+ .command("register")
255
+ .description("Record a server started by a script or another language (prints the record; the calling agent is recorded as owner)")
256
+ .requiredOption("--type <type>", "Server family, for example preview or review")
257
+ .option("--kind <kind>", "session (per piece of work) or service (long-running)", "session")
258
+ .option("--pid <pid>", "Process that owns the server's lifetime (default: this command's parent)")
259
+ .option("--port <port>", "Listening port")
260
+ .option("--id <id>", "Stable id (default derived from type and scope)")
261
+ .option("--url <url>", "Local URL")
262
+ .option("--scope <path>", "What the server serves, usually a directory")
263
+ .option("--label <label>", "Human-readable name")
264
+ .option("--log <path>", "Log file")
265
+ .option("--cwd <path>", "Working directory")
266
+ .action((options) => {
267
+ try {
268
+ if (options.kind !== "session" && options.kind !== "service")
269
+ throw new Error("--kind must be session or service");
270
+ if (options.id && !isValidServerId(options.id))
271
+ throw new Error(`invalid id: ${options.id}`);
272
+ const record = registerServer({
273
+ kind: options.kind,
274
+ type: options.type,
275
+ pid: options.pid ? integer(options.pid, "--pid") : process.ppid,
276
+ ...(options.port ? { port: integer(options.port, "--port") } : {}),
277
+ ...(options.id ? { id: options.id } : {}),
278
+ ...(options.url ? { url: options.url } : {}),
279
+ ...(options.scope ? { scope: options.scope } : {}),
280
+ ...(options.label ? { label: options.label } : {}),
281
+ ...(options.log ? { log: options.log } : {}),
282
+ ...(options.cwd ? { cwd: options.cwd } : {}),
283
+ }, { coordRoot: root() });
284
+ emit.config({ format: "json" });
285
+ emit.data(record);
286
+ }
287
+ catch (error) {
288
+ fail(error);
289
+ }
290
+ });
291
+ cmd
292
+ .command("unregister <id>")
293
+ .description("Remove a server's record (with --pid, only if the record still names that pid)")
294
+ .option("--pid <pid>", "Only remove when the record names this pid")
295
+ .action((id, options) => {
296
+ try {
297
+ const removed = unregisterServer(id, {
298
+ coordRoot: root(),
299
+ ...(options.pid ? { pid: integer(options.pid, "--pid") } : {}),
300
+ });
301
+ emit.config({ format: "json" });
302
+ emit.data({ id, removed });
303
+ }
304
+ catch (error) {
305
+ fail(error);
306
+ }
307
+ });
308
+ cmd
309
+ .command("touch <id>")
310
+ .description("Mark a server as used now, postponing idle cleanup")
311
+ .action((id) => {
312
+ try {
313
+ touchServer(id, { coordRoot: root() });
314
+ }
315
+ catch (error) {
316
+ fail(error);
317
+ }
318
+ });
319
+ }
320
+ function integer(value, flag) {
321
+ const parsed = Number(value);
322
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
323
+ throw new Error(`${flag} must be a positive integer`);
324
+ return parsed;
325
+ }
326
+ function positive(value, fallback, flag) {
327
+ if (value === undefined)
328
+ return fallback;
329
+ const parsed = Number(value);
330
+ if (!Number.isFinite(parsed) || parsed <= 0)
331
+ throw new Error(`${flag} must be a positive number`);
332
+ return parsed;
333
+ }
334
+ function duration(ms) {
335
+ const minutes = Math.round(ms / 60_000);
336
+ if (minutes < 60)
337
+ return `${minutes}m`;
338
+ const hours = Math.floor(minutes / 60);
339
+ if (hours < 48)
340
+ return `${hours}h${String(minutes % 60).padStart(2, "0")}m`;
341
+ return `${Math.floor(hours / 24)}d`;
342
+ }
343
+ function ownerText(view) {
344
+ if (!view.record.owner)
345
+ return "no agent";
346
+ const name = view.record.owner.name ?? view.record.owner.instance_id.slice(0, 8);
347
+ return view.owner_state === "live" ? name : `${name} (${view.owner_state})`;
348
+ }
349
+ /** Plain-text table grouped by kind; exported for tests. */
350
+ export function renderReport(report, bin) {
351
+ const lines = [];
352
+ for (const kind of ["service", "session"]) {
353
+ const views = report.servers.filter((view) => view.record.kind === kind);
354
+ lines.push(kind === "service"
355
+ ? "Services (stopped only by hand)"
356
+ : "Session servers (cleaned up when idle and their agent is gone)");
357
+ if (!views.length) {
358
+ lines.push(" none");
359
+ continue;
360
+ }
361
+ for (const view of views) {
362
+ const { record } = view;
363
+ const state = view.state === "running" ? "" : ` [${view.state}]`;
364
+ const started = Date.parse(record.started_at);
365
+ const use = kind === "service"
366
+ ? `up ${Number.isFinite(started) ? duration(Date.now() - started) : "?"}`
367
+ : view.connections === null
368
+ ? `idle ${duration(view.idle_ms)}`
369
+ : view.connections > 0
370
+ ? `${view.connections} open connection(s)`
371
+ : `idle ${duration(view.idle_ms)}`;
372
+ lines.push(` ${record.id}${state} ${record.url ?? (record.port ? `port ${record.port}` : "")}`, ` ${record.label} · ${ownerText(view)} · ${use}`);
373
+ }
374
+ }
375
+ if (report.scan === "unsupported") {
376
+ lines.push("Unregistered listeners: not checked on this platform");
377
+ }
378
+ else if (report.unregistered.length) {
379
+ const byPid = new Map();
380
+ for (const item of report.unregistered) {
381
+ const entry = byPid.get(item.pid) ?? { ports: [], command: item.command };
382
+ entry.ports.push(item.port);
383
+ byPid.set(item.pid, entry);
384
+ }
385
+ lines.push(`Unregistered servers inside this project (${byPid.size} processes):`);
386
+ for (const [pid, entry] of byPid)
387
+ lines.push(` pid ${pid} port ${entry.ports.join(", ")} ${entry.command.slice(0, 100)}`);
388
+ lines.push(` These started without registering. Stop one with: ${bin} servers stop --pid <pid>`);
389
+ }
390
+ if (report.pruned.length)
391
+ lines.push(`Removed records of exited servers: ${report.pruned.join(", ")}`);
392
+ lines.push(`Control: ${bin} servers stop <id> · ${bin} servers gc · ${bin} servers logs <id>`);
393
+ return lines.join("\n");
394
+ }
@@ -1,10 +1,48 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext, HarneryProgramContext } from "../commander.js";
3
+ import { type DetectOptions, detectPublicAddresses } from "../lib/tunnel/current-address.js";
3
4
  import { type TunnelProvider, type TunnelState } from "../lib/tunnel/state.js";
4
5
  export declare function tunnelLogDestinations(name: string, provider: TunnelProvider, env?: NodeJS.ProcessEnv, root?: string): {
5
6
  gate: string;
6
7
  provider: string;
7
8
  };
9
+ export interface GateLaunch {
10
+ command: string;
11
+ /** Arguments before the gate's own `--name` and `--port`. */
12
+ arguments: string[];
13
+ }
14
+ export interface GateLaunchInputs {
15
+ /** Absolute path of `lib/tunnel/gate.ts` beside this file, whether or not it exists. */
16
+ gateScript: string;
17
+ gateScriptExists: boolean;
18
+ /** True when this process runs on Bun (so `execPath` is a Bun binary). */
19
+ underBun: boolean;
20
+ execPath: string;
21
+ /** The script this process was started with (`argv[1]`): the host CLI's entry or bundle. */
22
+ entryScript: string | undefined;
23
+ entryScriptExists: boolean;
24
+ bunOnPath: boolean;
25
+ }
26
+ /**
27
+ * How to start the gate worker, which is a `Bun.serve` process (HTTP and
28
+ * WebSocket reverse proxy):
29
+ * 1. From a source or package checkout, `gate.ts` sits beside this module and
30
+ * runs directly.
31
+ * 2. Inside a single-file bundle there is no such file. The CLI then
32
+ * re-executes itself with the hidden `tunnel gate` task, which loads the
33
+ * gate in-process. This uses the running Bun binary, so Bun need not be
34
+ * on PATH.
35
+ * Null means neither works (a Node host with no Bun, or an unknown entry).
36
+ */
37
+ export declare function resolveGateLaunch(i: GateLaunchInputs): GateLaunch | null;
38
+ /**
39
+ * How to start the log wrapper that rotates each process's output. Beside a
40
+ * source or package checkout the default (this package's own wrapper file)
41
+ * works; inside a single-file bundle the CLI re-executes itself with the hidden
42
+ * `tunnel log-worker` task instead.
43
+ */
44
+ export declare function resolveWorkerLaunch(i: GateLaunchInputs): GateLaunch | undefined;
45
+ export declare function downOne(name: string, killed: Set<number>): number;
8
46
  /**
9
47
  * Restart ONE instance's gate in place, deliberately leaving the provider
10
48
  * process running.
@@ -20,5 +58,32 @@ export declare function reloadOne(state: TunnelState): Promise<{
20
58
  ok: boolean;
21
59
  message: string;
22
60
  }>;
61
+ export interface RefreshCurrentOptions {
62
+ emit?: EmitContext;
63
+ /** Reload running Cloudflare gates after an allowlist change, keeping their URLs. */
64
+ reload: boolean;
65
+ /** Speak only when the allowlist changed or no address could be detected. */
66
+ quiet?: boolean;
67
+ /** Lookup timeout and retries; a sync that must not stall offline passes small ones. */
68
+ detectOptions?: DetectOptions;
69
+ /** Replaces the lookup (tests). */
70
+ detect?: typeof detectPublicAddresses;
71
+ }
72
+ export interface RefreshCurrentResult {
73
+ /** False only when a requested reload of a live gate failed. A failed lookup is a warning, not a failure. */
74
+ ok: boolean;
75
+ /** The allowlist gained or lost an entry. */
76
+ changed: boolean;
77
+ /** Entries newly allowed. */
78
+ added: string[];
79
+ removed: string[];
80
+ warnings: string[];
81
+ }
82
+ /**
83
+ * `tunnel allow add --current`: allow this machine's own public addresses, as
84
+ * Cloudflare sees them, and drop the ones allowed by the previous refresh.
85
+ * Exported so a host CLI can run it before `tunnel up` and from its own sync.
86
+ */
87
+ export declare function refreshCurrentAddress(options: RefreshCurrentOptions): Promise<RefreshCurrentResult>;
23
88
  export declare function registerTunnelCommand(program: Command, emitParam: EmitContext, contextParam?: HarneryProgramContext): void;
24
89
  //# sourceMappingURL=tunnel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQ1E,OAAO,EAYL,KAAK,cAAc,EACnB,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AAoDhC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,EACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,SAAgB,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcpC;AAuoBD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAsE7F;AA+MD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA2EN"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/commands/tunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAiB1E,OAAO,EACL,KAAK,aAAa,EAClB,qBAAqB,EAEtB,MAAM,kCAAkC,CAAC;AAQ1C,OAAO,EAYL,KAAK,cAAc,EACnB,KAAK,WAAW,EAGjB,MAAM,wBAAwB,CAAC;AAyDhC,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,EACxB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,IAAI,SAAgB,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcpC;AAmHD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,gBAAgB,GAAG,UAAU,GAAG,IAAI,CAWxE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,gBAAgB,GAAG,UAAU,GAAG,SAAS,CAM/E;AA2iBD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CA0BjE;AA2DD;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAwF7F;AAoQD,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,qFAAqF;IACrF,MAAM,EAAE,OAAO,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wFAAwF;IACxF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACnC,6GAA6G;IAC7G,EAAE,EAAE,OAAO,CAAC;IACZ,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAuC/B;AAaD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,WAAW,EACtB,YAAY,CAAC,EAAE,qBAAqB,GACnC,IAAI,CA+GN"}