@serfab/cadre-host 1.13.0 → 1.14.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 (337) hide show
  1. package/README.md +332 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +301 -505
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +49 -0
  9. package/dist/bin/nat-output.js +127 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/hosted/claim-details.d.ts +44 -0
  12. package/dist/hosted/claim-details.js +66 -0
  13. package/dist/hosted/claim-details.js.map +1 -0
  14. package/dist/hosted/hosted-node-service.d.ts +318 -0
  15. package/dist/hosted/hosted-node-service.js +666 -0
  16. package/dist/hosted/hosted-node-service.js.map +1 -0
  17. package/dist/hosted/hosted-node-store.d.ts +40 -0
  18. package/dist/hosted/hosted-node-store.js +126 -0
  19. package/dist/hosted/hosted-node-store.js.map +1 -0
  20. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  21. package/dist/hosted/hosted-node-supervisor.js +396 -0
  22. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  23. package/dist/hosted/hosted-node-watcher.d.ts +83 -0
  24. package/dist/hosted/hosted-node-watcher.js +224 -0
  25. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  26. package/dist/hosted/index.d.ts +16 -0
  27. package/dist/hosted/index.js +13 -0
  28. package/dist/hosted/index.js.map +1 -0
  29. package/dist/hosted/node-status.d.ts +35 -0
  30. package/dist/hosted/node-status.js +61 -0
  31. package/dist/hosted/node-status.js.map +1 -0
  32. package/dist/hosted/types.d.ts +134 -0
  33. package/dist/hosted/types.js +22 -0
  34. package/dist/hosted/types.js.map +1 -0
  35. package/dist/index.d.ts +12 -24
  36. package/dist/index.js +9 -19
  37. package/dist/index.js.map +1 -1
  38. package/dist/installer/config.d.ts +4 -34
  39. package/dist/installer/config.js +3 -18
  40. package/dist/installer/config.js.map +1 -1
  41. package/dist/installer/index.d.ts +7 -19
  42. package/dist/installer/index.js +14 -69
  43. package/dist/installer/index.js.map +1 -1
  44. package/dist/installer/paths.d.ts +0 -2
  45. package/dist/installer/paths.js +0 -4
  46. package/dist/installer/paths.js.map +1 -1
  47. package/dist/installer/wizard.d.ts +0 -7
  48. package/dist/installer/wizard.js +1 -8
  49. package/dist/installer/wizard.js.map +1 -1
  50. package/dist/nat/address-resolver.d.ts +18 -17
  51. package/dist/nat/address-resolver.js +79 -25
  52. package/dist/nat/address-resolver.js.map +1 -1
  53. package/dist/nat/address-watch.d.ts +37 -0
  54. package/dist/nat/address-watch.js +84 -0
  55. package/dist/nat/address-watch.js.map +1 -0
  56. package/dist/nat/ddns/duckdns.d.ts +2 -1
  57. package/dist/nat/ddns/duckdns.js +5 -1
  58. package/dist/nat/ddns/duckdns.js.map +1 -1
  59. package/dist/nat/ddns/index.d.ts +6 -0
  60. package/dist/nat/ddns/index.js.map +1 -1
  61. package/dist/nat/index.d.ts +12 -9
  62. package/dist/nat/index.js +6 -5
  63. package/dist/nat/index.js.map +1 -1
  64. package/dist/nat/nat-service.d.ts +203 -77
  65. package/dist/nat/nat-service.js +602 -237
  66. package/dist/nat/nat-service.js.map +1 -1
  67. package/dist/nat/nat-store.d.ts +12 -5
  68. package/dist/nat/nat-store.js +114 -30
  69. package/dist/nat/nat-store.js.map +1 -1
  70. package/dist/nat/port-mapper.d.ts +83 -79
  71. package/dist/nat/port-mapper.js +148 -189
  72. package/dist/nat/port-mapper.js.map +1 -1
  73. package/dist/nat/reachability.d.ts +31 -15
  74. package/dist/nat/reachability.js +97 -22
  75. package/dist/nat/reachability.js.map +1 -1
  76. package/dist/nat/types.d.ts +78 -27
  77. package/dist/nat/types.js +8 -5
  78. package/dist/nat/types.js.map +1 -1
  79. package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
  80. package/dist/orchestrator/host-process-orchestrator.js +78 -218
  81. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  82. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  83. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  84. package/dist/orchestrator/identity-file.js.map +1 -0
  85. package/dist/orchestrator/index.d.ts +2 -3
  86. package/dist/orchestrator/index.js +1 -1
  87. package/dist/orchestrator/index.js.map +1 -1
  88. package/dist/orchestrator/node-identity.d.ts +6 -6
  89. package/dist/orchestrator/node-identity.js +7 -7
  90. package/dist/orchestrator/node-identity.js.map +1 -1
  91. package/dist/orchestrator/port-allocator.d.ts +5 -6
  92. package/dist/orchestrator/port-allocator.js +9 -11
  93. package/dist/orchestrator/port-allocator.js.map +1 -1
  94. package/dist/orchestrator/state-store.d.ts +3 -5
  95. package/dist/orchestrator/state-store.js +2 -10
  96. package/dist/orchestrator/state-store.js.map +1 -1
  97. package/dist/orchestrator/types.d.ts +33 -22
  98. package/dist/orchestrator/types.js.map +1 -1
  99. package/dist/server/error-handler.d.ts +5 -25
  100. package/dist/server/error-handler.js +11 -74
  101. package/dist/server/error-handler.js.map +1 -1
  102. package/dist/server/events/types.d.ts +7 -11
  103. package/dist/server/events/types.js +2 -1
  104. package/dist/server/events/types.js.map +1 -1
  105. package/dist/server/index.d.ts +11 -37
  106. package/dist/server/index.js +30 -48
  107. package/dist/server/index.js.map +1 -1
  108. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  109. package/dist/server/routes/hosted-nodes.js +68 -0
  110. package/dist/server/routes/hosted-nodes.js.map +1 -0
  111. package/dist/server/routes/nat.d.ts +4 -1
  112. package/dist/server/routes/nat.js +13 -2
  113. package/dist/server/routes/nat.js.map +1 -1
  114. package/dist/server/routes/nodes.d.ts +6 -20
  115. package/dist/server/routes/nodes.js +7 -86
  116. package/dist/server/routes/nodes.js.map +1 -1
  117. package/dist/server/routes/settings.d.ts +2 -7
  118. package/dist/server/routes/settings.js +3 -7
  119. package/dist/server/routes/settings.js.map +1 -1
  120. package/dist/server/routes/status.d.ts +2 -25
  121. package/dist/server/routes/status.js +1 -11
  122. package/dist/server/routes/status.js.map +1 -1
  123. package/dist/server/server.js +8 -8
  124. package/dist/server/settings-store.d.ts +2 -2
  125. package/dist/server/settings-store.js +2 -2
  126. package/dist/server/static.js +1 -2
  127. package/dist/server/static.js.map +1 -1
  128. package/dist/ui/assets/index-4FWuU8ye.js +19 -0
  129. package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
  130. package/dist/ui/assets/index-Di9Q4d01.css +1 -0
  131. package/dist/ui/index.html +2 -2
  132. package/dist/update/index.d.ts +2 -2
  133. package/dist/update/index.js +2 -2
  134. package/dist/update/store.d.ts +1 -1
  135. package/dist/update/store.js +1 -1
  136. package/dist/update/types.d.ts +1 -1
  137. package/dist/update/types.js +1 -1
  138. package/package.json +5 -6
  139. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  140. package/src/__tests__/cli.smoke.test.ts +1 -1
  141. package/src/__tests__/global-setup.ts +2 -3
  142. package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
  143. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  144. package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
  145. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  146. package/src/__tests__/orchestrator-push.test.ts +138 -146
  147. package/src/__tests__/orchestrator.test.ts +1019 -1063
  148. package/src/bin/host.ts +368 -553
  149. package/src/bin/join-output.ts +120 -0
  150. package/src/bin/nat-output.ts +177 -0
  151. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
  152. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
  153. package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
  154. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
  155. package/src/hosted/__tests__/test-invitation.ts +33 -0
  156. package/src/hosted/claim-details.ts +96 -0
  157. package/src/hosted/hosted-node-service.ts +795 -0
  158. package/src/hosted/hosted-node-store.ts +144 -0
  159. package/src/hosted/hosted-node-supervisor.ts +439 -0
  160. package/src/hosted/hosted-node-watcher.ts +250 -0
  161. package/src/hosted/index.ts +53 -0
  162. package/src/hosted/node-status.ts +71 -0
  163. package/src/hosted/types.ts +153 -0
  164. package/src/index.ts +72 -91
  165. package/src/installer/__tests__/config.test.ts +15 -40
  166. package/src/installer/__tests__/installer.smoke.test.ts +5 -86
  167. package/src/installer/__tests__/wizard.test.ts +56 -64
  168. package/src/installer/config.ts +135 -175
  169. package/src/installer/index.ts +15 -83
  170. package/src/installer/paths.ts +104 -109
  171. package/src/installer/wizard.ts +87 -103
  172. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  173. package/src/nat/__tests__/nat-service.test.ts +433 -408
  174. package/src/nat/__tests__/nat-store.test.ts +127 -93
  175. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  176. package/src/nat/__tests__/reachability.test.ts +96 -13
  177. package/src/nat/address-resolver.ts +94 -52
  178. package/src/nat/address-watch.ts +92 -0
  179. package/src/nat/ddns/duckdns.ts +5 -1
  180. package/src/nat/ddns/index.ts +6 -0
  181. package/src/nat/index.ts +87 -71
  182. package/src/nat/nat-service.ts +889 -501
  183. package/src/nat/nat-store.ts +237 -154
  184. package/src/nat/port-mapper.ts +241 -329
  185. package/src/nat/reachability.ts +134 -25
  186. package/src/nat/types.ts +209 -159
  187. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  188. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  189. package/src/orchestrator/host-process-orchestrator.ts +81 -243
  190. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  191. package/src/orchestrator/index.ts +9 -10
  192. package/src/orchestrator/node-identity.ts +41 -41
  193. package/src/orchestrator/port-allocator.ts +71 -73
  194. package/src/orchestrator/state-store.ts +76 -90
  195. package/src/orchestrator/types.ts +34 -23
  196. package/src/server/__tests__/bus.test.ts +38 -38
  197. package/src/server/__tests__/error-handler.test.ts +81 -118
  198. package/src/server/__tests__/fakes.ts +40 -44
  199. package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
  200. package/src/server/__tests__/nodes-route.test.ts +114 -283
  201. package/src/server/__tests__/publishers.test.ts +193 -180
  202. package/src/server/__tests__/server.smoke.test.ts +134 -223
  203. package/src/server/__tests__/settings-route.test.ts +179 -195
  204. package/src/server/__tests__/sse-route.test.ts +119 -119
  205. package/src/server/__tests__/status-route.test.ts +82 -121
  206. package/src/server/error-handler.ts +112 -178
  207. package/src/server/events/types.ts +20 -24
  208. package/src/server/index.ts +223 -267
  209. package/src/server/routes/hosted-nodes.ts +83 -0
  210. package/src/server/routes/nat.ts +75 -63
  211. package/src/server/routes/nodes.ts +96 -188
  212. package/src/server/routes/settings.ts +115 -123
  213. package/src/server/routes/status.ts +102 -134
  214. package/src/server/server.ts +8 -8
  215. package/src/server/settings-store.ts +2 -2
  216. package/src/server/static.ts +1 -2
  217. package/src/update/index.ts +2 -2
  218. package/src/update/store.ts +1 -1
  219. package/src/update/types.ts +1 -1
  220. package/dist/auth/duration.d.ts +0 -1
  221. package/dist/auth/duration.js +0 -32
  222. package/dist/auth/duration.js.map +0 -1
  223. package/dist/auth/index.d.ts +0 -11
  224. package/dist/auth/index.js +0 -9
  225. package/dist/auth/index.js.map +0 -1
  226. package/dist/auth/self-label.d.ts +0 -36
  227. package/dist/auth/self-label.js +0 -57
  228. package/dist/auth/self-label.js.map +0 -1
  229. package/dist/auth/trust-circle-store.d.ts +0 -26
  230. package/dist/auth/trust-circle-store.js +0 -129
  231. package/dist/auth/trust-circle-store.js.map +0 -1
  232. package/dist/auth/trust-circle.d.ts +0 -156
  233. package/dist/auth/trust-circle.js +0 -307
  234. package/dist/auth/trust-circle.js.map +0 -1
  235. package/dist/auth/types.d.ts +0 -84
  236. package/dist/auth/types.js +0 -24
  237. package/dist/auth/types.js.map +0 -1
  238. package/dist/donation/donation-service.d.ts +0 -389
  239. package/dist/donation/donation-service.js +0 -828
  240. package/dist/donation/donation-service.js.map +0 -1
  241. package/dist/donation/donation-store.d.ts +0 -51
  242. package/dist/donation/donation-store.js +0 -152
  243. package/dist/donation/donation-store.js.map +0 -1
  244. package/dist/donation/donation-supervisor.js +0 -364
  245. package/dist/donation/donation-supervisor.js.map +0 -1
  246. package/dist/donation/grant-service.d.ts +0 -80
  247. package/dist/donation/grant-service.js +0 -210
  248. package/dist/donation/grant-service.js.map +0 -1
  249. package/dist/donation/grant-store.d.ts +0 -38
  250. package/dist/donation/grant-store.js +0 -135
  251. package/dist/donation/grant-store.js.map +0 -1
  252. package/dist/donation/index.d.ts +0 -20
  253. package/dist/donation/index.js +0 -17
  254. package/dist/donation/index.js.map +0 -1
  255. package/dist/donation/types.d.ts +0 -224
  256. package/dist/donation/types.js +0 -40
  257. package/dist/donation/types.js.map +0 -1
  258. package/dist/installer/identity.js.map +0 -1
  259. package/dist/owner/index.d.ts +0 -11
  260. package/dist/owner/index.js +0 -11
  261. package/dist/owner/index.js.map +0 -1
  262. package/dist/owner/owner-node-client.d.ts +0 -110
  263. package/dist/owner/owner-node-client.js +0 -184
  264. package/dist/owner/owner-node-client.js.map +0 -1
  265. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  266. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  267. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  268. package/dist/server/routes/grants-admin.d.ts +0 -29
  269. package/dist/server/routes/grants-admin.js +0 -58
  270. package/dist/server/routes/grants-admin.js.map +0 -1
  271. package/dist/server/routes/grants.d.ts +0 -28
  272. package/dist/server/routes/grants.js +0 -165
  273. package/dist/server/routes/grants.js.map +0 -1
  274. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  275. package/dist/server/routes/provision-request-validation.js +0 -97
  276. package/dist/server/routes/provision-request-validation.js.map +0 -1
  277. package/dist/server/routes/strands.d.ts +0 -20
  278. package/dist/server/routes/strands.js +0 -38
  279. package/dist/server/routes/strands.js.map +0 -1
  280. package/dist/server/routes/trust-circle.d.ts +0 -17
  281. package/dist/server/routes/trust-circle.js +0 -35
  282. package/dist/server/routes/trust-circle.js.map +0 -1
  283. package/dist/strands/index.d.ts +0 -7
  284. package/dist/strands/index.js +0 -6
  285. package/dist/strands/index.js.map +0 -1
  286. package/dist/strands/strand-service.d.ts +0 -50
  287. package/dist/strands/strand-service.js +0 -103
  288. package/dist/strands/strand-service.js.map +0 -1
  289. package/dist/strands/types.d.ts +0 -93
  290. package/dist/strands/types.js +0 -23
  291. package/dist/strands/types.js.map +0 -1
  292. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  293. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  294. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  295. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  296. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  297. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  298. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  299. package/src/auth/__tests__/duration.test.ts +0 -28
  300. package/src/auth/__tests__/self-label.test.ts +0 -85
  301. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  302. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  303. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  304. package/src/auth/duration.ts +0 -33
  305. package/src/auth/index.ts +0 -21
  306. package/src/auth/self-label.ts +0 -74
  307. package/src/auth/trust-circle-store.ts +0 -155
  308. package/src/auth/trust-circle.ts +0 -395
  309. package/src/auth/types.ts +0 -99
  310. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  311. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  312. package/src/donation/__tests__/grant-service.test.ts +0 -213
  313. package/src/donation/__tests__/grant-store.test.ts +0 -113
  314. package/src/donation/donation-service.ts +0 -1033
  315. package/src/donation/donation-store.ts +0 -176
  316. package/src/donation/donation-supervisor.ts +0 -411
  317. package/src/donation/grant-service.ts +0 -252
  318. package/src/donation/grant-store.ts +0 -152
  319. package/src/donation/index.ts +0 -65
  320. package/src/donation/types.ts +0 -260
  321. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  322. package/src/owner/index.ts +0 -12
  323. package/src/owner/owner-node-client.ts +0 -259
  324. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  325. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  326. package/src/server/__tests__/grants-route.test.ts +0 -394
  327. package/src/server/__tests__/strands-route.test.ts +0 -234
  328. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  329. package/src/server/routes/grants-admin.ts +0 -74
  330. package/src/server/routes/grants.ts +0 -193
  331. package/src/server/routes/provision-request-validation.ts +0 -105
  332. package/src/server/routes/strands.ts +0 -54
  333. package/src/server/routes/trust-circle.ts +0 -49
  334. package/src/strands/__tests__/strand-service.test.ts +0 -165
  335. package/src/strands/index.ts +0 -14
  336. package/src/strands/strand-service.ts +0 -135
  337. package/src/strands/types.ts +0 -102
