harnery 0.43.0 → 0.45.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 (323) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.js +1 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +41 -48
  5. package/dist/commands/browse.d.ts.map +1 -1
  6. package/dist/commands/browse.js +38 -11
  7. package/dist/commands/diagnostics.d.ts.map +1 -1
  8. package/dist/commands/diagnostics.js +10 -0
  9. package/dist/commands/doctor.d.ts +12 -0
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +22 -7
  12. package/dist/commands/files.d.ts +2 -2
  13. package/dist/commands/files.d.ts.map +1 -1
  14. package/dist/commands/files.js +7 -4
  15. package/dist/commands/governor.js +22 -10
  16. package/dist/commands/inbox.d.ts +1 -1
  17. package/dist/commands/inbox.d.ts.map +1 -1
  18. package/dist/commands/inbox.js +55 -2
  19. package/dist/commands/rm.d.ts.map +1 -1
  20. package/dist/commands/rm.js +28 -4
  21. package/dist/commands/servers.d.ts.map +1 -1
  22. package/dist/commands/servers.js +26 -17
  23. package/dist/commands/supervisor.d.ts.map +1 -1
  24. package/dist/commands/supervisor.js +7 -2
  25. package/dist/commands/toc.d.ts +14 -0
  26. package/dist/commands/toc.d.ts.map +1 -1
  27. package/dist/commands/toc.js +7 -4
  28. package/dist/commands/tunnel.d.ts +15 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +453 -153
  31. package/dist/commands/turn-rollback.d.ts +5 -0
  32. package/dist/commands/turn-rollback.d.ts.map +1 -0
  33. package/dist/commands/turn-rollback.js +76 -0
  34. package/dist/commands/workflow.d.ts.map +1 -1
  35. package/dist/commands/workflow.js +68 -30
  36. package/dist/core/agents/coord-client.d.ts.map +1 -1
  37. package/dist/core/agents/coord-client.js +18 -0
  38. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  39. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  40. package/dist/core/agents/live-authority-v3.js +11 -0
  41. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  42. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  43. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  44. package/dist/core/agents/mailbox.d.ts +9 -8
  45. package/dist/core/agents/mailbox.d.ts.map +1 -1
  46. package/dist/core/agents/mailbox.js +24 -17
  47. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  48. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/prompt-context.js +8 -12
  50. package/dist/core/agents/render/session-context.d.ts +2 -8
  51. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  52. package/dist/core/agents/render/session-context.js +7 -30
  53. package/dist/core/agents/rules/stop-hook.js +3 -3
  54. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  55. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  56. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  57. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  58. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  59. package/dist/core/agents/state/pidmap.js +5 -0
  60. package/dist/core/agents/state/proc-start.d.ts +19 -1
  61. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  62. package/dist/core/agents/state/proc-start.js +46 -1
  63. package/dist/core/agents/state/process-handle.d.ts +26 -0
  64. package/dist/core/agents/state/process-handle.d.ts.map +1 -0
  65. package/dist/core/agents/state/process-handle.js +121 -0
  66. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  67. package/dist/core/agents/state/stale-sweep.js +180 -70
  68. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  69. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  70. package/dist/core/agents/state/unchanged-file.js +390 -0
  71. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  72. package/dist/core/artifacts/delivery-card.js +2 -1
  73. package/dist/core/context/index.d.ts +22 -10
  74. package/dist/core/context/index.d.ts.map +1 -1
  75. package/dist/core/context/index.js +381 -119
  76. package/dist/core/context/observation.d.ts +26 -0
  77. package/dist/core/context/observation.d.ts.map +1 -0
  78. package/dist/core/context/observation.js +11 -0
  79. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  80. package/dist/core/diagnostics/bundle.js +25 -14
  81. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  82. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  83. package/dist/core/diagnostics/path-identity.js +12 -0
  84. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  85. package/dist/core/diagnostics/sanitize.js +4 -3
  86. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  87. package/dist/core/events/v3/authority-outbox.js +29 -0
  88. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  89. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  90. package/dist/core/events/v3/generation-custody.js +50 -0
  91. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  92. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  93. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  94. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  95. package/dist/core/events/v3/producers/recorder.js +77 -64
  96. package/dist/core/governor/planning.d.ts.map +1 -1
  97. package/dist/core/governor/planning.js +14 -1
  98. package/dist/core/governor/service-read.d.ts +10 -2
  99. package/dist/core/governor/service-read.d.ts.map +1 -1
  100. package/dist/core/governor/service-read.js +105 -22
  101. package/dist/core/governor/service.d.ts +20 -2
  102. package/dist/core/governor/service.d.ts.map +1 -1
  103. package/dist/core/governor/service.js +72 -47
  104. package/dist/core/hooks/cli.js +33 -5
  105. package/dist/core/inbox/contract.d.ts +2 -1
  106. package/dist/core/inbox/contract.d.ts.map +1 -1
  107. package/dist/core/inbox/index.d.ts +2 -0
  108. package/dist/core/inbox/index.d.ts.map +1 -1
  109. package/dist/core/inbox/index.js +2 -0
  110. package/dist/core/inbox/peer-messages.d.ts +37 -0
  111. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  112. package/dist/core/inbox/peer-messages.js +72 -0
  113. package/dist/core/inbox/service.d.ts +8 -1
  114. package/dist/core/inbox/service.d.ts.map +1 -1
  115. package/dist/core/inbox/service.js +67 -21
  116. package/dist/core/inbox/targets.d.ts +22 -0
  117. package/dist/core/inbox/targets.d.ts.map +1 -0
  118. package/dist/core/inbox/targets.js +108 -0
  119. package/dist/core/inbox/watcher.js +7 -3
  120. package/dist/core/journal/index.d.ts.map +1 -1
  121. package/dist/core/journal/index.js +17 -2
  122. package/dist/core/resources/contract.d.ts +3 -0
  123. package/dist/core/resources/contract.d.ts.map +1 -1
  124. package/dist/core/resources/service-status.d.ts +14 -1
  125. package/dist/core/resources/service-status.d.ts.map +1 -1
  126. package/dist/core/resources/service-status.js +23 -13
  127. package/dist/core/resources/service.d.ts +7 -13
  128. package/dist/core/resources/service.d.ts.map +1 -1
  129. package/dist/core/resources/service.js +34 -91
  130. package/dist/core/resources/status.d.ts +2 -5
  131. package/dist/core/resources/status.d.ts.map +1 -1
  132. package/dist/core/resources/status.js +36 -7
  133. package/dist/core/semantic/service.d.ts.map +1 -1
  134. package/dist/core/semantic/service.js +2 -9
  135. package/dist/core/servers/index.d.ts +34 -22
  136. package/dist/core/servers/index.d.ts.map +1 -1
  137. package/dist/core/servers/index.js +297 -144
  138. package/dist/core/servers/tunnels.d.ts +7 -7
  139. package/dist/core/servers/tunnels.d.ts.map +1 -1
  140. package/dist/core/servers/tunnels.js +47 -10
  141. package/dist/core/supervisor/contract.d.ts +12 -0
  142. package/dist/core/supervisor/contract.d.ts.map +1 -1
  143. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  144. package/dist/core/supervisor/explanations.js +2 -1
  145. package/dist/core/supervisor/findings.d.ts.map +1 -1
  146. package/dist/core/supervisor/findings.js +54 -9
  147. package/dist/core/supervisor/service.d.ts +7 -1
  148. package/dist/core/supervisor/service.d.ts.map +1 -1
  149. package/dist/core/supervisor/service.js +59 -29
  150. package/dist/core/supervisor/services.d.ts.map +1 -1
  151. package/dist/core/supervisor/services.js +22 -1
  152. package/dist/core/supervisor/status.d.ts +17 -2
  153. package/dist/core/supervisor/status.d.ts.map +1 -1
  154. package/dist/core/supervisor/status.js +70 -12
  155. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  156. package/dist/core/supervisor/timeline.js +2 -1
  157. package/dist/core/turn-rollback/content.d.ts +53 -0
  158. package/dist/core/turn-rollback/content.d.ts.map +1 -0
  159. package/dist/core/turn-rollback/content.js +396 -0
  160. package/dist/core/turn-rollback/index.d.ts +72 -0
  161. package/dist/core/turn-rollback/index.d.ts.map +1 -0
  162. package/dist/core/turn-rollback/index.js +443 -0
  163. package/dist/core/work/state.js +7 -7
  164. package/dist/core/workflow/engine.d.ts.map +1 -1
  165. package/dist/core/workflow/engine.js +105 -32
  166. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  167. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  168. package/dist/core/workflow/failure-evidence.js +114 -0
  169. package/dist/core/workflow/index.d.ts +5 -3
  170. package/dist/core/workflow/index.d.ts.map +1 -1
  171. package/dist/core/workflow/index.js +3 -2
  172. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  173. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  174. package/dist/core/workflow/live-session-v3.js +126 -5
  175. package/dist/core/workflow/proof.d.ts +5 -5
  176. package/dist/core/workflow/proof.d.ts.map +1 -1
  177. package/dist/core/workflow/proof.js +115 -20
  178. package/dist/core/workflow/recovery-audit.d.ts +30 -0
  179. package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
  180. package/dist/core/workflow/recovery-audit.js +116 -0
  181. package/dist/core/workflow/run-state.d.ts +20 -1
  182. package/dist/core/workflow/run-state.d.ts.map +1 -1
  183. package/dist/core/workflow/run-state.js +141 -32
  184. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  185. package/dist/core/workflow/spawn-claude.js +19 -0
  186. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  187. package/dist/core/workflow/spawn-codex.js +13 -0
  188. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  189. package/dist/core/workflow/spawn-cursor.js +19 -0
  190. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-opencode.js +14 -0
  192. package/dist/core/workflow/types.d.ts +64 -17
  193. package/dist/core/workflow/types.d.ts.map +1 -1
  194. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  195. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  196. package/dist/core/workflow/workspaces/index.js +0 -1
  197. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  198. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  199. package/dist/core/workflow/workspaces/leases.js +30 -16
  200. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  201. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/local-git.js +328 -8
  203. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  204. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  205. package/dist/core/workflow/workspaces/ownership.js +194 -0
  206. package/dist/core/workflow/workspaces/reclaim.d.ts +40 -0
  207. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  209. package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
  210. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  211. package/dist/core/workflow/workspaces/recovery.js +42 -10
  212. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  213. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/state.js +21 -1
  215. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  216. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  217. package/dist/lib/browser/windows-chrome.js +262 -73
  218. package/dist/lib/docs-links.d.ts.map +1 -1
  219. package/dist/lib/docs-links.js +45 -2
  220. package/dist/lib/guarded-remove.d.ts +7 -0
  221. package/dist/lib/guarded-remove.d.ts.map +1 -1
  222. package/dist/lib/guarded-remove.js +106 -12
  223. package/dist/lib/identities/assume.d.ts.map +1 -1
  224. package/dist/lib/identities/assume.js +4 -0
  225. package/dist/lib/tunnel/state.d.ts +13 -6
  226. package/dist/lib/tunnel/state.d.ts.map +1 -1
  227. package/dist/lib/tunnel/state.js +58 -12
  228. package/dist/lib/zip/index.d.ts +41 -0
  229. package/dist/lib/zip/index.d.ts.map +1 -0
  230. package/dist/lib/zip/index.js +398 -0
  231. package/package.json +12 -1
  232. package/src/commander.ts +10 -0
  233. package/src/commands/agents.ts +54 -63
  234. package/src/commands/browse.ts +50 -14
  235. package/src/commands/diagnostics.ts +10 -0
  236. package/src/commands/doctor.ts +26 -7
  237. package/src/commands/files.ts +13 -5
  238. package/src/commands/governor.ts +23 -10
  239. package/src/commands/inbox.ts +52 -2
  240. package/src/commands/rm.ts +43 -4
  241. package/src/commands/servers.ts +27 -26
  242. package/src/commands/supervisor.ts +7 -2
  243. package/src/commands/toc.ts +14 -6
  244. package/src/commands/tunnel.ts +533 -166
  245. package/src/commands/turn-rollback.ts +111 -0
  246. package/src/commands/workflow.ts +89 -30
  247. package/src/core/agents/coord-client.ts +20 -0
  248. package/src/core/agents/live-authority-v3.ts +17 -0
  249. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  250. package/src/core/agents/mailbox.ts +26 -17
  251. package/src/core/agents/render/prompt-context.ts +10 -12
  252. package/src/core/agents/render/session-context.ts +9 -32
  253. package/src/core/agents/rules/stop-hook.ts +3 -3
  254. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  255. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  256. package/src/core/agents/state/pidmap.ts +13 -0
  257. package/src/core/agents/state/proc-start.ts +62 -2
  258. package/src/core/agents/state/process-handle.ts +165 -0
  259. package/src/core/agents/state/stale-sweep.ts +247 -82
  260. package/src/core/agents/state/unchanged-file.ts +456 -0
  261. package/src/core/artifacts/delivery-card.ts +7 -1
  262. package/src/core/context/index.ts +446 -136
  263. package/src/core/context/observation.ts +29 -0
  264. package/src/core/diagnostics/bundle.ts +23 -18
  265. package/src/core/diagnostics/path-identity.ts +15 -0
  266. package/src/core/diagnostics/sanitize.ts +4 -3
  267. package/src/core/events/v3/authority-outbox.ts +56 -0
  268. package/src/core/events/v3/generation-custody.ts +59 -0
  269. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  270. package/src/core/events/v3/producers/recorder.ts +94 -68
  271. package/src/core/governor/planning.ts +20 -8
  272. package/src/core/governor/service-read.ts +128 -20
  273. package/src/core/governor/service.ts +119 -42
  274. package/src/core/hooks/cli.ts +33 -5
  275. package/src/core/inbox/contract.ts +2 -1
  276. package/src/core/inbox/index.ts +2 -0
  277. package/src/core/inbox/peer-messages.ts +94 -0
  278. package/src/core/inbox/service.ts +88 -22
  279. package/src/core/inbox/targets.ts +148 -0
  280. package/src/core/inbox/watcher.ts +8 -9
  281. package/src/core/journal/index.ts +19 -2
  282. package/src/core/resources/contract.ts +4 -0
  283. package/src/core/resources/service-status.ts +35 -12
  284. package/src/core/resources/service.ts +57 -95
  285. package/src/core/resources/status.ts +57 -8
  286. package/src/core/semantic/service.ts +2 -8
  287. package/src/core/servers/index.ts +376 -141
  288. package/src/core/servers/tunnels.ts +57 -13
  289. package/src/core/supervisor/contract.ts +13 -0
  290. package/src/core/supervisor/explanations.ts +4 -1
  291. package/src/core/supervisor/findings.ts +68 -10
  292. package/src/core/supervisor/service.ts +87 -26
  293. package/src/core/supervisor/services.ts +26 -8
  294. package/src/core/supervisor/status.ts +89 -12
  295. package/src/core/supervisor/timeline.ts +2 -1
  296. package/src/core/turn-rollback/content.ts +499 -0
  297. package/src/core/turn-rollback/index.ts +580 -0
  298. package/src/core/work/state.ts +7 -7
  299. package/src/core/workflow/engine.ts +131 -49
  300. package/src/core/workflow/failure-evidence.ts +141 -0
  301. package/src/core/workflow/index.ts +12 -2
  302. package/src/core/workflow/live-session-v3.ts +146 -5
  303. package/src/core/workflow/proof.ts +134 -26
  304. package/src/core/workflow/recovery-audit.ts +181 -0
  305. package/src/core/workflow/run-state.ts +165 -34
  306. package/src/core/workflow/spawn-claude.ts +18 -0
  307. package/src/core/workflow/spawn-codex.ts +13 -0
  308. package/src/core/workflow/spawn-cursor.ts +18 -0
  309. package/src/core/workflow/spawn-opencode.ts +14 -0
  310. package/src/core/workflow/types.ts +51 -17
  311. package/src/core/workflow/workspaces/index.ts +0 -1
  312. package/src/core/workflow/workspaces/leases.ts +48 -17
  313. package/src/core/workflow/workspaces/local-git.ts +388 -7
  314. package/src/core/workflow/workspaces/ownership.ts +232 -0
  315. package/src/core/workflow/workspaces/reclaim.ts +62 -0
  316. package/src/core/workflow/workspaces/recovery.ts +45 -10
  317. package/src/core/workflow/workspaces/state.ts +20 -0
  318. package/src/lib/browser/windows-chrome.ts +346 -86
  319. package/src/lib/docs-links.ts +49 -2
  320. package/src/lib/guarded-remove.ts +132 -14
  321. package/src/lib/identities/assume.ts +4 -0
  322. package/src/lib/tunnel/state.ts +70 -15
  323. package/src/lib/zip/index.ts +483 -0
