harnery 0.42.0 → 0.44.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 (343) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.d.ts +16 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +18 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +95 -52
  7. package/dist/commands/browse.d.ts.map +1 -1
  8. package/dist/commands/browse.js +38 -11
  9. package/dist/commands/diagnostics.d.ts.map +1 -1
  10. package/dist/commands/diagnostics.js +10 -0
  11. package/dist/commands/doctor.d.ts +12 -0
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +22 -7
  14. package/dist/commands/governor.js +22 -10
  15. package/dist/commands/inbox.d.ts +1 -1
  16. package/dist/commands/inbox.d.ts.map +1 -1
  17. package/dist/commands/inbox.js +55 -2
  18. package/dist/commands/rm.d.ts.map +1 -1
  19. package/dist/commands/rm.js +28 -4
  20. package/dist/commands/servers.d.ts.map +1 -1
  21. package/dist/commands/servers.js +16 -1
  22. package/dist/commands/supervisor.d.ts.map +1 -1
  23. package/dist/commands/supervisor.js +7 -2
  24. package/dist/commands/toc.d.ts +14 -0
  25. package/dist/commands/toc.d.ts.map +1 -1
  26. package/dist/commands/toc.js +7 -4
  27. package/dist/commands/tunnel.d.ts +65 -0
  28. package/dist/commands/tunnel.d.ts.map +1 -1
  29. package/dist/commands/tunnel.js +290 -101
  30. package/dist/commands/web-fetch.d.ts +3 -2
  31. package/dist/commands/web-fetch.d.ts.map +1 -1
  32. package/dist/commands/web-fetch.js +25 -19
  33. package/dist/commands/web.d.ts +7 -1
  34. package/dist/commands/web.d.ts.map +1 -1
  35. package/dist/commands/web.js +9 -3
  36. package/dist/commands/workflow.js +24 -30
  37. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  38. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  39. package/dist/core/agents/live-authority-v3.js +11 -0
  40. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  41. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  42. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  43. package/dist/core/agents/mailbox.d.ts +9 -8
  44. package/dist/core/agents/mailbox.d.ts.map +1 -1
  45. package/dist/core/agents/mailbox.js +24 -17
  46. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  47. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  48. package/dist/core/agents/render/prompt-context.js +19 -17
  49. package/dist/core/agents/render/session-context.d.ts +2 -8
  50. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  51. package/dist/core/agents/render/session-context.js +64 -43
  52. package/dist/core/agents/rules/stop-hook.js +3 -3
  53. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  54. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  55. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  56. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  57. package/dist/core/agents/state/names.d.ts +43 -3
  58. package/dist/core/agents/state/names.d.ts.map +1 -1
  59. package/dist/core/agents/state/names.js +87 -3
  60. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  61. package/dist/core/agents/state/pidmap.js +5 -0
  62. package/dist/core/agents/state/proc-start.d.ts +19 -0
  63. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  64. package/dist/core/agents/state/proc-start.js +41 -0
  65. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  66. package/dist/core/agents/state/stale-sweep.js +180 -70
  67. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  68. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  69. package/dist/core/agents/state/unchanged-file.js +390 -0
  70. package/dist/core/context/index.d.ts +22 -10
  71. package/dist/core/context/index.d.ts.map +1 -1
  72. package/dist/core/context/index.js +381 -119
  73. package/dist/core/context/observation.d.ts +26 -0
  74. package/dist/core/context/observation.d.ts.map +1 -0
  75. package/dist/core/context/observation.js +11 -0
  76. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  77. package/dist/core/diagnostics/bundle.js +25 -14
  78. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  79. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  80. package/dist/core/diagnostics/path-identity.js +12 -0
  81. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  82. package/dist/core/diagnostics/sanitize.js +4 -3
  83. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  84. package/dist/core/events/v3/authority-outbox.js +29 -0
  85. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  86. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  87. package/dist/core/events/v3/generation-custody.js +50 -0
  88. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  89. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  90. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  91. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  92. package/dist/core/events/v3/producers/recorder.js +77 -64
  93. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  94. package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
  95. package/dist/core/governor/planning.d.ts.map +1 -1
  96. package/dist/core/governor/planning.js +14 -1
  97. package/dist/core/governor/service-read.d.ts +10 -2
  98. package/dist/core/governor/service-read.d.ts.map +1 -1
  99. package/dist/core/governor/service-read.js +105 -22
  100. package/dist/core/governor/service.d.ts +20 -2
  101. package/dist/core/governor/service.d.ts.map +1 -1
  102. package/dist/core/governor/service.js +72 -47
  103. package/dist/core/hooks/cli.js +120 -23
  104. package/dist/core/hooks/fork-lineage.d.ts +40 -0
  105. package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
  106. package/dist/core/hooks/fork-lineage.js +62 -0
  107. package/dist/core/hooks/resolve/transcript.d.ts +16 -0
  108. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  109. package/dist/core/hooks/resolve/transcript.js +84 -0
  110. package/dist/core/hooks/shell-rm.d.ts +2 -0
  111. package/dist/core/hooks/shell-rm.d.ts.map +1 -0
  112. package/dist/core/hooks/shell-rm.js +105 -0
  113. package/dist/core/hooks/shell-tokens.d.ts +12 -0
  114. package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
  115. package/dist/core/hooks/shell-tokens.js +97 -0
  116. package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
  117. package/dist/core/hooks/shell-waiter.js +3 -93
  118. package/dist/core/inbox/contract.d.ts +2 -1
  119. package/dist/core/inbox/contract.d.ts.map +1 -1
  120. package/dist/core/inbox/index.d.ts +2 -0
  121. package/dist/core/inbox/index.d.ts.map +1 -1
  122. package/dist/core/inbox/index.js +2 -0
  123. package/dist/core/inbox/peer-messages.d.ts +37 -0
  124. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  125. package/dist/core/inbox/peer-messages.js +72 -0
  126. package/dist/core/inbox/service.d.ts +8 -1
  127. package/dist/core/inbox/service.d.ts.map +1 -1
  128. package/dist/core/inbox/service.js +67 -21
  129. package/dist/core/inbox/targets.d.ts +22 -0
  130. package/dist/core/inbox/targets.d.ts.map +1 -0
  131. package/dist/core/inbox/targets.js +108 -0
  132. package/dist/core/inbox/watcher.js +7 -3
  133. package/dist/core/journal/index.d.ts.map +1 -1
  134. package/dist/core/journal/index.js +17 -2
  135. package/dist/core/resources/contract.d.ts +3 -0
  136. package/dist/core/resources/contract.d.ts.map +1 -1
  137. package/dist/core/resources/service-status.d.ts +14 -1
  138. package/dist/core/resources/service-status.d.ts.map +1 -1
  139. package/dist/core/resources/service-status.js +23 -13
  140. package/dist/core/resources/service.d.ts +9 -1
  141. package/dist/core/resources/service.d.ts.map +1 -1
  142. package/dist/core/resources/service.js +47 -27
  143. package/dist/core/resources/status.d.ts +2 -5
  144. package/dist/core/resources/status.d.ts.map +1 -1
  145. package/dist/core/resources/status.js +36 -7
  146. package/dist/core/semantic/contract.d.ts +5 -5
  147. package/dist/core/servers/index.d.ts +24 -13
  148. package/dist/core/servers/index.d.ts.map +1 -1
  149. package/dist/core/servers/index.js +132 -92
  150. package/dist/core/servers/tunnels.d.ts +3 -5
  151. package/dist/core/servers/tunnels.d.ts.map +1 -1
  152. package/dist/core/servers/tunnels.js +3 -9
  153. package/dist/core/storage/process-log.d.ts +12 -0
  154. package/dist/core/storage/process-log.d.ts.map +1 -1
  155. package/dist/core/storage/process-log.js +10 -2
  156. package/dist/core/supervisor/contract.d.ts +12 -0
  157. package/dist/core/supervisor/contract.d.ts.map +1 -1
  158. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  159. package/dist/core/supervisor/explanations.js +2 -1
  160. package/dist/core/supervisor/findings.d.ts.map +1 -1
  161. package/dist/core/supervisor/findings.js +54 -9
  162. package/dist/core/supervisor/service.d.ts +9 -1
  163. package/dist/core/supervisor/service.d.ts.map +1 -1
  164. package/dist/core/supervisor/service.js +59 -29
  165. package/dist/core/supervisor/services.d.ts.map +1 -1
  166. package/dist/core/supervisor/services.js +22 -1
  167. package/dist/core/supervisor/status.d.ts +17 -2
  168. package/dist/core/supervisor/status.d.ts.map +1 -1
  169. package/dist/core/supervisor/status.js +70 -12
  170. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  171. package/dist/core/supervisor/timeline.js +2 -1
  172. package/dist/core/work/state.js +7 -7
  173. package/dist/core/workflow/engine.d.ts.map +1 -1
  174. package/dist/core/workflow/engine.js +105 -32
  175. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  176. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  177. package/dist/core/workflow/failure-evidence.js +114 -0
  178. package/dist/core/workflow/index.d.ts +1 -1
  179. package/dist/core/workflow/index.d.ts.map +1 -1
  180. package/dist/core/workflow/index.js +1 -1
  181. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  182. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  183. package/dist/core/workflow/live-session-v3.js +126 -5
  184. package/dist/core/workflow/proof.d.ts +5 -5
  185. package/dist/core/workflow/proof.d.ts.map +1 -1
  186. package/dist/core/workflow/proof.js +115 -20
  187. package/dist/core/workflow/run-state.d.ts +3 -1
  188. package/dist/core/workflow/run-state.d.ts.map +1 -1
  189. package/dist/core/workflow/run-state.js +31 -27
  190. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-claude.js +19 -0
  192. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  193. package/dist/core/workflow/spawn-codex.js +13 -0
  194. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  195. package/dist/core/workflow/spawn-cursor.js +19 -0
  196. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  197. package/dist/core/workflow/spawn-opencode.js +14 -0
  198. package/dist/core/workflow/types.d.ts +64 -17
  199. package/dist/core/workflow/types.d.ts.map +1 -1
  200. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  201. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/index.js +0 -1
  203. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  204. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  205. package/dist/core/workflow/workspaces/leases.js +30 -16
  206. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  207. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/local-git.js +270 -6
  209. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  210. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  211. package/dist/core/workflow/workspaces/ownership.js +194 -0
  212. package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
  213. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  215. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  216. package/dist/core/workflow/workspaces/recovery.js +21 -13
  217. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  218. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  219. package/dist/core/workflow/workspaces/state.js +21 -1
  220. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  221. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  222. package/dist/lib/browser/windows-chrome.js +262 -73
  223. package/dist/lib/docs-links.d.ts.map +1 -1
  224. package/dist/lib/docs-links.js +45 -2
  225. package/dist/lib/guarded-remove.d.ts +7 -0
  226. package/dist/lib/guarded-remove.d.ts.map +1 -1
  227. package/dist/lib/guarded-remove.js +106 -12
  228. package/dist/lib/identities/assume.d.ts.map +1 -1
  229. package/dist/lib/identities/assume.js +4 -0
  230. package/dist/lib/tunnel/allowlist.d.ts +44 -0
  231. package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
  232. package/dist/lib/tunnel/allowlist.js +204 -0
  233. package/dist/lib/tunnel/current-address.d.ts +41 -0
  234. package/dist/lib/tunnel/current-address.d.ts.map +1 -0
  235. package/dist/lib/tunnel/current-address.js +123 -0
  236. package/dist/lib/tunnel/gate.js +13 -6
  237. package/dist/lib/tunnel/processes.d.ts +39 -0
  238. package/dist/lib/tunnel/processes.d.ts.map +1 -0
  239. package/dist/lib/tunnel/processes.js +121 -0
  240. package/dist/lib/tunnel/state.d.ts +28 -3
  241. package/dist/lib/tunnel/state.d.ts.map +1 -1
  242. package/dist/lib/tunnel/state.js +98 -27
  243. package/dist/lib/zip/index.d.ts +41 -0
  244. package/dist/lib/zip/index.d.ts.map +1 -0
  245. package/dist/lib/zip/index.js +398 -0
  246. package/package.json +8 -1
  247. package/src/commander.ts +32 -0
  248. package/src/commands/agents.ts +151 -74
  249. package/src/commands/browse.ts +50 -14
  250. package/src/commands/diagnostics.ts +10 -0
  251. package/src/commands/doctor.ts +26 -7
  252. package/src/commands/governor.ts +23 -10
  253. package/src/commands/inbox.ts +52 -2
  254. package/src/commands/rm.ts +43 -4
  255. package/src/commands/servers.ts +16 -4
  256. package/src/commands/supervisor.ts +7 -2
  257. package/src/commands/toc.ts +14 -6
  258. package/src/commands/tunnel.ts +394 -116
  259. package/src/commands/web-fetch.ts +30 -19
  260. package/src/commands/web.ts +9 -3
  261. package/src/commands/workflow.ts +30 -30
  262. package/src/core/agents/live-authority-v3.ts +17 -0
  263. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  264. package/src/core/agents/mailbox.ts +26 -17
  265. package/src/core/agents/render/prompt-context.ts +35 -20
  266. package/src/core/agents/render/session-context.ts +92 -48
  267. package/src/core/agents/rules/stop-hook.ts +3 -3
  268. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  269. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  270. package/src/core/agents/state/names.ts +117 -6
  271. package/src/core/agents/state/pidmap.ts +13 -0
  272. package/src/core/agents/state/proc-start.ts +57 -0
  273. package/src/core/agents/state/stale-sweep.ts +247 -82
  274. package/src/core/agents/state/unchanged-file.ts +456 -0
  275. package/src/core/context/index.ts +446 -136
  276. package/src/core/context/observation.ts +29 -0
  277. package/src/core/diagnostics/bundle.ts +23 -18
  278. package/src/core/diagnostics/path-identity.ts +15 -0
  279. package/src/core/diagnostics/sanitize.ts +4 -3
  280. package/src/core/events/v3/authority-outbox.ts +56 -0
  281. package/src/core/events/v3/generation-custody.ts +59 -0
  282. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  283. package/src/core/events/v3/producers/recorder.ts +94 -68
  284. package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
  285. package/src/core/governor/planning.ts +20 -8
  286. package/src/core/governor/service-read.ts +128 -20
  287. package/src/core/governor/service.ts +119 -42
  288. package/src/core/hooks/cli.ts +139 -22
  289. package/src/core/hooks/fork-lineage.ts +82 -0
  290. package/src/core/hooks/resolve/transcript.ts +88 -0
  291. package/src/core/hooks/shell-rm.ts +103 -0
  292. package/src/core/hooks/shell-tokens.ts +96 -0
  293. package/src/core/hooks/shell-waiter.ts +4 -91
  294. package/src/core/inbox/contract.ts +2 -1
  295. package/src/core/inbox/index.ts +2 -0
  296. package/src/core/inbox/peer-messages.ts +94 -0
  297. package/src/core/inbox/service.ts +88 -22
  298. package/src/core/inbox/targets.ts +148 -0
  299. package/src/core/inbox/watcher.ts +8 -9
  300. package/src/core/journal/index.ts +19 -2
  301. package/src/core/resources/contract.ts +4 -0
  302. package/src/core/resources/service-status.ts +35 -12
  303. package/src/core/resources/service.ts +71 -24
  304. package/src/core/resources/status.ts +57 -8
  305. package/src/core/servers/index.ts +177 -91
  306. package/src/core/servers/tunnels.ts +3 -11
  307. package/src/core/storage/process-log.ts +17 -2
  308. package/src/core/supervisor/contract.ts +13 -0
  309. package/src/core/supervisor/explanations.ts +4 -1
  310. package/src/core/supervisor/findings.ts +68 -10
  311. package/src/core/supervisor/service.ts +88 -26
  312. package/src/core/supervisor/services.ts +26 -8
  313. package/src/core/supervisor/status.ts +89 -12
  314. package/src/core/supervisor/timeline.ts +2 -1
  315. package/src/core/work/state.ts +7 -7
  316. package/src/core/workflow/engine.ts +131 -49
  317. package/src/core/workflow/failure-evidence.ts +141 -0
  318. package/src/core/workflow/index.ts +0 -1
  319. package/src/core/workflow/live-session-v3.ts +146 -5
  320. package/src/core/workflow/proof.ts +134 -26
  321. package/src/core/workflow/run-state.ts +31 -29
  322. package/src/core/workflow/spawn-claude.ts +18 -0
  323. package/src/core/workflow/spawn-codex.ts +13 -0
  324. package/src/core/workflow/spawn-cursor.ts +18 -0
  325. package/src/core/workflow/spawn-opencode.ts +14 -0
  326. package/src/core/workflow/types.ts +51 -17
  327. package/src/core/workflow/workspaces/index.ts +0 -1
  328. package/src/core/workflow/workspaces/leases.ts +48 -17
  329. package/src/core/workflow/workspaces/local-git.ts +315 -5
  330. package/src/core/workflow/workspaces/ownership.ts +232 -0
  331. package/src/core/workflow/workspaces/reclaim.ts +58 -0
  332. package/src/core/workflow/workspaces/recovery.ts +25 -13
  333. package/src/core/workflow/workspaces/state.ts +20 -0
  334. package/src/lib/browser/windows-chrome.ts +346 -86
  335. package/src/lib/docs-links.ts +49 -2
  336. package/src/lib/guarded-remove.ts +132 -14
  337. package/src/lib/identities/assume.ts +4 -0
  338. package/src/lib/tunnel/allowlist.ts +208 -0
  339. package/src/lib/tunnel/current-address.ts +158 -0
  340. package/src/lib/tunnel/gate.ts +16 -8
  341. package/src/lib/tunnel/processes.ts +149 -0
  342. package/src/lib/tunnel/state.ts +123 -27
  343. package/src/lib/zip/index.ts +483 -0