@@ -0,0 +1 @@
1
+ .dot.svelte-1qa2kn2{display:inline-flex;align-items:center;gap:.5rem;font-weight:500}.bulb.svelte-1qa2kn2{display:inline-block;border-radius:999px;flex:0 0 auto;background:var(--color-text-soft);box-shadow:0 0 0 3px #0000000f}.dot-ok.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-success);box-shadow:0 0 0 3px var(--color-success-bg)}.dot-warn.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-warn);box-shadow:0 0 0 3px var(--color-warn-bg)}.dot-err.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-danger);box-shadow:0 0 0 3px var(--color-danger-bg)}.dot-muted.svelte-1qa2kn2 .bulb:where(.svelte-1qa2kn2){background:var(--color-text-soft)}.dot-ok.svelte-1qa2kn2{color:var(--color-success)}.dot-warn.svelte-1qa2kn2{color:var(--color-warn)}.dot-err.svelte-1qa2kn2{color:var(--color-danger)}.banner.svelte-15mtls8{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info)}.summary.svelte-15mtls8{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr))}.kv.svelte-15mtls8{margin:var(--space-3) 0 0 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-15mtls8>div:where(.svelte-15mtls8){display:contents}.kv.svelte-15mtls8 dt:where(.svelte-15mtls8){color:var(--color-text-muted)}.kv.svelte-15mtls8 dd:where(.svelte-15mtls8){margin:0}.big.svelte-15mtls8{font-size:1.75rem;font-weight:600;margin:var(--space-2) 0}.big.svelte-15mtls8 .muted:where(.svelte-15mtls8){font-size:.95rem;font-weight:400}.none.svelte-15mtls8{margin:var(--space-2) 0;color:var(--color-warn);font-weight:500}.link.svelte-15mtls8{display:inline-block;margin-top:var(--space-2);font-weight:500}.row.svelte-io9f1k{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.kv.svelte-io9f1k{margin:0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-io9f1k>div:where(.svelte-io9f1k){display:contents}.kv.svelte-io9f1k dt:where(.svelte-io9f1k){color:var(--color-text-muted)}.kv.svelte-io9f1k dd:where(.svelte-io9f1k){margin:0;min-width:0}.addrs.svelte-io9f1k{list-style:none;margin:0;padding:0}.addrs.svelte-io9f1k li:where(.svelte-io9f1k){display:flex;align-items:center;gap:.25rem}.addrs.svelte-io9f1k code:where(.svelte-io9f1k){word-break:break-all}.reach.svelte-io9f1k p:where(.svelte-io9f1k){margin:0}.instruction.svelte-io9f1k{font-weight:500}summary.svelte-io9f1k{cursor:pointer;font-size:.9rem;font-weight:500}.forward.svelte-io9f1k{display:grid;gap:var(--space-2);margin-top:var(--space-2)}.fields.svelte-io9f1k{display:grid;gap:var(--space-2);grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))}.actions.svelte-io9f1k{display:flex;justify-content:flex-end;gap:.5rem}.small.svelte-io9f1k{font-size:.85rem}.error.svelte-io9f1k{color:var(--color-danger)}.grid.two.svelte-hh3bto{grid-template-columns:repeat(auto-fit,minmax(20rem,1fr))}.kv.svelte-hh3bto{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.9rem}.kv.svelte-hh3bto>div:where(.svelte-hh3bto){display:contents}.kv.svelte-hh3bto dt:where(.svelte-hh3bto){color:var(--color-text-muted)}.kv.svelte-hh3bto dd:where(.svelte-hh3bto){margin:0}.row.inline.svelte-hh3bto{gap:.5rem}.row.inline.svelte-hh3bto input[type=checkbox]:where(.svelte-hh3bto){width:auto}.nodes.svelte-hh3bto{list-style:none;margin:0;padding:0;display:grid;gap:var(--space-3)}.nodes.svelte-hh3bto li:where(.svelte-hh3bto){border-top:1px solid var(--color-border);padding-top:var(--space-3)}.nodes.svelte-hh3bto li:where(.svelte-hh3bto):first-child{border-top:0;padding-top:0}.warning.svelte-hh3bto{background:var(--color-warn-bg);border-color:var(--color-warn)}.actions.svelte-hh3bto{display:flex;justify-content:flex-end;gap:.5rem}.small.svelte-hh3bto{font-size:.85rem}.scroll.svelte-7tcksg{overflow-x:auto}table.svelte-7tcksg{width:100%;border-collapse:collapse;font-size:.92rem}th.svelte-7tcksg,td.svelte-7tcksg{padding:.5rem .75rem;text-align:left;border-bottom:1px solid var(--color-border);white-space:nowrap}th.svelte-7tcksg{font-weight:600;font-size:.78rem;text-transform:uppercase;letter-spacing:.05em;color:var(--color-text-muted);border-bottom-color:var(--color-border-strong)}tr.svelte-7tcksg:last-child td:where(.svelte-7tcksg){border-bottom:none}.badges.svelte-7tcksg{display:inline-flex;gap:.25rem}.error.svelte-7tcksg{color:var(--color-danger)}.qr.svelte-1uqetyj{background:#fff;border-radius:var(--radius);padding:.5rem;display:inline-block;line-height:0}.qr.svelte-1uqetyj svg{width:100%;height:100%;display:block}.placeholder.svelte-1uqetyj{background:var(--color-surface-alt)}.qr-wrap.svelte-sddtr9{display:flex;justify-content:center;margin:var(--space-3) 0}.token.svelte-sddtr9{display:flex;flex-direction:column;gap:.5rem}.token.svelte-sddtr9 textarea:where(.svelte-sddtr9){font-family:var(--font-mono);font-size:.78rem;word-break:break-all;resize:vertical}.token.svelte-sddtr9 button:where(.svelte-sddtr9){align-self:flex-end;display:inline-flex;align-items:center;gap:.375rem}p.svelte-1jht8du{margin:0}.small.svelte-1jht8du{font-size:.85rem}.error.svelte-1jht8du{color:var(--color-danger)}.warn.svelte-1jht8du{color:var(--color-warn);font-weight:500}.backdrop.svelte-80vbit{position:fixed;inset:0;background:#0f111673;z-index:1000}.dialog.svelte-80vbit{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:var(--space-5);min-width:min(24rem,calc(100vw - 2rem));max-width:min(32rem,calc(100vw - 2rem));overflow-wrap:anywhere;z-index:1010}.note.svelte-80vbit{background:var(--color-warn-bg);color:var(--color-warn);border-radius:var(--radius);padding:var(--space-2) var(--space-3);font-size:.9rem}.actions.svelte-80vbit{display:flex;justify-content:flex-end;gap:.5rem;margin-top:var(--space-4)}.log-card.svelte-15ufwrh{display:flex;flex-direction:column;gap:var(--space-3)}.header.svelte-15ufwrh{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3)}.logs.svelte-15ufwrh{max-height:24rem;font-size:.78rem;line-height:1.45;white-space:pre}.small.svelte-15ufwrh{font-size:.85rem}button.svelte-15ufwrh{display:inline-flex;align-items:center;gap:.375rem}.back.svelte-1eq4g96{font-size:.92rem}.page-header.svelte-1eq4g96{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.page-header.svelte-1eq4g96 h2:where(.svelte-1eq4g96) code:where(.svelte-1eq4g96){word-break:break-all}.card-head.svelte-1eq4g96{display:flex;align-items:center;gap:.5rem}.card-head.svelte-1eq4g96 h3:where(.svelte-1eq4g96){margin:0}.state.svelte-1eq4g96{margin:0;font-weight:500}.small.svelte-1eq4g96{font-size:.85rem;margin:0}.error.svelte-1eq4g96{color:var(--color-danger)}.kv.svelte-1eq4g96{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1eq4g96>div:where(.svelte-1eq4g96){display:contents}.kv.svelte-1eq4g96 dt:where(.svelte-1eq4g96){color:var(--color-text-muted)}.kv.svelte-1eq4g96 dd:where(.svelte-1eq4g96){margin:0;min-width:0;overflow-wrap:anywhere}.actions.svelte-1eq4g96{display:flex;justify-content:flex-end;gap:.5rem;flex-wrap:wrap}.page-header.svelte-cshs6v{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:var(--space-3)}.page-header.svelte-cshs6v>div:where(.svelte-cshs6v){flex:1 1 20rem}.invitation.svelte-cshs6v p:where(.svelte-cshs6v){margin:0}.invitation.svelte-cshs6v textarea:where(.svelte-cshs6v){width:100%;box-sizing:border-box;font-family:var(--font-mono);font-size:.85rem;word-break:break-all}.node-head.svelte-cshs6v{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.node-head.svelte-cshs6v code:where(.svelte-cshs6v){word-break:break-all}.state.svelte-cshs6v{margin:0;font-weight:500}.small.svelte-cshs6v{font-size:.85rem;margin:0}.actions.svelte-cshs6v{display:flex;justify-content:flex-end;gap:.5rem;flex-wrap:wrap}.error.svelte-cshs6v{color:var(--color-danger)}.row.inline.svelte-1cl3wo6{gap:.5rem}.row.inline.svelte-1cl3wo6 input[type=checkbox]:where(.svelte-1cl3wo6){width:auto}.banner.svelte-1cl3wo6{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius);background:var(--color-info-bg);color:var(--color-info);border:1px solid var(--color-info);flex-wrap:wrap}.kv.svelte-1cl3wo6{margin:var(--space-3) 0;display:grid;grid-template-columns:max-content 1fr;gap:var(--space-1) var(--space-3);font-size:.92rem}.kv.svelte-1cl3wo6>div:where(.svelte-1cl3wo6){display:contents}.kv.svelte-1cl3wo6 dt:where(.svelte-1cl3wo6){color:var(--color-text-muted)}.kv.svelte-1cl3wo6 dd:where(.svelte-1cl3wo6){margin:0}.copy-row.svelte-1cl3wo6{display:flex;align-items:center;gap:.375rem}.actions.svelte-1cl3wo6{display:flex;justify-content:flex-end}.small.svelte-1cl3wo6{font-size:.85rem}.error.svelte-1cl3wo6{color:var(--color-danger)}.toast-stack.svelte-g7yjw8{position:fixed;right:1rem;bottom:1rem;display:flex;flex-direction:column;gap:.5rem;max-width:min(30rem,calc(100vw - 2rem));z-index:1100}.toast.svelte-g7yjw8{background:var(--color-surface);color:var(--color-text);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:.625rem .75rem;display:flex;align-items:center;gap:.5rem;box-shadow:var(--shadow);font-size:.92rem}.toast-error.svelte-g7yjw8{border-color:var(--color-danger);background:var(--color-danger-bg);color:var(--color-danger)}.toast-success.svelte-g7yjw8{border-color:var(--color-success);background:var(--color-success-bg);color:var(--color-success)}.text.svelte-g7yjw8{flex:1 1 auto}button.svelte-g7yjw8{font-size:1.1rem;line-height:1;padding:.125rem .5rem;color:currentColor}.topbar.svelte-1r8vfx3{position:sticky;top:0;z-index:10;display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-3) var(--space-5);background:var(--color-surface);border-bottom:1px solid var(--color-border);backdrop-filter:saturate(180%) blur(8px)}.brand.svelte-1r8vfx3{display:flex;align-items:center;gap:.6rem;font-weight:600}.logo.svelte-1r8vfx3{color:var(--color-accent);font-size:1.1rem}nav.svelte-1r8vfx3{display:flex;gap:.125rem}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3){font-size:.92rem;color:var(--color-text-muted);padding:.375rem .75rem;border-radius:var(--radius);text-decoration:none}nav.svelte-1r8vfx3 a:where(.svelte-1r8vfx3):hover{background:var(--color-surface-alt);color:var(--color-text)}nav.svelte-1r8vfx3 a.active:where(.svelte-1r8vfx3){color:var(--color-accent-on);background:var(--color-accent)}main.svelte-1r8vfx3{flex:1 1 auto;width:min(64rem,100%);margin:0 auto;padding:var(--space-5)}@media(max-width:640px){.topbar.svelte-1r8vfx3{flex-direction:column;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3)}nav.svelte-1r8vfx3{width:100%;overflow-x:auto}main.svelte-1r8vfx3{padding:var(--space-3)}}:root{color-scheme:light dark;--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, monospace;--radius-sm: .25rem;--radius: .5rem;--radius-lg: .75rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.5rem;--space-6: 2rem;--space-7: 3rem;--color-bg: #f7f8fa;--color-surface: #ffffff;--color-surface-alt: #f0f2f6;--color-border: #d9dde3;--color-border-strong: #b8bec7;--color-text: #1d1f24;--color-text-muted: #5b626d;--color-text-soft: #8a8f99;--color-accent: #1d4ed8;--color-accent-strong: #1e3a8a;--color-accent-on: #ffffff;--color-accent-soft: #e0e7ff;--color-success: #15803d;--color-success-bg: #dcfce7;--color-warn: #b45309;--color-warn-bg: #fef3c7;--color-danger: #b91c1c;--color-danger-bg: #fee2e2;--color-info: #1d4ed8;--color-info-bg: #dbeafe;--shadow-sm: 0 1px 2px rgba(15, 23, 42, .05);--shadow: 0 4px 12px rgba(15, 23, 42, .08);--shadow-lg: 0 12px 32px rgba(15, 23, 42, .16)}@media(prefers-color-scheme:dark){:root{--color-bg: #0f1116;--color-surface: #181b22;--color-surface-alt: #20242d;--color-border: #2c313c;--color-border-strong: #3b424f;--color-text: #e8eaf0;--color-text-muted: #a4abb8;--color-text-soft: #777e8b;--color-accent: #93c5fd;--color-accent-strong: #bfdbfe;--color-accent-on: #0f1116;--color-accent-soft: #1e293b;--color-success: #4ade80;--color-success-bg: #14532d;--color-warn: #fbbf24;--color-warn-bg: #78350f;--color-danger: #f87171;--color-danger-bg: #7f1d1d;--color-info: #93c5fd;--color-info-bg: #1e3a8a;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow: 0 4px 12px rgba(0, 0, 0, .45);--shadow-lg: 0 12px 32px rgba(0, 0, 0, .6)}}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;background:var(--color-bg);color:var(--color-text);font-family:var(--font-sans);font-size:15px;line-height:1.5;-webkit-font-smoothing:antialiased;min-height:100vh}#app{min-height:100vh;display:flex;flex-direction:column}h1,h2,h3,h4,h5,h6{margin:0 0 var(--space-2) 0;font-weight:600;line-height:1.25;letter-spacing:-.01em}h1{font-size:1.5rem}h2{font-size:1.25rem}h3{font-size:1.05rem}p{margin:0 0 var(--space-3) 0}a{color:var(--color-accent);text-decoration:none}a:hover{text-decoration:underline}button{font:inherit;cursor:pointer;border:1px solid var(--color-border-strong);background:var(--color-surface);color:var(--color-text);padding:var(--space-2) var(--space-3);border-radius:var(--radius);transition:background-color .12s ease,border-color .12s ease}button:hover:not(:disabled){background:var(--color-surface-alt);border-color:var(--color-text-muted)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}button:disabled{opacity:.55;cursor:not-allowed}button.primary{background:var(--color-accent);border-color:var(--color-accent);color:var(--color-accent-on)}button.primary:hover:not(:disabled){background:var(--color-accent-strong);border-color:var(--color-accent-strong)}button.danger{background:var(--color-danger);border-color:var(--color-danger);color:#fff}button.ghost{background:transparent;border-color:transparent;color:var(--color-text-muted);padding:var(--space-1) var(--space-2)}button.ghost:hover:not(:disabled){color:var(--color-text);background:var(--color-surface-alt)}input[type=text],input[type=url],input[type=number],input[type=password],select,textarea{font:inherit;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border-strong);border-radius:var(--radius);padding:var(--space-2) var(--space-3);width:100%}label{display:block;font-size:.85rem;color:var(--color-text-muted);font-weight:500;margin-bottom:var(--space-1)}code,pre{font-family:var(--font-mono);font-size:.85rem}pre{background:var(--color-surface-alt);padding:var(--space-3);border-radius:var(--radius);overflow:auto;margin:0}hr{border:none;border-top:1px solid var(--color-border);margin:var(--space-5) 0}.card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:var(--shadow-sm)}.muted{color:var(--color-text-muted)}.row{display:flex;gap:var(--space-3);align-items:center;flex-wrap:wrap}.stack{display:flex;flex-direction:column;gap:var(--space-3)}.grid{display:grid;gap:var(--space-4)}.badge{display:inline-flex;align-items:center;gap:var(--space-1);font-size:.78rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;padding:.125rem .5rem;border-radius:999px;background:var(--color-surface-alt);color:var(--color-text-muted)}.badge.ok{background:var(--color-success-bg);color:var(--color-success)}.badge.warn{background:var(--color-warn-bg);color:var(--color-warn)}.badge.err{background:var(--color-danger-bg);color:var(--color-danger)}.badge.info{background:var(--color-info-bg);color:var(--color-info)}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="color-scheme" content="light dark" />
7
7
  <title>cadre-host</title>
8
- <script type="module" crossorigin src="./assets/index-C6nELNfC.js"></script>
9
- <link rel="stylesheet" crossorigin href="./assets/index-TtIYSeZO.css">
8
+ <script type="module" crossorigin src="./assets/index-4FWuU8ye.js"></script>
9
+ <link rel="stylesheet" crossorigin href="./assets/index-Di9Q4d01.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="app"></div>
@@ -98,8 +98,8 @@ export declare class UpdateService {
98
98
  stop(): void;
99
99
  }
100
100
  /**
101
- * Typed handler facade — mirrors `createTrustCircleHandlers` /
102
- * `createNatHandlers`. The local-UI ticket consumes these.
101
+ * Typed handler facade — same shape as `createNatHandlers`. The local-UI
102
+ * server consumes these.
103
103
  */
104
104
  export declare function createUpdateHandlers(service: UpdateService): UpdateHandlers;
105
105
  export type { UpdateState, UpdateApplyResult, UpdateSettings, UpdateManifest, SignedManifest } from './types.js';
@@ -231,8 +231,8 @@ export class UpdateService {
231
231
  }
232
232
  }