@@ -1,20 +1,43 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { lstatSync, readdirSync, readlinkSync, realpathSync, rmSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { basename, dirname, isAbsolute, parse, relative, resolve, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
5
5
  const protectedNames = new Set([".git", ".harnery", ".credentials", ".ssh", ".gnupg"]);
6
6
  const MAX_ENTRIES = 10_000;
7
+ const MAX_OWNED_ARTIFACT_ENTRIES = 100_000;
7
8
  /** Permanent removal is deliberately limited to inspected, untracked local outputs. */
8
9
  export function guardedRemove(paths, options, checkClaims = () => { }) {
9
10
  const cwd = realpathSync(process.cwd());
10
11
  const root = checkedPath(options.root, new Set(), true);
11
- const allowed = allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? []);
12
+ if (options.cleanClone) {
13
+ if (!options.recursive)
14
+ throw new Error("Clean clone removal requires --recursive");
15
+ if (options.allowProtected?.length)
16
+ throw new Error("Clean clone removal cannot be combined with --allow-protected");
17
+ if (!options.assertOwnedArtifact)
18
+ throw new Error("Clean clone removal requires one current caller-owned managed artifact");
19
+ options.assertOwnedArtifact(root);
20
+ }
21
+ const entryLimit = options.entryLimit ?? MAX_ENTRIES;
22
+ if (!Number.isSafeInteger(entryLimit) ||
23
+ entryLimit < 1 ||
24
+ entryLimit > MAX_OWNED_ARTIFACT_ENTRIES)
25
+ throw new Error(`The entry limit must be an integer from 1 to ${MAX_OWNED_ARTIFACT_ENTRIES}`);
26
+ const assertEntryLimitAuthority = () => {
27
+ if (entryLimit <= MAX_ENTRIES)
28
+ return;
29
+ if (!options.assertOwnedArtifact)
30
+ throw new Error("An entry limit above 10000 requires one current caller-owned managed artifact");
31
+ options.assertOwnedArtifact(root);
32
+ };
33
+ const allowed = allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? [], options.assertOwnedArtifact);
12
34
  const rootStat = lstatSync(root);
13
35
  const rootFingerprint = fingerprint(rootStat);
14
36
  if (!rootStat.isDirectory())
15
37
  throw new Error("--root must be a directory");
16
38
  if (root === parse(root).root || root === realpathSync(homedir()))
17
39
  throw new Error("--root cannot be a filesystem root or home directory");
40
+ assertEntryLimitAuthority();
18
41
  if (paths.length === 0 || paths.length > 100)
19
42
  throw new Error("Supply 1-100 explicit targets");
20
43
  const targets = paths.map((path) => checkedPath(path, allowed));
@@ -22,6 +45,7 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
22
45
  const removedProtected = new Set();
23
46
  const links = [];
24
47
  const gitDirs = new Set();
48
+ const cleanClones = new Set();
25
49
  let bytes = 0;
26
50
  const devices = new Set();
27
51
  for (const target of targets) {
@@ -64,6 +88,10 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
64
88
  if (stat.isDirectory() && !options.recursive)
65
89
  throw new Error(`Directory removal requires --recursive: ${target}`);
66
90
  const device = stat.dev;
91
+ if (options.cleanClone) {
92
+ assertCleanClone(target);
93
+ cleanClones.add(target);
94
+ }
67
95
  devices.add(device);
68
96
  const pending = [target];
69
97
  while (pending.length) {
@@ -72,6 +100,12 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
72
100
  if (basename(path) === ARTIFACT_RECORD && insideArtifactWorkspace(path, null))
73
101
  throw new Error(`An artifact's record is managed by the artifact commands: ${path}`);
74
102
  if (protectedNames.has(basename(path).toLowerCase())) {
103
+ if (options.cleanClone && basename(path) === ".git") {
104
+ const clone = dirname(path);
105
+ assertCleanClone(clone);
106
+ cleanClones.add(clone);
107
+ allowed.add(path);
108
+ }
75
109
  if (!allowed.has(path))
76
110
  throw new Error(protectedMessage(path));
77
111
  removedProtected.add(path);
@@ -82,16 +116,16 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
82
116
  // were already refused if they were links, in checkedPath.)
83
117
  if (path === target)
84
118
  throw new Error(`Path contains a symlink: ${path}`);
85
- if (snapshots.size >= MAX_ENTRIES)
86
- throw new Error(`Removal exceeds the ${MAX_ENTRIES}-entry limit; use the owning cleanup command`);
119
+ if (snapshots.size >= entryLimit)
120
+ throw new Error(`Removal exceeds the ${entryLimit}-entry limit; use the owning cleanup command`);
87
121
  snapshots.set(path, fingerprint(info));
88
122
  links.push({ path, target: readlinkSync(path) });
89
123
  continue;
90
124
  }
91
125
  if (info.dev !== device || (!info.isDirectory() && !info.isFile()))
92
126
  throw new Error(`Mount boundary or special file cannot be removed: ${path}`);
93
- if (snapshots.size >= MAX_ENTRIES)
94
- throw new Error(`Removal exceeds the ${MAX_ENTRIES}-entry limit; use the owning cleanup command`);
127
+ if (snapshots.size >= entryLimit)
128
+ throw new Error(`Removal exceeds the ${entryLimit}-entry limit; use the owning cleanup command`);
95
129
  snapshots.set(path, fingerprint(info));
96
130
  if (info.isFile())
97
131
  bytes += info.size;
@@ -126,6 +160,7 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
126
160
  root,
127
161
  targets,
128
162
  entries: snapshots.size,
163
+ entry_limit: entryLimit,
129
164
  bytes,
130
165
  protected: [...removedProtected].sort(),
131
166
  links,
@@ -137,12 +172,19 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
137
172
  if (checkedPath(options.root, new Set(), true) !== root ||
138
173
  fingerprint(lstatSync(root)) !== rootFingerprint)
139
174
  throw new Error("Allowed root changed during inspection");
175
+ assertEntryLimitAuthority();
176
+ if (options.cleanClone)
177
+ options.assertOwnedArtifact(root);
178
+ // Ownership may have been released or changed while the contents were inspected.
179
+ allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? [], options.assertOwnedArtifact);
140
180
  for (const target of targets) {
141
181
  checkedPath(target, allowed);
142
182
  assertUntracked(target);
143
183
  }
144
184
  for (const dir of gitDirs)
145
185
  assertNoUnpushedHistory(dir);
186
+ for (const clone of cleanClones)
187
+ assertCleanClone(clone);
146
188
  checkClaims(targets);
147
189
  for (const [path, previous] of snapshots) {
148
190
  if (fingerprint(lstatSync(path)) !== previous)
@@ -152,6 +194,44 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
152
194
  rmSync(target, { recursive: Boolean(options.recursive), force: false });
153
195
  return { ...report, applied: true };
154
196
  }
197
+ /** A disposable clone must not be the only copy of work or a registered worktree. */
198
+ function assertCleanClone(clone) {
199
+ const metadata = join(clone, ".git");
200
+ const stat = lstatSync(metadata);
201
+ if (!stat.isDirectory() || stat.isSymbolicLink())
202
+ throw new Error(`Clean clone removal requires a standalone .git directory: ${clone}`);
203
+ const git = (args) => {
204
+ const result = spawnSync("git", ["-c", "core.fsmonitor=false", "-C", clone, ...args], {
205
+ encoding: "utf8",
206
+ timeout: 10_000,
207
+ maxBuffer: 4 * 1024 * 1024,
208
+ env: {
209
+ ...process.env,
210
+ LC_ALL: "C",
211
+ GIT_DIR: undefined,
212
+ GIT_COMMON_DIR: undefined,
213
+ GIT_WORK_TREE: undefined,
214
+ GIT_INDEX_FILE: undefined,
215
+ GIT_OPTIONAL_LOCKS: "0",
216
+ },
217
+ });
218
+ if (result.error || result.status !== 0)
219
+ throw new Error(`Cannot verify clean clone ${clone}: git ${args[0]} failed`);
220
+ return result.stdout;
221
+ };
222
+ if (realpathSync(git(["rev-parse", "--show-toplevel"]).trim()) !== clone ||
223
+ realpathSync(resolve(clone, git(["rev-parse", "--git-common-dir"]).trim())) !== metadata)
224
+ throw new Error(`Clean clone removal refuses shared repository metadata: ${clone}`);
225
+ const worktrees = git(["worktree", "list", "--porcelain", "-z"])
226
+ .split("\0")
227
+ .filter((entry) => entry.startsWith("worktree "));
228
+ if (worktrees.length !== 1 || worktrees[0] !== `worktree ${clone}`)
229
+ throw new Error(`Clean clone removal refuses registered or linked worktrees: ${clone}`);
230
+ if (git(["status", "--porcelain=v1", "--untracked-files=all"]).trim())
231
+ throw new Error(`Clean clone has uncommitted or untracked work: ${clone}`);
232
+ if (git(["rev-list", "--max-count=1", "HEAD", "--all", "--reflog", "--not", "--remotes"]).trim())
233
+ throw new Error(`Clean clone has HEAD, ref or reflog commits not on any remote: ${clone}`);
234
+ }
155
235
  /** The metadata file the artifact commands keep in each artifact workspace. */
156
236
  const ARTIFACT_RECORD = ".harnery-artifact.json";
157
237
  /**
@@ -188,9 +268,9 @@ function insideAllowed(path, allowed) {
188
268
  * protected path strictly inside the root, reached without symlinks, and
189
269
  * never the Git metadata or Harnery state of a live workspace root.
190
270
  */
191
- function allowedProtected(paths, root, liveRoots) {
271
+ function allowedProtected(paths, root, liveRoots, assertOwnedArtifact) {
192
272
  const allowed = new Set();
193
- const live = liveRoots.map((dir) => resolve(dir));
273
+ const live = liveRoots.map((dir) => realpathSync(dir));
194
274
  for (const input of paths) {
195
275
  if (!input?.trim() || /[\0\r\n*?[\]$]/.test(input))
196
276
  throw new Error("Use a nonempty explicit --allow-protected path without wildcards");
@@ -213,11 +293,25 @@ function allowedProtected(paths, root, liveRoots) {
213
293
  if (!named && !(info.isDirectory() && isRepositoryMetadata(path)))
214
294
  throw new Error(`--allow-protected must name a protected path itself: ${path}`);
215
295
  for (const dir of live) {
216
- const own = [".git", ".harnery"].map((name) => resolve(dir, name));
217
- if (path === dir ||
218
- strictlyInside(path, dir) ||
219
- own.some((state) => path === state || strictlyInside(state, path)))
296
+ if (path === dir || strictlyInside(path, dir))
220
297
  throw new Error(`The live workspace's own state cannot be allowed: ${path}`);
298
+ for (const name of [".git", ".harnery"]) {
299
+ const state = resolve(dir, name);
300
+ if (path !== state && !strictlyInside(state, path))
301
+ continue;
302
+ // Only an explicitly named copied state directory inside one owned
303
+ // artifact may cross the outer live-state ancestor. Other live roots
304
+ // (including a checkout inside that artifact) still veto the path.
305
+ if (name === ".harnery" &&
306
+ basename(path) === ".harnery" &&
307
+ info.isDirectory() &&
308
+ dirname(root) === resolve(state, "artifacts") &&
309
+ assertOwnedArtifact) {
310
+ assertOwnedArtifact(root);
311
+ continue;
312
+ }
313
+ throw new Error(`The live workspace's own state cannot be allowed: ${path}`);
314
+ }
221
315
  }
222
316
  allowed.add(realpathSync(path));
223
317
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assume.d.ts","sourceRoot":"","sources":["../../../src/lib/identities/assume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,mBAAmB,CAAC;AAExB,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAC7C,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAOD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAa,EAClB,mBAAmB,GAAE,WAAW,CAAC,MAAM,CAAa,GACnD,gBAAgB,GAAG,IAAI,CA6BzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAyCT;AAuED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,oBAAoB,CAyHtB"}
1
+ {"version":3,"file":"assume.d.ts","sourceRoot":"","sources":["../../../src/lib/identities/assume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,mBAAmB,CAAC;AAExB,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAC7C,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAOD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAa,EAClB,mBAAmB,GAAE,WAAW,CAAC,MAAM,CAAa,GACnD,gBAAgB,GAAG,IAAI,CA6BzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CA6CT;AAuED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,oBAAoB,CAyHtB"}
@@ -85,6 +85,7 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
85
85
  let sessionId = conflict.instance_id;
86
86
  let ageSecs;
87
87
  const liveRow = readLiveCoordinationRow(coordRoot, conflict.instance_id);
88
+ let expectedGenerationId = liveRow?.v3_generation_id ?? null;
88
89
  if (liveRow) {
89
90
  adapter = adapterOf(liveRow.platform);
90
91
  sessionId = liveRow.session_id || conflict.instance_id;
@@ -99,8 +100,10 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
99
100
  // It is required to join the exact producer state when recording the sweep.
100
101
  adapter = adapterOf(hb.platform);
101
102
  sessionId = hb.session_id || conflict.instance_id;
103
+ expectedGenerationId = hb.v3_generation_id ?? null;
102
104
  }
103
105
  catch {
106
+ expectedGenerationId = null;
104
107
  /* content unreadable — still reclaim the file */
105
108
  }
106
109
  try {
@@ -116,6 +119,7 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
116
119
  owner: conflict.instance_id,
117
120
  nativeSessionId: sessionId,
118
121
  adapter,
122
+ expectedGenerationId,
119
123
  observation: "stale_heartbeat",
120
124
  ageMs: Math.max(0, (ageSecs ?? 0) * 1_000),
121
125
  });
@@ -32,10 +32,12 @@ export interface TunnelState {
32
32
  provider: TunnelProvider;
33
33
  url: string;
34
34
  gate_pid: number;
35
+ gate_start_token?: string;
36
+ cloudflared_start_token?: string;
37
+ registration_id?: string;
38
+ provider_state?: "pending" | "ready";
35
39
  /** Present for Cloudflare quick tunnels; absent for Tailscale Serve/Funnel. */
36
40
  cloudflared_pid?: number;
37
- /** Optional provider-side process when a provider owns one. */
38
- provider_pid?: number;
39
41
  started_at: string;
40
42
  target: string;
41
43
  vhost: string;
@@ -57,9 +59,14 @@ export declare function clearState(name?: string, root?: string): void;
57
59
  */
58
60
  export declare function listStates(root?: string): TunnelState[];
59
61
  export declare function isProcessAlive(pid: number): boolean;
60
- export type ProcessAliveCheck = (pid: number) => boolean;
61
- /** True when both the local gate and its provider-side process are live. */
62
- export declare function isTunnelStateLive(state: TunnelState, processAlive?: ProcessAliveCheck): boolean;
62
+ export type TunnelReadiness = "ready" | "dead" | "unknown";
63
+ export type TunnelProcessObservation = "same" | "absent" | "different" | "unknown";
64
+ export interface TunnelReadinessVerifier {
65
+ observeProcess(pid: number, originalToken: string): TunnelProcessObservation;
66
+ withRegistration(state: TunnelState, inspect: () => TunnelReadiness): TunnelReadiness;
67
+ }
68
+ /** Classify saved original witnesses under the verifier's registration custody. */
69
+ export declare function tunnelReadiness(state: TunnelState, verifier: TunnelReadinessVerifier): TunnelReadiness;
63
70
  export declare function tunnelTargetPort(state: TunnelState): number | null;
64
71
  /**
65
72
  * Newest live tunnel that serves `vhost` on the requested local web port.
@@ -72,7 +79,7 @@ export declare function tunnelTargetPort(state: TunnelState): number | null;
72
79
  * link 400s. Matching the vhost too keeps the returned tunnel the one that
73
80
  * actually serves the caller's origin.
74
81
  */
75
- export declare function findLiveTunnelForOrigin(port: number, vhost: string, root?: string, processAlive?: ProcessAliveCheck): TunnelState | null;
82
+ export declare function findLiveTunnelForOrigin(port: number, vhost: string, root: string, verifier: TunnelReadinessVerifier): TunnelState | null;
76
83
  /** True when the tunnel's path scope forwards every one of `paths`. */
77
84
  export declare function tunnelServesPaths(state: TunnelState, paths: readonly string[]): boolean;
78
85
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  // Tunnel config + state persistence + provider helpers. Commands default to
2
2
  // <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
3
3
  import { spawnSync } from "node:child_process";
4
- import { accessSync, chmodSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
4
+ import { randomUUID } from "node:crypto";
5
+ import { accessSync, chmodSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
5
6
  import { homedir } from "node:os";
6
7
  import { delimiter, join, resolve } from "node:path";
7
8
  import { isPathAllowed } from "./path-scope.js";
@@ -104,7 +105,17 @@ export function readState(name = DEFAULT_INSTANCE, root = process.cwd()) {
104
105
  }
105
106
  }
106
107
  export function writeState(state, root = process.cwd()) {
107
- writeFileSync(cachePath(stateFile(state.name), root), JSON.stringify(state, null, 2));
108
+ const path = cachePath(stateFile(state.name), root);
109
+ const temporary = `${path}.${randomUUID()}.tmp`;
110
+ try {
111
+ writeFileSync(temporary, JSON.stringify(state, null, 2), { flag: "wx", mode: 0o600 });
112
+ renameSync(temporary, path);
113
+ }
114
+ catch (error) {
115
+ if (existsSync(temporary))
116
+ unlinkSync(temporary);
117
+ throw error;
118
+ }
108
119
  }
109
120
  export function clearState(name = DEFAULT_INSTANCE, root = process.cwd()) {
110
121
  const p = cachePath(stateFile(name), root);
@@ -139,14 +150,49 @@ export function isProcessAlive(pid) {
139
150
  return false;
140
151
  }
141
152
  }
142
- /** True when both the local gate and its provider-side process are live. */
143
- export function isTunnelStateLive(state, processAlive = isProcessAlive) {
144
- if (!processAlive(state.gate_pid))
145
- return false;
146
- if (state.provider === "tailscale")
147
- return true;
148
- const providerPid = state.cloudflared_pid ?? state.provider_pid;
149
- return typeof providerPid === "number" && processAlive(providerPid);
153
+ /** Classify saved original witnesses under the verifier's registration custody. */
154
+ export function tunnelReadiness(state, verifier) {
155
+ if (!state ||
156
+ typeof state !== "object" ||
157
+ Object.hasOwn(state, "provider_pid") ||
158
+ Object.hasOwn(state, "provider_start_token") ||
159
+ typeof state.registration_id !== "string" ||
160
+ !state.registration_id ||
161
+ !Number.isSafeInteger(state.gate_pid) ||
162
+ state.gate_pid < 1 ||
163
+ typeof state.gate_start_token !== "string" ||
164
+ !state.gate_start_token)
165
+ return "unknown";
166
+ const targets = [{ pid: state.gate_pid, token: state.gate_start_token }];
167
+ if (state.provider === "tailscale") {
168
+ if (state.provider_state !== "ready" ||
169
+ state.cloudflared_pid !== undefined ||
170
+ state.cloudflared_start_token !== undefined)
171
+ return "unknown";
172
+ }
173
+ else if (state.provider === "cloudflare") {
174
+ if (!Number.isSafeInteger(state.cloudflared_pid) ||
175
+ state.cloudflared_pid < 1 ||
176
+ typeof state.cloudflared_start_token !== "string" ||
177
+ !state.cloudflared_start_token)
178
+ return "unknown";
179
+ targets.push({ pid: state.cloudflared_pid, token: state.cloudflared_start_token });
180
+ }
181
+ else
182
+ return "unknown";
183
+ try {
184
+ return verifier.withRegistration(state, () => {
185
+ const observed = targets.map(({ pid, token }) => verifier.observeProcess(pid, token));
186
+ if (observed.every((value) => value === "absent"))
187
+ return "dead";
188
+ if (observed.every((value) => value === "same"))
189
+ return "ready";
190
+ return "unknown";
191
+ });
192
+ }
193
+ catch {
194
+ return "unknown";
195
+ }
150
196
  }
151
197
  export function tunnelTargetPort(state) {
152
198
  const value = /^[a-z][a-z\d+.-]*:\/\//i.test(state.target)
@@ -173,10 +219,10 @@ export function tunnelTargetPort(state) {
173
219
  * link 400s. Matching the vhost too keeps the returned tunnel the one that
174
220
  * actually serves the caller's origin.
175
221
  */
176
- export function findLiveTunnelForOrigin(port, vhost, root = process.cwd(), processAlive = isProcessAlive) {
222
+ export function findLiveTunnelForOrigin(port, vhost, root, verifier) {
177
223
  return (listStates(root).find((state) => tunnelTargetPort(state) === port &&
178
224
  state.vhost === vhost &&
179
- isTunnelStateLive(state, processAlive)) ?? null);
225
+ tunnelReadiness(state, verifier) === "ready") ?? null);
180
226
  }
181
227
  /** True when the tunnel's path scope forwards every one of `paths`. */
182
228
  export function tunnelServesPaths(state, paths) {
@@ -0,0 +1,41 @@
1
+ export interface ZipLimits {
2
+ archiveBytes: number;
3
+ entryBytes: number;
4
+ totalBytes: number;
5
+ entries: number;
6
+ }
7
+ export declare const DEFAULT_ZIP_LIMITS: Readonly<ZipLimits>;
8
+ export interface ZipEntryInfo {
9
+ name: string;
10
+ size: number;
11
+ compressedSize: number;
12
+ directory: boolean;
13
+ crc32: number;
14
+ }
15
+ export type ZipSource = string | Uint8Array;
16
+ export interface ReadOptions {
17
+ limits?: Partial<ZipLimits>;
18
+ }
19
+ export declare function listZip(source: ZipSource, options?: ReadOptions): ZipEntryInfo[];
20
+ export declare function readZipEntries(source: ZipSource, keep?: (name: string) => boolean, options?: ReadOptions): Map<string, Uint8Array>;
21
+ export declare function readZipEntry(source: ZipSource, name: string, options?: ReadOptions): Uint8Array | undefined;
22
+ export declare function readZipText(source: ZipSource, name: string, options?: ReadOptions): string | undefined;
23
+ /** Portable names exclude traversal, device names, streams and ambiguous aliases. */
24
+ export declare function safeEntryPath(root: string, name: string): string;
25
+ export interface ExtractOptions extends ReadOptions {
26
+ overwrite?: boolean;
27
+ filter?: (name: string) => boolean;
28
+ }
29
+ /** Preflight all selected names, collisions and existing filesystem paths before writing. */
30
+ export declare function extractZip(source: ZipSource, dest: string, options?: ExtractOptions): string[];
31
+ export interface CreateOptions extends ReadOptions {
32
+ base?: string;
33
+ level?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
34
+ overwrite?: boolean;
35
+ }
36
+ export declare function buildZip(inputs: readonly string[], options?: CreateOptions): {
37
+ bytes: Uint8Array;
38
+ entries: string[];
39
+ };
40
+ export declare function createZip(archive: string, inputs: readonly string[], options?: CreateOptions): string[];
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/zip/index.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AACD,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,SAAS,CAKjD,CAAC;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAC5C,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC7B;AA+JD,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,WAAgB,GAAG,YAAY,EAAE,CAKpF;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,EAC5C,OAAO,GAAE,WAAgB,GACxB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAoBzB;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,UAAU,GAAG,SAAS,CAExB;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,GAAG,SAAS,CAGpB;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBhE;AAsFD,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,MAAM,EAAE,CAsCV;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,QAAQ,CACtB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,GAAE,aAAkB,GAC1B;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CA8C1C;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,GAAE,aAAkB,GAC1B,MAAM,EAAE,CAQV"}