@@ -11,6 +11,7 @@
11
11
  // Runs detached, outside the CLI command framework; no command context is
12
12
  // available; stdout/stderr is captured into .cache/tunnel/gate.log by the
13
13
  // spawner.
14
+ import { allowlistMatches, compileAllowlist } from "./allowlist.js";
14
15
  import { renderTunnelErrorPage } from "./error-page";
15
16
  import { applyUpstreamHeaders } from "./forward-headers";
16
17
  import { ALLOW_PATHS_ENV, isPathAllowed, parseAllowPathsEnv } from "./path-scope.js";
@@ -24,10 +25,8 @@ function argvFlag(flag) {
24
25
  const i = process.argv.indexOf(flag);
25
26
  return i !== -1 ? process.argv[i + 1] : undefined;
26
27
  }
27
- const ALLOW = new Set((process.env.HARNERY_TUNNEL_ALLOW ?? "")
28
- .split(",")
29
- .map((s) => s.trim())
30
- .filter(Boolean));
28
+ // Entries are exact addresses or CIDR ranges (`203.0.113.0/24`, `2601:db8:1:2::/64`).
29
+ const ALLOW = compileAllowlist((process.env.HARNERY_TUNNEL_ALLOW ?? "").split(","));
31
30
  const TARGET = process.env.HARNERY_TUNNEL_TARGET ?? "127.0.0.1:8001";