233
233
  /**
234
- * Typed handler facade — mirrors `createTrustCircleHandlers` /
235
- * `createNatHandlers`. The local-UI ticket consumes these.
234
+ * Typed handler facade — same shape as `createNatHandlers`. The local-UI
235
+ * server consumes these.
236
236
  */
237
237
  export function createUpdateHandlers(service) {
238
238
  return {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Atomic JSON store for `<dataDir>/update-state.json`.
3
3
  *
4
- * Mirrors `TrustCircleStore` / `NatStore`: load on demand, write to
4
+ * Mirrors `NatStore`: load on demand, write to
5
5
  * `<path>.tmp` then rename. Single-writer assumption — only one cadre-host
6
6
  * process owns a given dataDir.
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Atomic JSON store for `<dataDir>/update-state.json`.
3
3
  *
4
- * Mirrors `TrustCircleStore` / `NatStore`: load on demand, write to
4
+ * Mirrors `NatStore`: load on demand, write to
5
5
  * `<path>.tmp` then rename. Single-writer assumption — only one cadre-host
6
6
  * process owns a given dataDir.
7
7
  */
@@ -8,7 +8,7 @@
8
8
  * - apply.ts — invoke `npm install -g` + rollback on failure.
9
9
  * - index.ts — composes the above (UpdateService).
10
10
  *
11
- * Mirrors the typed-handler pattern used by trust-circle and NAT (handlers
11
+ * Mirrors the typed-handler pattern used by NAT (handlers
12
12
  * throw `UpdateError` whose `.code` the local-UI maps to HTTP status).
13
13
  */
14
14
  /** Inner manifest object — signed by the release key. */
@@ -8,7 +8,7 @@
8
8
  * - apply.ts — invoke `npm install -g` + rollback on failure.
9
9
  * - index.ts — composes the above (UpdateService).
10
10
  *
11
- * Mirrors the typed-handler pattern used by trust-circle and NAT (handlers
11
+ * Mirrors the typed-handler pattern used by NAT (handlers
12
12
  * throw `UpdateError` whose `.code` the local-UI maps to HTTP status).
13
13
  */
14
14
  /** Thrown by UpdateService.apply() and related entry points. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serfab/cadre-host",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "description": "Self-hosted cadre node manager for basement-PC deployments",
6
6
  "main": "dist/index.js",
@@ -29,8 +29,7 @@
29
29
  "cadre",
30
30
  "host",
31
31
  "self-hosted",
32
- "p2p",
33
- "trust-circle"
32
+ "p2p"
34
33
  ],
35
34
  "scripts": {
36
35
  "clean": "rimraf dist",
@@ -65,9 +64,9 @@
65
64
  "@libp2p/interface": "^3.3.0",
66
65
  "@libp2p/peer-id": "^6.0.15",
67
66
  "@multiformats/multiaddr": "^13.0.3",
68
- "@serfab/cadre-cli": "^1.13.0",
69
- "@serfab/cadre-core": "^1.13.0",
70
- "@serfab/cadre-provider": "^1.13.0",
67
+ "@serfab/cadre-cli": "^1.14.0",
68
+ "@serfab/cadre-core": "^1.14.0",
69
+ "@serfab/cadre-provider": "^1.14.0",
71
70
  "commander": "^14.0.2",
72
71
  "debug": "^4.4.3",
73
72
  "fastify": "^5.7.1",
@@ -1,212 +1,219 @@
1
- /**
2
- * CLI smoke test for the `nat` subcommand. Spawns a stub HTTP server playing
3
- * the role of cadre-host's management API and asserts the CLI:
4
- * - GETs /nat/status
5
- * - POSTs /nat/test
6
- * - PUTs /nat/ddns with the expected body
7
- * - PUTs /nat/settings
8
- * - Exits non-zero when the host is not reachable
9
- *
10
- * Relies on `yarn build` having produced dist/bin/host.js.
11
- */
12
-
13
- import { spawn } from 'node:child_process';
14
- import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
15
- import { existsSync } from 'node:fs';
16
- import { fileURLToPath } from 'node:url';
17
- import { dirname, resolve } from 'node:path';
18
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
19
-
20
- const here = dirname(fileURLToPath(import.meta.url));
21
- const binPath = resolve(here, '../../dist/bin/host.js');
22
-
23
- // These smoke tests cold-start the built CLI as a child process (Node boot +,
24
- // for some, an HTTP server). That comfortably exceeds vitest's 5s default under
25
- // heavy parallel load, so give the subprocess generous headroom.
26
- vi.setConfig({ testTimeout: 30000 });
27
-
28
- interface StubServer {
29
- server: Server;
30
- port: number;
31
- requests: Array<{ method: string; url: string; body: string }>;
32
- setResponse(handler: (req: IncomingMessage, res: ServerResponse) => void): void;
33
- }
34
-
35
- const DEFAULT_STATUS = {
36
- portMode: 'auto-upnp',
37
- externalPort: 4001,
38
- internalPort: 4001,
39
- routerExternalIp: '203.0.113.5',
40
- mappingLeaseExpiresAt: null,
41
- externalIp: '203.0.113.5',
42
- externalIpDetectedAt: new Date().toISOString(),
43
- cgnatDetected: false,
44
- directReachability: 'reachable',
45
- lastTestedAt: null,
46
- ddns: {
47
- providerId: 'duckdns',
48
- hostname: 'foo.duckdns.org',
49
- externallyManaged: false,
50
- lastUpdateAt: null,
51
- lastUpdateOk: null,
52
- lastError: null,
53
- },
54
- };
55
-
56
- async function startStub(): Promise<StubServer> {
57
- const requests: StubServer['requests'] = [];
58
- let handler: (req: IncomingMessage, res: ServerResponse) => void = (_req, res) => {
59
- res.statusCode = 200;
60
- res.setHeader('content-type', 'application/json');
61
- res.end(JSON.stringify(DEFAULT_STATUS));
62
- };
63
-
64
- const server = createServer((req, res) => {
65
- const chunks: Buffer[] = [];
66
- req.on('data', (chunk: Buffer) => chunks.push(chunk));
67
- req.on('end', () => {
68
- requests.push({ method: req.method ?? '', url: req.url ?? '', body: Buffer.concat(chunks).toString('utf8') });
69
- handler(req, res);
70
- });
71
- });
72
-
73
- await new Promise<void>((resolveStart) => server.listen(0, '127.0.0.1', () => resolveStart()));
74
- const addr = server.address();
75
- const port = typeof addr === 'object' && addr ? addr.port : 0;
76
- return {
77
- server,
78
- port,
79
- requests,
80
- setResponse: (h) => { handler = h; },
81
- };
82
- }
83
-
84
- function runCli(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
85
- return new Promise((resolveRun) => {
86
- const child = spawn(process.execPath, [binPath, ...args], { stdio: ['ignore', 'pipe', 'pipe'] });
87
- let stdout = '';
88
- let stderr = '';
89
- child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8'); });
90
- child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8'); });
91
- child.on('close', (code) => resolveRun({ code: code ?? -1, stdout, stderr }));
92
- });
93
- }
94
-
95
- let stub: StubServer;
96
-
97
- beforeEach(async () => {
98
- if (!existsSync(binPath)) {
99
- throw new Error(`cadre-host bin not built at ${binPath}; run \`yarn build\` first`);
100
- }
101
- stub = await startStub();
102
- });
103
-
104
- afterEach(async () => {
105
- await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
106
- });
107
-
108
- describe('cadre-host nat status', () => {
109
- it('GETs /nat/status and renders the snapshot', async () => {
110
- const result = await runCli(['nat', 'status', '--port', String(stub.port)]);
111
- expect(result.code).toBe(0);
112
- expect(stub.requests).toHaveLength(1);
113
- expect(stub.requests[0]!.method).toBe('GET');
114
- expect(stub.requests[0]!.url).toBe('/nat/status');
115
- expect(result.stdout).toContain('auto-upnp');
116
- expect(result.stdout).toContain('203.0.113.5');
117
- expect(result.stdout).toContain('foo.duckdns.org');
118
- });
119
-
120
- it('--json prints raw JSON', async () => {
121
- const result = await runCli(['nat', 'status', '--port', String(stub.port), '--json']);
122
- expect(result.code).toBe(0);
123
- const parsed = JSON.parse(result.stdout);
124
- expect(parsed.portMode).toBe('auto-upnp');
125
- });
126
-
127
- it('exits 2 when cadre-host is unreachable', async () => {
128
- const port = stub.port;
129
- await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
130
- const result = await runCli(['nat', 'status', '--port', String(port)]);
131
- expect(result.code).toBe(2);
132
- expect(result.stderr).toMatch(/cadre-host start|Failed to reach/);
133
- });
134
- });
135
-
136
- describe('cadre-host nat test', () => {
137
- it('POSTs /nat/test', async () => {
138
- const result = await runCli(['nat', 'test', '--port', String(stub.port)]);
139
- expect(result.code).toBe(0);
140
- expect(stub.requests[0]!.method).toBe('POST');
141
- expect(stub.requests[0]!.url).toBe('/nat/test');
142
- });
143
- });
144
-
145
- describe('cadre-host nat ddns set', () => {
146
- it('PUTs /nat/ddns with provider + hostname + token', async () => {
147
- const result = await runCli([
148
- 'nat', 'ddns', 'set', 'duckdns',
149
- '--hostname', 'foo.duckdns.org',
150
- '--token', 'TOK',
151
- '--port', String(stub.port),
152
- ]);
153
- expect(result.code).toBe(0);
154
- expect(stub.requests[0]!.method).toBe('PUT');
155
- expect(stub.requests[0]!.url).toBe('/nat/ddns');
156
- const body = JSON.parse(stub.requests[0]!.body);
157
- expect(body.providerId).toBe('duckdns');
158
- expect(body.hostname).toBe('foo.duckdns.org');
159
- expect(body.config).toEqual({ token: 'TOK' });
160
- });
161
-
162
- it('exits 1 when --hostname is missing', async () => {
163
- const result = await runCli([
164
- 'nat', 'ddns', 'set', 'duckdns',
165
- '--token', 'TOK',
166
- '--port', String(stub.port),
167
- ]);
168
- expect(result.code).toBe(1);
169
- expect(result.stderr).toMatch(/--hostname/);
170
- });
171
-
172
- it('exits 1 when --token is missing on a non-TTY stdin', async () => {
173
- const result = await runCli([
174
- 'nat', 'ddns', 'set', 'duckdns',
175
- '--hostname', 'foo.duckdns.org',
176
- '--port', String(stub.port),
177
- ]);
178
- expect(result.code).toBe(1);
179
- expect(result.stderr).toMatch(/--token/);
180
- });
181
- });
182
-
183
- describe('cadre-host nat ddns external', () => {
184
- it('PUTs /nat/settings with externallyManaged=true', async () => {
185
- const result = await runCli([
186
- 'nat', 'ddns', 'external',
187
- '--hostname', 'mydomain.example.com',
188
- '--port', String(stub.port),
189
- ]);
190
- expect(result.code).toBe(0);
191
- expect(stub.requests[0]!.method).toBe('PUT');
192
- expect(stub.requests[0]!.url).toBe('/nat/settings');
193
- const body = JSON.parse(stub.requests[0]!.body);
194
- expect(body.ddns.hostname).toBe('mydomain.example.com');
195
- expect(body.ddns.externallyManaged).toBe(true);
196
- });
197
- });
198
-
199
- describe('cadre-host nat settings', () => {
200
- it('PUTs /nat/settings with --external-port and --no-upnp', async () => {
201
- const result = await runCli([
202
- 'nat', 'settings',
203
- '--external-port', '4002',
204
- '--no-upnp',
205
- '--port', String(stub.port),
206
- ]);
207
- expect(result.code).toBe(0);
208
- const body = JSON.parse(stub.requests[0]!.body);
209
- expect(body.externalPort).toBe(4002);
210
- expect(body.upnpEnabled).toBe(false);
211
- });
212
- });
1
+ /**
2
+ * CLI smoke test for the `nat` subcommand. Spawns a stub HTTP server playing
3
+ * the role of cadre-host's management API and asserts the CLI:
4
+ * - GETs /nat/status
5
+ * - POSTs /nat/test
6
+ * - PUTs /nat/ddns with the expected body
7
+ * - PUTs /nat/settings
8
+ * - Exits non-zero when the host is not reachable
9
+ *
10
+ * Relies on `yarn build` having produced dist/bin/host.js.
11
+ */
12
+
13
+ import { spawn } from 'node:child_process';
14
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';
15
+ import { existsSync } from 'node:fs';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { dirname, resolve } from 'node:path';
18
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
19
+
20
+ const here = dirname(fileURLToPath(import.meta.url));
21
+ const binPath = resolve(here, '../../dist/bin/host.js');
22
+
23
+ // These smoke tests cold-start the built CLI as a child process (Node boot +,
24
+ // for some, an HTTP server). That comfortably exceeds vitest's 5s default under
25
+ // heavy parallel load, so give the subprocess generous headroom.
26
+ vi.setConfig({ testTimeout: 30000 });
27
+
28
+ interface StubServer {
29
+ server: Server;
30
+ port: number;
31
+ requests: Array<{ method: string; url: string; body: string }>;
32
+ setResponse(handler: (req: IncomingMessage, res: ServerResponse) => void): void;
33
+ }
34
+
35
+ const DEFAULT_STATUS = {
36
+ upnpEnabled: true,
37
+ gateway: { found: true, lanAddress: '192.168.1.20', routerExternalIp: '203.0.113.5', lastError: null },
38
+ externalIp: '203.0.113.5',
39
+ externalIpDetectedAt: new Date().toISOString(),
40
+ cgnatDetected: false,
41
+ directReachability: 'reachable',
42
+ lastTestedAt: null,
43
+ nodes: [
44
+ {
45
+ nodeId: 'owner',
46
+ running: true,
47
+ verdict: 'mapped',
48
+ reason: null,
49
+ tcp: { internalPort: 4001, externalPort: 4001, source: 'upnp', leaseExpiresAt: null, error: null },
50
+ ws: { internalPort: 10001, externalPort: 10001, source: 'upnp', leaseExpiresAt: null, error: null },
51
+ publicAddrs: ['/ip4/203.0.113.5/tcp/4001', '/ip4/203.0.113.5/tcp/10001/ws'],
52
+ },
53
+ ],
54
+ ddns: {
55
+ providerId: 'duckdns',
56
+ hostname: 'foo.duckdns.org',
57
+ externallyManaged: false,
58
+ lastUpdateAt: null,
59
+ lastUpdateOk: null,
60
+ lastError: null,
61
+ },
62
+ };
63
+
64
+ async function startStub(): Promise<StubServer> {
65
+ const requests: StubServer['requests'] = [];
66
+ let handler: (req: IncomingMessage, res: ServerResponse) => void = (_req, res) => {
67
+ res.statusCode = 200;
68
+ res.setHeader('content-type', 'application/json');
69
+ res.end(JSON.stringify(DEFAULT_STATUS));
70
+ };
71
+
72
+ const server = createServer((req, res) => {
73
+ const chunks: Buffer[] = [];
74
+ req.on('data', (chunk: Buffer) => chunks.push(chunk));
75
+ req.on('end', () => {
76
+ requests.push({ method: req.method ?? '', url: req.url ?? '', body: Buffer.concat(chunks).toString('utf8') });
77
+ handler(req, res);
78
+ });
79
+ });
80
+
81
+ await new Promise<void>((resolveStart) => server.listen(0, '127.0.0.1', () => resolveStart()));
82
+ const addr = server.address();
83
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
84
+ return {
85
+ server,
86
+ port,
87
+ requests,
88
+ setResponse: (h) => { handler = h; },
89
+ };
90
+ }
91
+
92
+ function runCli(args: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
93
+ return new Promise((resolveRun) => {
94
+ const child = spawn(process.execPath, [binPath, ...args], { stdio: ['ignore', 'pipe', 'pipe'] });
95
+ let stdout = '';
96
+ let stderr = '';
97
+ child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString('utf8'); });
98
+ child.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString('utf8'); });
99
+ child.on('close', (code) => resolveRun({ code: code ?? -1, stdout, stderr }));
100
+ });
101
+ }
102
+
103
+ let stub: StubServer;
104
+
105
+ beforeEach(async () => {
106
+ if (!existsSync(binPath)) {
107
+ throw new Error(`cadre-host bin not built at ${binPath}; run \`yarn build\` first`);
108
+ }
109
+ stub = await startStub();
110
+ });
111
+
112
+ afterEach(async () => {
113
+ await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
114
+ });
115
+
116
+ describe('cadre-host nat status', () => {
117
+ it('GETs /nat/status and renders the snapshot', async () => {
118
+ const result = await runCli(['nat', 'status', '--port', String(stub.port)]);
119
+ expect(result.code).toBe(0);
120
+ expect(stub.requests).toHaveLength(1);
121
+ expect(stub.requests[0]!.method).toBe('GET');
122
+ expect(stub.requests[0]!.url).toBe('/nat/status');
123
+ expect(result.stdout).toContain('reachable');
124
+ expect(result.stdout).toContain('203.0.113.5');
125
+ expect(result.stdout).toContain('owner: mapped');
126
+ expect(result.stdout).toContain('foo.duckdns.org');
127
+ });
128
+
129
+ it('--json prints raw JSON', async () => {
130
+ const result = await runCli(['nat', 'status', '--port', String(stub.port), '--json']);
131
+ expect(result.code).toBe(0);
132
+ const parsed = JSON.parse(result.stdout);
133
+ expect(parsed.directReachability).toBe('reachable');
134
+ });
135
+
136
+ it('exits 2 when cadre-host is unreachable', async () => {
137
+ const port = stub.port;
138
+ await new Promise<void>((resolveClose) => stub.server.close(() => resolveClose()));
139
+ const result = await runCli(['nat', 'status', '--port', String(port)]);
140
+ expect(result.code).toBe(2);
141
+ expect(result.stderr).toMatch(/cadre-host start|Failed to reach/);
142
+ });
143
+ });
144
+
145
+ describe('cadre-host nat test', () => {
146
+ it('POSTs /nat/test', async () => {
147
+ const result = await runCli(['nat', 'test', '--port', String(stub.port)]);
148
+ expect(result.code).toBe(0);
149
+ expect(stub.requests[0]!.method).toBe('POST');
150
+ expect(stub.requests[0]!.url).toBe('/nat/test');
151
+ });
152
+ });
153
+
154
+ describe('cadre-host nat ddns set', () => {
155
+ it('PUTs /nat/ddns with provider + hostname + token', async () => {
156
+ const result = await runCli([
157
+ 'nat', 'ddns', 'set', 'duckdns',
158
+ '--hostname', 'foo.duckdns.org',
159
+ '--token', 'TOK',
160
+ '--port', String(stub.port),
161
+ ]);
162
+ expect(result.code).toBe(0);
163
+ expect(stub.requests[0]!.method).toBe('PUT');
164
+ expect(stub.requests[0]!.url).toBe('/nat/ddns');
165
+ const body = JSON.parse(stub.requests[0]!.body);
166
+ expect(body.providerId).toBe('duckdns');
167
+ expect(body.hostname).toBe('foo.duckdns.org');
168
+ expect(body.config).toEqual({ token: 'TOK' });
169
+ });
170
+
171
+ it('exits 1 when --hostname is missing', async () => {
172
+ const result = await runCli([
173
+ 'nat', 'ddns', 'set', 'duckdns',
174
+ '--token', 'TOK',
175
+ '--port', String(stub.port),
176
+ ]);
177
+ expect(result.code).toBe(1);
178
+ expect(result.stderr).toMatch(/--hostname/);
179
+ });
180
+
181
+ it('exits 1 when --token is missing on a non-TTY stdin', async () => {
182
+ const result = await runCli([
183
+ 'nat', 'ddns', 'set', 'duckdns',
184
+ '--hostname', 'foo.duckdns.org',
185
+ '--port', String(stub.port),
186
+ ]);
187
+ expect(result.code).toBe(1);
188
+ expect(result.stderr).toMatch(/--token/);
189
+ });
190
+ });
191
+
192
+ describe('cadre-host nat ddns external', () => {
193
+ it('PUTs /nat/settings with externallyManaged=true', async () => {
194
+ const result = await runCli([
195
+ 'nat', 'ddns', 'external',
196
+ '--hostname', 'mydomain.example.com',
197
+ '--port', String(stub.port),
198
+ ]);
199
+ expect(result.code).toBe(0);
200
+ expect(stub.requests[0]!.method).toBe('PUT');
201
+ expect(stub.requests[0]!.url).toBe('/nat/settings');
202
+ const body = JSON.parse(stub.requests[0]!.body);
203
+ expect(body.ddns.hostname).toBe('mydomain.example.com');
204
+ expect(body.ddns.externallyManaged).toBe(true);
205
+ });
206
+ });
207
+
208
+ describe('cadre-host nat settings', () => {
209
+ it('PUTs /nat/settings with --no-upnp', async () => {
210
+ const result = await runCli([
211
+ 'nat', 'settings',
212
+ '--no-upnp',
213
+ '--port', String(stub.port),
214
+ ]);
215
+ expect(result.code).toBe(0);
216
+ expect(stub.requests[0]!.url).toBe('/nat/settings');
217
+ expect(JSON.parse(stub.requests[0]!.body)).toEqual({ upnpEnabled: false });
218
+ });
219
+ });
@@ -28,7 +28,7 @@ describe('cadre-host CLI smoke test', () => {
28
28
  });
29
29
 
30
30
  expect(stdout.length).toBeGreaterThan(0);
31
- for (const cmd of ['install', 'start', 'status', 'invite', 'uninstall']) {
31
+ for (const cmd of ['install', 'start', 'status', 'uninstall']) {
32
32
  expect(stdout).toContain(cmd);
33
33
  }
34
34
  });
@@ -2,9 +2,8 @@
2
2
  * Vitest global setup — runs once before the whole cadre-host suite.
3
3
  *
4
4
  * `src/update/manifest.ts` and `src/push/index.ts` import real symbols from
5
- * `@serfab/cadre-core` (`canonicalJson`, `validatePushCredentials`), and
6
- * `trust-circle-integration.test.ts` imports and drives a real `CadreNode` from
7
- * it too. `@serfab/cadre-core`'s compiled entry point in turn imports
5
+ * `@serfab/cadre-core` (`canonicalJson`, `validatePushCredentials`).
6
+ * `@serfab/cadre-core`'s compiled entry point in turn imports
8
7
  * `@serfab/quereus-plugin-sereus`, which imports the `@optimystic/*` and
9
8
  * `@quereus/quereus` packages linked in from their sibling checkouts.
10
9
  * `host-process-orchestrator.ts` also resolves `@serfab/cadre-cli`'s bin path at