32
31
  const VHOST = process.env.HARNERY_TUNNEL_VHOST ?? "localhost";
33
32
  const PORT = Number(process.env.HARNERY_TUNNEL_PORT ?? argvFlag("--port") ?? "9001");
@@ -66,7 +65,7 @@ const server = Bun.serve({
66
65
  const url = new URL(req.url);
67
66
  if (ACCESS === "cloudflare-allowlist") {
68
67
  const ip = req.headers.get("cf-connecting-ip") ?? "";
69
- if (!ALLOW.has(ip)) {
68
+ if (!allowlistMatches(ALLOW, ip)) {
70
69
  const details = requestDetails(req, url);
71
70
  // Log denials so operators can whitelist a phone/laptop that just
72
71
  // hit 403 without asking the human to dig up their public IP.
@@ -187,4 +186,12 @@ const server = Bun.serve({
187
186
  console.log(`harn-tunnel-gate :${server.port} -> ${UPSTREAM_HTTP} (Host: ${VHOST})`); // lint-ok-emission: detached worker, see file note above
188
187
  console.log(`access: ${ACCESS}`); // lint-ok-emission: detached worker, see file note above
189
188
  console.log(`paths: ${ALLOW_PATHS.join(", ") || "(empty, every path refused)"}`); // lint-ok-emission: detached worker, see file note above
190
- console.log(`allow: ${[...ALLOW].join(", ") || "(empty, not used outside allowlist mode)"}`); // lint-ok-emission: detached worker, see file note above
189
+ console.log(
190
+ // lint-ok-emission: detached worker, see file note above
191
+ `allow: ${process.env.HARNERY_TUNNEL_ALLOW?.split(",")
192
+ .filter((s) => s.trim())
193
+ .join(", ") || "(empty, not used outside allowlist mode)"}`);
194
+ if (ALLOW.invalid.length > 0) {
195
+ // lint-ok-emission: detached worker, see file note above
196
+ console.log(`allow: ignoring unreadable entries: ${ALLOW.invalid.join(", ")}`);
197
+ }
@@ -0,0 +1,39 @@
1
+ type Run = (command: string, args: string[]) => {
2
+ status: number | null;
3
+ stdout: string;
4
+ missing: boolean;
5
+ };
6
+ export interface ProcessRow {
7
+ pid: number;
8
+ command: string;
9
+ }
10
+ /** Parse `ps -axo pid=,command=` output (also the shape the PowerShell probe emits). */
11
+ export declare function parseProcessRows(output: string): ProcessRow[];
12
+ /** Every process with a command line, or null when the platform gives no way to list them. */
13
+ export declare function listProcesses(platform?: NodeJS.Platform, run?: Run): ProcessRow[] | null;
14
+ /**
15
+ * PIDs whose command line matches `pattern`, an extended regular expression
16
+ * (the intersection of `pgrep -f` and JavaScript syntax). Returns [] when
17
+ * nothing matches or no listing tool works.
18
+ */
19
+ export declare function findPidsByCommandLine(pattern: string, platform?: NodeJS.Platform, run?: Run): number[];
20
+ /** Ports from `ss -tlnH`: `LISTEN 0 4096 127.0.0.1:9001 0.0.0.0:*`. */
21
+ export declare function parseSsPorts(output: string): number[];
22
+ /** Ports from `lsof -nP -iTCP -sTCP:LISTEN` rows ending `127.0.0.1:58055 (LISTEN)`. */
23
+ export declare function parseLsofPorts(output: string): number[];
24
+ /** Ports from `netstat -ano -p TCP` rows: `TCP 0.0.0.0:9001 0.0.0.0:0 LISTENING 4`. */
25
+ export declare function parseNetstatPorts(output: string): number[];
26
+ /**
27
+ * Ports with a LISTEN socket, or null when no discovery tool answered.
28
+ *
29
+ * Null is not "no ports": reporting every port as free would let a caller hand
30
+ * out an occupied port, so callers that need a real answer use `portListening`,
31
+ * which falls back to connecting.
32
+ */
33
+ export declare function listeningPorts(platform?: NodeJS.Platform, run?: Run): Set<number> | null;
34
+ /** True when something accepts a TCP connection on 127.0.0.1:<port>. */
35
+ export declare function connectProbe(port: number, timeoutMs?: number): Promise<boolean>;
36
+ /** Whether `port` has a listener, by the platform tool when there is one, else by connecting. */
37
+ export declare function portListening(port: number): Promise<boolean>;
38
+ export {};
39
+ //# sourceMappingURL=processes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processes.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/processes.ts"],"names":[],"mappings":"AAWA,KAAK,GAAG,GAAG,CACT,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAQjE,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CAO7D;AAMD,8FAA8F;AAC9F,wBAAgB,aAAa,CAC3B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,GAAa,GACjB,UAAU,EAAE,GAAG,IAAI,CAWrB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,GAAa,GACjB,MAAM,EAAE,CAiBV;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAErD;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAI1D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,GAAa,GACjB,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAepB;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW5E;AAED,iGAAiG;AACjG,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGlE"}
@@ -0,0 +1,121 @@
1
+ // Process and port discovery for the tunnel commands, per platform.
2
+ //
3
+ // Linux and macOS have `pgrep`/`ps`; Linux has `ss`; both have `lsof`. Windows
4
+ // has none of these, so it uses PowerShell for command lines and `netstat` for
5
+ // listening sockets. Every probe is best effort: a missing tool yields "no
6
+ // answer" (null or an empty list), never a thrown error, and the callers fall
7
+ // back to checks that need no tool (a TCP connect probe, a PID check).
8
+ import { spawnSync } from "node:child_process";
9
+ import { connect } from "node:net";
10
+ const runTool = (command, args) => {
11
+ const r = spawnSync(command, args, { encoding: "utf-8", windowsHide: true, timeout: 15_000 });
12
+ const missing = r.error?.code === "ENOENT";
13
+ return { status: r.status, stdout: typeof r.stdout === "string" ? r.stdout : "", missing };
14
+ };
15
+ /** Parse `ps -axo pid=,command=` output (also the shape the PowerShell probe emits). */
16
+ export function parseProcessRows(output) {
17
+ const rows = [];
18
+ for (const line of output.split(/\r?\n/)) {
19
+ const m = line.match(/^\s*(\d+)\s+(.*\S)\s*$/);
20
+ if (m)
21
+ rows.push({ pid: Number(m[1]), command: m[2] });
22
+ }
23
+ return rows;
24
+ }
25
+ const POWERSHELL_PROCESS_LIST = "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine } | " +
26
+ 'ForEach-Object { "$($_.ProcessId) $($_.CommandLine)" }';
27
+ /** Every process with a command line, or null when the platform gives no way to list them. */
28
+ export function listProcesses(platform = process.platform, run = runTool) {
29
+ if (platform === "win32") {
30
+ for (const shell of ["powershell.exe", "pwsh.exe", "pwsh"]) {
31
+ const r = run(shell, ["-NoProfile", "-NonInteractive", "-Command", POWERSHELL_PROCESS_LIST]);
32
+ if (r.missing)
33
+ continue;
34
+ if (r.status === 0)
35
+ return parseProcessRows(r.stdout);
36
+ }
37
+ return null;
38
+ }
39
+ const ps = run("ps", ["-axo", "pid=,command="]);
40
+ return ps.status === 0 ? parseProcessRows(ps.stdout) : null;
41
+ }
42
+ /**
43
+ * PIDs whose command line matches `pattern`, an extended regular expression
44
+ * (the intersection of `pgrep -f` and JavaScript syntax). Returns [] when
45
+ * nothing matches or no listing tool works.
46
+ */
47
+ export function findPidsByCommandLine(pattern, platform = process.platform, run = runTool) {
48
+ if (platform !== "win32") {
49
+ // `--` ends options: the provider pattern starts with `--url`, which pgrep
50
+ // would otherwise reject as an unknown flag and report as "no match".
51
+ const r = run("pgrep", ["-f", "--", pattern]);
52
+ if (!r.missing) {
53
+ if (r.status !== 0)
54
+ return [];
55
+ return r.stdout
56
+ .split("\n")
57
+ .map((line) => Number(line.trim()))
58
+ .filter((pid) => Number.isInteger(pid) && pid > 0);
59
+ }
60
+ }
61
+ const rows = listProcesses(platform, run);
62
+ if (!rows)
63
+ return [];
64
+ const re = new RegExp(pattern);
65
+ return rows.filter((row) => re.test(row.command)).map((row) => row.pid);
66
+ }
67
+ /** Ports from `ss -tlnH`: `LISTEN 0 4096 127.0.0.1:9001 0.0.0.0:*`. */
68
+ export function parseSsPorts(output) {
69
+ return [...output.matchAll(/:(\d+)\s/g)].map((m) => Number(m[1]));
70
+ }
71
+ /** Ports from `lsof -nP -iTCP -sTCP:LISTEN` rows ending `127.0.0.1:58055 (LISTEN)`. */
72
+ export function parseLsofPorts(output) {
73
+ return [...output.matchAll(/:(\d+) \(LISTEN\)\s*$/gm)].map((m) => Number(m[1]));
74
+ }
75
+ /** Ports from `netstat -ano -p TCP` rows: `TCP 0.0.0.0:9001 0.0.0.0:0 LISTENING 4`. */
76
+ export function parseNetstatPorts(output) {
77
+ return [...output.matchAll(/^\s*TCP\s+\S*:(\d+)\s+\S+\s+LISTENING\b/gim)].map((m) => Number(m[1]));
78
+ }
79
+ /**
80
+ * Ports with a LISTEN socket, or null when no discovery tool answered.
81
+ *
82
+ * Null is not "no ports": reporting every port as free would let a caller hand
83
+ * out an occupied port, so callers that need a real answer use `portListening`,
84
+ * which falls back to connecting.
85
+ */
86
+ export function listeningPorts(platform = process.platform, run = runTool) {
87
+ if (platform === "win32") {
88
+ const r = run("netstat", ["-ano", "-p", "TCP"]);
89
+ return !r.missing && r.status === 0 ? new Set(parseNetstatPorts(r.stdout)) : null;
90
+ }
91
+ if (platform === "linux") {
92
+ const ss = run("ss", ["-tlnH"]);
93
+ if (!ss.missing && ss.status === 0)
94
+ return new Set(parseSsPorts(ss.stdout));
95
+ }
96
+ // macOS and BSD, or Linux without iproute2. lsof reports a failure when any
97
+ // single process is unreadable while stdout still holds usable rows, so
98
+ // judge it on the output.
99
+ const lsof = run("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN"]);
100
+ if (!lsof.missing && lsof.stdout.trim())
101
+ return new Set(parseLsofPorts(lsof.stdout));
102
+ return null;
103
+ }
104
+ /** True when something accepts a TCP connection on 127.0.0.1:<port>. */
105
+ export function connectProbe(port, timeoutMs = 400) {
106
+ return new Promise((resolve) => {
107
+ const socket = connect({ port, host: "127.0.0.1" });
108
+ const done = (result) => {
109
+ socket.destroy();
110
+ resolve(result);
111
+ };
112
+ socket.setTimeout(timeoutMs, () => done(false));
113
+ socket.once("connect", () => done(true));
114
+ socket.once("error", () => done(false));
115
+ });
116
+ }
117
+ /** Whether `port` has a listener, by the platform tool when there is one, else by connecting. */
118
+ export async function portListening(port) {
119
+ const ports = listeningPorts();
120
+ return ports ? ports.has(port) : connectProbe(port);
121
+ }
@@ -1,10 +1,29 @@
1
+ /** Overrides the directory that holds tunnel state and config (a host keeps it out of `.cache/`). */
2
+ export declare const TUNNEL_DIR_ENV = "HARNERY_TUNNEL_DIR";
3
+ /** Explicit cloudflared binary, for a host that ships a managed copy. */
4
+ export declare const CLOUDFLARED_ENV = "HARNERY_CLOUDFLARED";
5
+ /**
6
+ * Where tunnel state lives: `$HARNERY_TUNNEL_DIR` when set, else
7
+ * `<root>/.cache/tunnel/`. Root defaults to cwd for the command surface and can
8
+ * be supplied by callers that already resolved a repo.
9
+ */
10
+ export declare function tunnelDir(root?: string, env?: NodeJS.ProcessEnv): string;
1
11
  /** The default instance name when `--name` is omitted. */
2
12
  export declare const DEFAULT_INSTANCE = "default";
3
13
  export declare function gateLogFile(name: string): string;
4
14
  export declare function cfdLogFile(name: string): string;
5
15
  export declare function providerLogFile(name: string, provider: TunnelProvider): string;
6
16
  export interface TunnelConfig {
17
+ /** Exact addresses and CIDR ranges the gate admits (Cloudflare provider). */
7
18
  allowed_ips: string[];
19
+ /**
20
+ * Entries `allow add --current` added for this machine's own public
21
+ * addresses. They are a subset of `allowed_ips`; the next refresh replaces
22
+ * exactly these and never touches an entry added by hand.
23
+ */
24
+ auto_allowed?: string[];
25
+ /** Explicit cloudflared binary; `$HARNERY_CLOUDFLARED` wins over this. */
26
+ cloudflared_path?: string;
8
27
  }
9
28
  export type TunnelProvider = "cloudflare" | "tailscale";
10
29
  export type TailscaleMode = "serve" | "funnel";
@@ -57,8 +76,14 @@ export declare function findLiveTunnelForOrigin(port: number, vhost: string, roo
57
76
  /** True when the tunnel's path scope forwards every one of `paths`. */
58
77
  export declare function tunnelServesPaths(state: TunnelState, paths: readonly string[]): boolean;
59
78
  /**
60
- * Ensure cloudflared is on PATH or installed at ~/.local/bin/cloudflared.
61
- * Auto-downloads on Linux; throws on other platforms with brew hint.
79
+ * Resolve the cloudflared binary:
80
+ * 1. `$HARNERY_CLOUDFLARED`, then `cloudflared_path` in the tunnel config: an
81
+ * explicit choice, so a missing or non-runnable file is an error, never a
82
+ * silent fall-through to some other copy.
83
+ * 2. `cloudflared` on PATH.
84
+ * 3. `~/.local/bin/cloudflared`.
85
+ * 4. Linux x86-64 and arm64: download the latest release to (3).
86
+ * Elsewhere it throws with install guidance.
62
87
  */
63
- export declare function ensureCloudflared(): string;
88
+ export declare function ensureCloudflared(env?: NodeJS.ProcessEnv, configuredPath?: string | undefined): string;
64
89
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"AAyBA,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAW1C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,CAG9E;AAgBD,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAgBD,wBAAgB,UAAU,IAAI,YAAY,CAWzC;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEnD;AAED,wBAAgB,SAAS,CACvB,IAAI,GAAE,MAAyB,EAC/B,IAAI,GAAE,MAAsB,GAC3B,WAAW,GAAG,IAAI,CAQpB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,MAAsB,GAAG,IAAI,CAEjF;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAyB,EAAE,IAAI,GAAE,MAAsB,GAAG,IAAI,CAG9F;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAsB,GAAG,WAAW,EAAE,CAWtE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAEzD,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,YAAY,GAAE,iBAAkC,GAC/C,OAAO,CAKT;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAWlE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAsB,EAC5B,YAAY,GAAE,iBAAkC,GAC/C,WAAW,GAAG,IAAI,CASpB;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAEvF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAuB1C"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,25 @@
1
1
  // Tunnel config + state persistence + provider helpers. Commands default to
2
2
  // <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
3
- import { execSync } from "node:child_process";
4
- import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
5
- import { resolve } from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { accessSync, chmodSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { delimiter, join, resolve } from "node:path";
6
7
  import { isPathAllowed } from "./path-scope.js";
7
- // Tunnel state lives under <root>/.cache/tunnel/. Root defaults to cwd for the
8
- // command surface and can be supplied by callers that already resolved a repo.
9
- function cachePath(tool, filename, root = process.cwd()) {
10
- const dir = resolve(root, ".cache", tool);
8
+ /** Overrides the directory that holds tunnel state and config (a host keeps it out of `.cache/`). */
9
+ export const TUNNEL_DIR_ENV = "HARNERY_TUNNEL_DIR";
10
+ /** Explicit cloudflared binary, for a host that ships a managed copy. */
11
+ export const CLOUDFLARED_ENV = "HARNERY_CLOUDFLARED";
12
+ /**
13
+ * Where tunnel state lives: `$HARNERY_TUNNEL_DIR` when set, else
14
+ * `<root>/.cache/tunnel/`. Root defaults to cwd for the command surface and can
15
+ * be supplied by callers that already resolved a repo.
16
+ */
17
+ export function tunnelDir(root = process.cwd(), env = process.env) {
18
+ const override = env[TUNNEL_DIR_ENV]?.trim();
19
+ return override ? resolve(override) : resolve(root, ".cache", "tunnel");
20
+ }
21
+ function cachePath(filename, root = process.cwd()) {
22
+ const dir = tunnelDir(root);
11
23
  if (!existsSync(dir))
12
24
  mkdirSync(dir, { recursive: true });
13
25
  return resolve(dir, filename);
@@ -61,23 +73,27 @@ function normalizeState(raw, fallbackName) {
61
73
  };
62
74
  }
63
75
  export function readConfig() {
64
- const p = cachePath("tunnel", CONFIG_FILE);
76
+ const p = cachePath(CONFIG_FILE);
65
77
  if (!existsSync(p)) {
66
78
  writeConfig(DEFAULT_CONFIG);
67
79
  return { ...DEFAULT_CONFIG, allowed_ips: [...DEFAULT_CONFIG.allowed_ips] };
68
80
  }
69
81
  try {
70
- return JSON.parse(readFileSync(p, "utf-8"));
82
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
83
+ return {
84
+ ...parsed,
85
+ allowed_ips: Array.isArray(parsed.allowed_ips) ? parsed.allowed_ips : [],
86
+ };
71
87
  }
72
88
  catch {
73
89
  return { ...DEFAULT_CONFIG, allowed_ips: [...DEFAULT_CONFIG.allowed_ips] };
74
90
  }
75
91
  }
76
92
  export function writeConfig(cfg) {
77
- writeFileSync(cachePath("tunnel", CONFIG_FILE), JSON.stringify(cfg, null, 2));
93
+ writeFileSync(cachePath(CONFIG_FILE), JSON.stringify(cfg, null, 2));
78
94
  }
79
95
  export function readState(name = DEFAULT_INSTANCE, root = process.cwd()) {
80
- const p = cachePath("tunnel", stateFile(name), root);
96
+ const p = cachePath(stateFile(name), root);
81
97
  if (!existsSync(p))
82
98
  return null;
83
99
  try {
@@ -88,10 +104,10 @@ export function readState(name = DEFAULT_INSTANCE, root = process.cwd()) {
88
104
  }
89
105
  }
90
106
  export function writeState(state, root = process.cwd()) {
91
- writeFileSync(cachePath("tunnel", stateFile(state.name), root), JSON.stringify(state, null, 2));
107
+ writeFileSync(cachePath(stateFile(state.name), root), JSON.stringify(state, null, 2));
92
108
  }
93
109
  export function clearState(name = DEFAULT_INSTANCE, root = process.cwd()) {
94
- const p = cachePath("tunnel", stateFile(name), root);
110
+ const p = cachePath(stateFile(name), root);
95
111
  if (existsSync(p))
96
112
  unlinkSync(p);
97
113
  }
@@ -100,7 +116,7 @@ export function clearState(name = DEFAULT_INSTANCE, root = process.cwd()) {
100
116
  * `state*.json` under `.cache/tunnel/`; tolerates missing/corrupt files.
101
117
  */
102
118
  export function listStates(root = process.cwd()) {
103
- const dir = resolve(root, ".cache", "tunnel");
119
+ const dir = tunnelDir(root);
104
120
  if (!existsSync(dir))
105
121
  return [];
106
122
  const out = [];
@@ -166,26 +182,81 @@ export function findLiveTunnelForOrigin(port, vhost, root = process.cwd(), proce
166
182
  export function tunnelServesPaths(state, paths) {
167
183
  return paths.every((path) => isPathAllowed(path, state.allow_paths));
168
184
  }
169
- /**
170
- * Ensure cloudflared is on PATH or installed at ~/.local/bin/cloudflared.
171
- * Auto-downloads on Linux; throws on other platforms with brew hint.
172
- */
173
- export function ensureCloudflared() {
185
+ function isRunnableFile(path) {
174
186
  try {
175
- execSync("command -v cloudflared", { stdio: "ignore" });
176
- return "cloudflared";
187
+ if (!statSync(path).isFile())
188
+ return false;
189
+ if (process.platform === "win32")
190
+ return true;
191
+ accessSync(path, constants.X_OK);
192
+ return true;
177
193
  }
178
194
  catch {
179
- /* not on PATH; fall through */
195
+ return false;
196
+ }
197
+ }
198
+ /** First `name` on PATH (honoring `.exe` on Windows), or null. Needs no shell. */
199
+ function findOnPath(name, env = process.env) {
200
+ const names = process.platform === "win32" ? [`${name}.exe`, name] : [name];
201
+ for (const dir of (env.PATH ?? env.Path ?? "").split(delimiter)) {
202
+ if (!dir)
203
+ continue;
204
+ for (const candidate of names) {
205
+ const full = join(dir, candidate);
206
+ if (isRunnableFile(full))
207
+ return full;
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+ /**
213
+ * Resolve the cloudflared binary:
214
+ * 1. `$HARNERY_CLOUDFLARED`, then `cloudflared_path` in the tunnel config: an
215
+ * explicit choice, so a missing or non-runnable file is an error, never a
216
+ * silent fall-through to some other copy.
217
+ * 2. `cloudflared` on PATH.
218
+ * 3. `~/.local/bin/cloudflared`.
219
+ * 4. Linux x86-64 and arm64: download the latest release to (3).
220
+ * Elsewhere it throws with install guidance.
221
+ */
222
+ export function ensureCloudflared(env = process.env, configuredPath = readConfig().cloudflared_path) {
223
+ const explicit = env[CLOUDFLARED_ENV]?.trim();
224
+ const fromConfig = configuredPath?.trim();
225
+ for (const [source, path] of [
226
+ [CLOUDFLARED_ENV, explicit],
227
+ ["cloudflared_path in the tunnel config", fromConfig],
228
+ ]) {
229
+ if (!path)
230
+ continue;
231
+ if (isRunnableFile(path))
232
+ return path;
233
+ throw new Error(`cloudflared is set by ${source} to ${path}, but that file is missing or not runnable.`);
180
234
  }
181
- const local = `${process.env.HOME}/.local/bin/cloudflared`;
182
- if (existsSync(local))
235
+ const onPath = findOnPath("cloudflared", env);
236
+ if (onPath)
237
+ return onPath;
238
+ const exe = process.platform === "win32" ? "cloudflared.exe" : "cloudflared";
239
+ const local = join(homedir(), ".local", "bin", exe);
240
+ if (isRunnableFile(local))
183
241
  return local;
184
- if (process.platform !== "linux") {
185
- throw new Error("cloudflared not installed. On macOS: `brew install cloudflared`. " +
242
+ const asset = process.platform === "linux"
243
+ ? { x64: "cloudflared-linux-amd64", arm64: "cloudflared-linux-arm64" }[process.arch]
244
+ : undefined;
245
+ if (!asset) {
246
+ throw new Error("cloudflared not installed. Install it (macOS: `brew install cloudflared`; Windows: " +
247
+ "`winget install Cloudflare.cloudflared`), or point `$HARNERY_CLOUDFLARED` at a binary. " +
186
248
  "On Linux it auto-installs to ~/.local/bin/cloudflared.");
187
249
  }
188
250
  process.stderr.write("Installing cloudflared to ~/.local/bin/...\n"); // lint-ok-emission: sync setup phase before structured output; pairs with the inherited stdio of the curl below
189
- execSync(`curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o ${local} && chmod +x ${local}`, { stdio: "inherit" });
251
+ mkdirSync(join(homedir(), ".local", "bin"), { recursive: true });
252
+ const download = spawnSync("curl", [
253
+ "-fsSL",
254
+ `https://github.com/cloudflare/cloudflared/releases/latest/download/${asset}`,
255
+ "-o",
256
+ local,
257
+ ], { stdio: "inherit" });
258
+ if (download.status !== 0)
259
+ throw new Error("Downloading cloudflared failed; install it by hand.");
260
+ chmodSync(local, 0o755);
190
261
  return local;
191
262
  }
@@ -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"}