@songsid/agend 2.1.5 → 2.1.6-beta.2

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 (102) hide show
  1. package/dist/apply-job.d.ts +114 -0
  2. package/dist/apply-job.js +214 -0
  3. package/dist/apply-job.js.map +1 -0
  4. package/dist/backend/codex.d.ts +16 -0
  5. package/dist/backend/codex.js +50 -0
  6. package/dist/backend/codex.js.map +1 -1
  7. package/dist/backend/credential-profile.d.ts +114 -0
  8. package/dist/backend/credential-profile.js +242 -0
  9. package/dist/backend/credential-profile.js.map +1 -0
  10. package/dist/backend/kiro-auth-store.d.ts +25 -0
  11. package/dist/backend/kiro-auth-store.js +61 -0
  12. package/dist/backend/kiro-auth-store.js.map +1 -0
  13. package/dist/backend/kiro.d.ts +8 -0
  14. package/dist/backend/kiro.js +31 -2
  15. package/dist/backend/kiro.js.map +1 -1
  16. package/dist/backend/types.d.ts +13 -0
  17. package/dist/backend/types.js.map +1 -1
  18. package/dist/cli.js +113 -0
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config-validator.js +21 -0
  21. package/dist/config-validator.js.map +1 -1
  22. package/dist/daemon.d.ts +25 -0
  23. package/dist/daemon.js +157 -16
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/fleet-level-config.d.ts +42 -0
  26. package/dist/fleet-level-config.js +78 -0
  27. package/dist/fleet-level-config.js.map +1 -0
  28. package/dist/fleet-lock.d.ts +21 -0
  29. package/dist/fleet-lock.js +42 -8
  30. package/dist/fleet-lock.js.map +1 -1
  31. package/dist/fleet-manager.d.ts +130 -1
  32. package/dist/fleet-manager.js +611 -83
  33. package/dist/fleet-manager.js.map +1 -1
  34. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +121 -0
  35. package/dist/instance-config-impact.d.ts +55 -0
  36. package/dist/instance-config-impact.js +152 -0
  37. package/dist/instance-config-impact.js.map +1 -0
  38. package/dist/locale.js +2 -0
  39. package/dist/locale.js.map +1 -1
  40. package/dist/outbound-handlers.d.ts +5 -1
  41. package/dist/outbound-handlers.js +179 -1
  42. package/dist/outbound-handlers.js.map +1 -1
  43. package/dist/outbound-schemas.d.ts +6 -5
  44. package/dist/outbound-schemas.js +1 -0
  45. package/dist/outbound-schemas.js.map +1 -1
  46. package/dist/provider-probe.d.ts +45 -0
  47. package/dist/provider-probe.js +111 -0
  48. package/dist/provider-probe.js.map +1 -0
  49. package/dist/quickstart-api.d.ts +161 -0
  50. package/dist/quickstart-api.js +340 -0
  51. package/dist/quickstart-api.js.map +1 -0
  52. package/dist/quickstart.js +23 -50
  53. package/dist/quickstart.js.map +1 -1
  54. package/dist/self-restart-limit.d.ts +21 -0
  55. package/dist/self-restart-limit.js +129 -0
  56. package/dist/self-restart-limit.js.map +1 -0
  57. package/dist/settings-api.d.ts +15 -0
  58. package/dist/settings-api.js +126 -1
  59. package/dist/settings-api.js.map +1 -1
  60. package/dist/setup-auth.d.ts +113 -0
  61. package/dist/setup-auth.js +181 -0
  62. package/dist/setup-auth.js.map +1 -0
  63. package/dist/setup-form.d.ts +18 -0
  64. package/dist/setup-form.js +315 -0
  65. package/dist/setup-form.js.map +1 -0
  66. package/dist/setup-host.d.ts +162 -0
  67. package/dist/setup-host.js +496 -0
  68. package/dist/setup-host.js.map +1 -0
  69. package/dist/setup-marker.d.ts +8 -0
  70. package/dist/setup-marker.js +39 -0
  71. package/dist/setup-marker.js.map +1 -0
  72. package/dist/setup-tunnel-consent.d.ts +53 -0
  73. package/dist/setup-tunnel-consent.js +82 -0
  74. package/dist/setup-tunnel-consent.js.map +1 -0
  75. package/dist/tips.js +1 -1
  76. package/dist/tips.js.map +1 -1
  77. package/dist/transcript-sources.d.ts +12 -1
  78. package/dist/transcript-sources.js +19 -3
  79. package/dist/transcript-sources.js.map +1 -1
  80. package/dist/tunnel/cloudflared.d.ts +71 -0
  81. package/dist/tunnel/cloudflared.js +447 -0
  82. package/dist/tunnel/cloudflared.js.map +1 -0
  83. package/dist/tunnel/lease.d.ts +75 -0
  84. package/dist/tunnel/lease.js +209 -0
  85. package/dist/tunnel/lease.js.map +1 -0
  86. package/dist/tunnel/manager.d.ts +56 -0
  87. package/dist/tunnel/manager.js +167 -0
  88. package/dist/tunnel/manager.js.map +1 -0
  89. package/dist/tunnel/types.d.ts +119 -0
  90. package/dist/tunnel/types.js +33 -0
  91. package/dist/tunnel/types.js.map +1 -0
  92. package/dist/ui/dashboard.html +4 -3
  93. package/dist/ui/settings.html +640 -153
  94. package/dist/usage/providers.d.ts +16 -13
  95. package/dist/usage/providers.js +79 -53
  96. package/dist/usage/providers.js.map +1 -1
  97. package/dist/web-api.js +6 -4
  98. package/dist/web-api.js.map +1 -1
  99. package/dist/web-auth.d.ts +75 -0
  100. package/dist/web-auth.js +208 -2
  101. package/dist/web-auth.js.map +1 -1
  102. package/package.json +1 -1
@@ -0,0 +1,496 @@
1
+ /**
2
+ * The form that runs before there is a fleet.
3
+ *
4
+ * This process exists only to collect what `agend quickstart` collects, write
5
+ * it, and hand the port to the real fleet. Three properties matter more than
6
+ * anything it does:
7
+ *
8
+ * - **It cannot reach the fleet.** Its imports stop at the config and the
9
+ * provider probes; `tests/prefleet-host.test.ts` asserts the graph never
10
+ * reaches fleet-manager, daemon or instance-lifecycle. A pre-fleet surface
11
+ * that could start or stop agents would be a much larger thing to defend.
12
+ * - **It lets go of the port before the fleet is started.** The fleet's
13
+ * health-port takeover is not a safety net: on a busy port it signals
14
+ * whatever fleet.pid names and then disables the dashboard for the rest of
15
+ * its life. So the listener is closed — including its open connections —
16
+ * and only then is the fleet spawned.
17
+ * - **It goes away on its own.** A setup form left open is an unauthenticated-
18
+ * by-default surface waiting for someone to find it, so it exits on its TTL
19
+ * and on idleness as readily as on success.
20
+ *
21
+ * Hardened ahead of being reachable from outside (see
22
+ * `docs/design/setup-host-tunnel.zh-TW.md`): the page lives under a random sid
23
+ * so a scanner cannot find it, the credential is a short code typed into the
24
+ * page rather than anything in the URL, the cookie is derived from a freshly
25
+ * minted 256-bit secret rather than from that code, and every presented-and-
26
+ * wrong credential spends one of five shared attempts.
27
+ */
28
+ import { createServer } from "node:http";
29
+ import { spawn } from "node:child_process";
30
+ import { existsSync } from "node:fs";
31
+ import { dirname, join } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ import { loadFleetConfig } from "./config.js";
34
+ import { acquireFleetLock, releaseFleetLock } from "./fleet-lock.js";
35
+ import { handleQuickstartRequest } from "./quickstart-api.js";
36
+ import { markSetupComplete } from "./setup-marker.js";
37
+ import { isSameOriginRequest, parseCookieHeader } from "./web-auth.js";
38
+ import { buildSetupCookie, SetupCredentials, SETUP_COOKIE_NAME, } from "./setup-auth.js";
39
+ import { SETUP_CODE_PAGE_HTML, SETUP_FORM_HTML } from "./setup-form.js";
40
+ import { ManagedTunnel } from "./tunnel/manager.js";
41
+ import { leasePath } from "./tunnel/lease.js";
42
+ import { CloudflaredProvider } from "./tunnel/cloudflared.js";
43
+ import yaml from "js-yaml";
44
+ import { writeFileSync } from "node:fs";
45
+ /** How long the form may stay open at all. */
46
+ export const SETUP_HOST_TTL_MS = 15 * 60_000;
47
+ /**
48
+ * …and how long it may stay open with nobody touching it.
49
+ *
50
+ * Strictly shorter than the TTL, which it was not: both were fifteen minutes,
51
+ * so the idle timer could never fire first and the page always stayed open for
52
+ * the full quarter hour whether anyone was using it or not.
53
+ */
54
+ export const SETUP_HOST_IDLE_MS = 10 * 60_000;
55
+ /**
56
+ * The port a setup host may bind.
57
+ *
58
+ * In tunnel mode it is always zero. Not "unless `--port` was given", and not
59
+ * "unless fleet.yaml names a health port" — those are the two ways a fixed
60
+ * port creeps back behind a tunnel, and the second one needs no flag at all.
61
+ * Whatever the fleet would later bind must never be the thing the tunnel is
62
+ * pointed at.
63
+ */
64
+ export function resolvePort(opts) {
65
+ if (opts.tunnel)
66
+ return 0;
67
+ return opts.port ?? opts.healthPort ?? 19280;
68
+ }
69
+ function defaultSpawnFleet() {
70
+ const cliEntry = join(dirname(fileURLToPath(import.meta.url)), "cli.js");
71
+ const child = spawn(process.execPath, [cliEntry, "start"], { detached: true, stdio: "ignore" });
72
+ child.unref();
73
+ }
74
+ export class SetupHost {
75
+ opts;
76
+ server = null;
77
+ lock;
78
+ ttlTimer = null;
79
+ idleTimer = null;
80
+ stopping = false;
81
+ credentials;
82
+ /** Where this page lives. Random, and not a credential — see setup-auth.ts. */
83
+ sid;
84
+ /** What the person types. Printed by the CLI; never put in a URL. */
85
+ code;
86
+ constructor(opts) {
87
+ this.opts = opts;
88
+ this.credentials = opts.credentials ?? new SetupCredentials();
89
+ this.sid = this.credentials.sid;
90
+ this.code = this.credentials.code;
91
+ }
92
+ /** The page's own path. Everything outside it is not this page. */
93
+ get basePath() { return `/s/${this.sid}/`; }
94
+ managedTunnel = null;
95
+ tunnelHandle = null;
96
+ /** The exact host the tunnel answers on; nothing else is added to the set. */
97
+ externalHost = null;
98
+ tunnelStopResult = "none";
99
+ /**
100
+ * Set when the wizard asked to hand over, so a shutdown that started for
101
+ * another reason still completes it.
102
+ *
103
+ * Without this the tunnel dying in the moment between "finish" answering and
104
+ * the handover beginning would take the `stopping` flag first, and the
105
+ * handover would silently return: configuration written, page saying
106
+ * "starting", and no fleet.
107
+ */
108
+ handoverRequested = false;
109
+ get ttlMs() { return this.opts.ttlMs ?? SETUP_HOST_TTL_MS; }
110
+ get idleMs() { return this.opts.idleMs ?? SETUP_HOST_IDLE_MS; }
111
+ log(message) { (this.opts.log ?? console.log)(message); }
112
+ /** The config the wizard edits: a file if there is one, otherwise a blank. */
113
+ readConfig() {
114
+ try {
115
+ return loadFleetConfig(this.opts.configPath);
116
+ }
117
+ catch {
118
+ return { defaults: {}, instances: {} };
119
+ }
120
+ }
121
+ context() {
122
+ const config = this.readConfig();
123
+ return {
124
+ fleetConfig: config,
125
+ dataDir: this.opts.dataDir,
126
+ logger: { info: () => { }, warn: () => { } },
127
+ // Nothing to preserve: before a fleet there are no comments and no hand
128
+ // edits to keep, so a plain dump is the whole writer.
129
+ saveFleetConfig: () => {
130
+ writeFileSync(this.opts.configPath, yaml.dump(config, { quotingType: '"', forceQuotes: false }), { mode: 0o600 });
131
+ },
132
+ // No fleet, so nothing is polling any bot token.
133
+ };
134
+ }
135
+ async start() {
136
+ // This host writes fleet.yaml by dumping the loader's output, which expands
137
+ // every default and drops every comment. That is fine for a file it is
138
+ // creating and destructive to one somebody already has — and an existing
139
+ // installation should be using the panel's wizard anyway, which edits the
140
+ // document in place.
141
+ const existing = this.readConfig();
142
+ if (Object.keys(existing.instances ?? {}).length > 0) {
143
+ throw new Error("This installation already has agents configured. Open Settings in the dashboard and use the setup wizard there — it edits your fleet.yaml in place.");
144
+ }
145
+ // Refuses while a fleet runs, and — since the record carries a role — is
146
+ // equally refused to a fleet that starts while this host is up.
147
+ this.lock = acquireFleetLock(this.opts.dataDir, { role: "setup-host", ...this.opts.lockProbe });
148
+ const server = createServer((req, res) => this.handle(req, res));
149
+ this.server = server;
150
+ // Zero in tunnel mode, and the caller cannot override it: `resolvePort` is
151
+ // the only thing that decides, so a flag or a config value cannot put a
152
+ // fixed port behind a tunnel.
153
+ const bindPort = this.opts.tunnel ? 0 : this.opts.port;
154
+ await new Promise((resolve, reject) => {
155
+ server.once("error", reject);
156
+ server.listen(bindPort, "127.0.0.1", () => { server.removeListener("error", reject); resolve(); });
157
+ });
158
+ this.ttlTimer = setTimeout(() => { void this.shutdown(false, "ttl"); }, this.ttlMs);
159
+ this.ttlTimer.unref?.();
160
+ this.boundPort = server.address().port;
161
+ this.touch();
162
+ const publicUrl = this.opts.tunnel ? await this.openTunnel() : null;
163
+ return { port: this.boundPort, sid: this.sid, code: this.code, path: this.basePath, publicUrl };
164
+ }
165
+ /**
166
+ * Put the page behind a tunnel, or say why it is not.
167
+ *
168
+ * Three outcomes, and the third is the one that matters: a start that failed
169
+ * while leaving a process it could not account for is not a fallback, it is a
170
+ * stop. Carrying on would mean serving this page on loopback while something
171
+ * we cannot see may still be exposing it.
172
+ */
173
+ async openTunnel() {
174
+ const managed = new ManagedTunnel({ dataDir: this.opts.dataDir, log: m => this.log(m) });
175
+ this.managedTunnel = managed;
176
+ const provider = this.opts.tunnelProvider ?? new CloudflaredProvider();
177
+ const result = await managed.start(provider, {
178
+ sid: this.sid,
179
+ origin: new URL(`http://127.0.0.1:${this.boundPort}`),
180
+ pagePath: this.basePath,
181
+ readinessMarker: this.credentials.readinessMarker,
182
+ expiresAt: (this.opts.now ?? Date.now)() + this.ttlMs,
183
+ signal: new AbortController().signal,
184
+ // The provider's own readiness probe arrives here under the public
185
+ // hostname, so it has to be on the list before that probe runs. Exactly
186
+ // the one host the validator just accepted — the alternative shortcuts,
187
+ // accepting any Host during startup or any `*.trycloudflare.com`, are
188
+ // each a hole that outlives the ten seconds they would save.
189
+ onCandidateHost: host => { this.externalHost = host; },
190
+ });
191
+ if (result.ok) {
192
+ this.tunnelHandle = result.handle;
193
+ this.externalHost = new URL(result.handle.baseUrl).host;
194
+ // The page is gone the moment the tunnel is, and a session whose
195
+ // hostname has changed underneath it is not a session to keep.
196
+ result.handle.onUnexpectedExit(() => {
197
+ this.log("The tunnel closed unexpectedly. Run `agend setup --tunnel` again for a new link.");
198
+ void this.shutdown(false, "tunnel died");
199
+ });
200
+ return result.handle.pageUrl;
201
+ }
202
+ // Whatever happens below, a host that never became a working tunnel does
203
+ // not stay on the list.
204
+ if (!result.ok)
205
+ this.externalHost = null;
206
+ if (result.leaseHeld) {
207
+ // Fail closed: a tunnel process we cannot account for may still be
208
+ // pointed at this listener, so there is no honest "falling back to
209
+ // localhost" to announce.
210
+ await this.shutdown(false, "tunnel could not be cleaned up");
211
+ throw new Error(`Could not open a tunnel, and could not confirm it was cleaned up: ${result.message}`);
212
+ }
213
+ this.log(`No public tunnel: ${result.message}`);
214
+ this.log("The link below only works on this machine — a phone will not be able to open it.");
215
+ return null;
216
+ }
217
+ boundPort = 0;
218
+ /**
219
+ * The hosts this listener will answer to.
220
+ *
221
+ * Origin==Host alone accepts any Host at all, which was harmless while the
222
+ * only way in was loopback. It stops being harmless the moment something in
223
+ * front of the listener can set it, so the set is named rather than inferred.
224
+ */
225
+ allowedHosts() {
226
+ const hosts = [`127.0.0.1:${this.boundPort}`, `localhost:${this.boundPort}`, `[::1]:${this.boundPort}`];
227
+ // Exactly the host this tunnel answers on, and only while it is ours. An
228
+ // ephemeral port can be handed to a later listener, and a tunnel nobody
229
+ // cleaned up would then be pointed at that one — this list is what makes
230
+ // the zombie's requests 403 instead of reaching a stranger's page.
231
+ if (this.externalHost)
232
+ hosts.push(this.externalHost);
233
+ return hosts;
234
+ }
235
+ /**
236
+ * Whether the browser reached this over TLS.
237
+ *
238
+ * Derived from what this process knows it is serving, never from
239
+ * `X-Forwarded-Proto`: that header is set by whatever is in front, including
240
+ * an attacker, and a cookie's Secure attribute is not something to take on
241
+ * trust from a request.
242
+ */
243
+ get endpointIsHttps() { return this.externalHost !== null; }
244
+ touch() {
245
+ if (this.idleTimer)
246
+ clearTimeout(this.idleTimer);
247
+ this.idleTimer = setTimeout(() => { void this.shutdown(false, "idle"); }, this.idleMs);
248
+ this.idleTimer.unref?.();
249
+ }
250
+ /**
251
+ * The answer for anything that is not this page.
252
+ *
253
+ * Byte-identical whatever the reason, because the difference between "wrong
254
+ * sid" and "no such path" is precisely how a sid gets found.
255
+ */
256
+ notFound(res) {
257
+ res.setHeader("Content-Type", "application/json");
258
+ res.writeHead(404);
259
+ res.end(JSON.stringify({ error: "not found" }));
260
+ }
261
+ deny(res, status, error) {
262
+ res.setHeader("Content-Type", "application/json");
263
+ res.writeHead(status);
264
+ res.end(JSON.stringify({ error }));
265
+ }
266
+ hostAllowed(req) {
267
+ const host = req.headers.host;
268
+ return typeof host === "string" && this.allowedHosts().includes(host);
269
+ }
270
+ presentedCookie(req) {
271
+ return parseCookieHeader(req.headers.cookie).get(SETUP_COOKIE_NAME);
272
+ }
273
+ /** Spend an attempt's worth of consequence: at zero the page is over. */
274
+ afterFailure(res, kind, attemptsLeft) {
275
+ if (kind === "locked") {
276
+ this.deny(res, 410, "too many failed attempts — run `agend setup` again on the host");
277
+ setTimeout(() => { void this.shutdown(false, "lockout"); }, 10);
278
+ return;
279
+ }
280
+ this.deny(res, 401, `incorrect — ${attemptsLeft} attempt${attemptsLeft === 1 ? "" : "s"} left`);
281
+ }
282
+ handle(req, res) {
283
+ res.setHeader("Referrer-Policy", "no-referrer");
284
+ res.setHeader("Vary", "Cookie");
285
+ // On every response, not just the redirect that used to carry it: a shared
286
+ // cache in front of this — which is what a tunnel edge is — must never keep
287
+ // a copy of a page or an answer belonging to one setup session.
288
+ res.setHeader("Cache-Control", "no-store");
289
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${this.boundPort}`);
290
+ // Everything below the sid, and nothing else. Checked before the idle timer
291
+ // is touched, or a scanner that cannot find the page could still keep it
292
+ // open forever by knocking on the wrong paths.
293
+ const segments = url.pathname.split("/");
294
+ if (segments[1] !== "s" || !this.credentials.matchesSid(segments[2] ?? "")) {
295
+ this.notFound(res);
296
+ return;
297
+ }
298
+ const rest = `/${segments.slice(3).join("/")}`;
299
+ if (!isSameOriginRequest(req) || !this.hostAllowed(req)) {
300
+ this.deny(res, 403, "cross-site request rejected");
301
+ return;
302
+ }
303
+ // The two unauthenticated endpoints: the page that asks for the code, and
304
+ // the exchange itself. Everything else needs the cookie.
305
+ if (req.method === "GET" && (rest === "/" || rest === "")) {
306
+ const signedIn = this.credentials.checkCookie(this.presentedCookie(req)).kind === "ok";
307
+ // Only a session that exists keeps the page alive. Reloading the code
308
+ // prompt is not use of the page, and treating it as such would let
309
+ // anyone holding the link hold the window open indefinitely.
310
+ if (signedIn)
311
+ this.touch();
312
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
313
+ res.writeHead(200);
314
+ // The wizard itself is only served to a session that has the cookie; an
315
+ // unauthenticated visitor gets the code prompt and nothing about this
316
+ // machine.
317
+ res.end(signedIn
318
+ ? SETUP_FORM_HTML
319
+ : SETUP_CODE_PAGE_HTML.replace("__AGEND_SETUP_MARKER__", this.credentials.readinessMarker));
320
+ return;
321
+ }
322
+ if (req.method === "POST" && rest === "/open") {
323
+ this.redeem(req, res);
324
+ return;
325
+ }
326
+ const verdict = this.credentials.checkCookie(this.presentedCookie(req));
327
+ if (verdict.kind === "locked") {
328
+ this.afterFailure(res, "locked", 0);
329
+ return;
330
+ }
331
+ if (verdict.kind === "rejected") {
332
+ this.afterFailure(res, "rejected", verdict.attemptsLeft);
333
+ return;
334
+ }
335
+ if (verdict.kind === "unauthenticated") {
336
+ this.deny(res, 401, "enter the setup code first");
337
+ return;
338
+ }
339
+ // Only now: an authorized request is the only kind that should be able to
340
+ // keep this page alive.
341
+ this.touch();
342
+ if (req.method === "GET" && rest === "/setup/status") {
343
+ res.setHeader("Content-Type", "application/json");
344
+ res.writeHead(200);
345
+ res.end(JSON.stringify({ pre_fleet: true, ttl_ms: this.ttlMs }));
346
+ return;
347
+ }
348
+ if (req.method === "POST" && rest === "/setup/finish") {
349
+ res.setHeader("Content-Type", "application/json");
350
+ res.writeHead(202);
351
+ // `watch` is false behind a tunnel: this page is about to stop existing
352
+ // along with the hostname it is served on, so polling would only produce
353
+ // a false alarm. The dashboard is not an answer either — it binds
354
+ // loopback, so a link to it would be one a phone cannot open.
355
+ res.end(JSON.stringify({ starting: true, watch: this.externalHost === null }));
356
+ // Recorded before the timer, not inside it: the tunnel can die in
357
+ // between, and whichever shutdown runs first has to know a handover was
358
+ // asked for.
359
+ this.handoverRequested = true;
360
+ // After the response, so the browser has its answer before the port goes.
361
+ setTimeout(() => { void this.shutdown(true, "finished"); }, 10);
362
+ return;
363
+ }
364
+ // The wizard API is mounted under the sid too, so its paths are rewritten
365
+ // rather than exposed at the root where a scanner could reach them.
366
+ const inner = new URL(rest + url.search, "http://127.0.0.1");
367
+ if (handleQuickstartRequest(req, res, inner, this.context()))
368
+ return;
369
+ this.notFound(res);
370
+ }
371
+ /** Exchange the typed code for a session cookie. The only way in. */
372
+ redeem(req, res) {
373
+ let body = "";
374
+ req.on("data", chunk => {
375
+ body += String(chunk);
376
+ if (body.length > 4096) {
377
+ req.destroy();
378
+ }
379
+ });
380
+ req.on("end", () => {
381
+ let provided = "";
382
+ try {
383
+ provided = String(JSON.parse(body || "{}").code ?? "");
384
+ }
385
+ catch { /* empty */ }
386
+ const verdict = this.credentials.redeem(provided);
387
+ if (verdict.kind === "ok" && verdict.secret) {
388
+ this.touch();
389
+ res.setHeader("Set-Cookie", buildSetupCookie(verdict.secret, {
390
+ path: this.basePath,
391
+ secure: this.endpointIsHttps,
392
+ maxAgeSeconds: Math.ceil(this.ttlMs / 1000),
393
+ }));
394
+ res.setHeader("Content-Type", "application/json");
395
+ res.writeHead(200);
396
+ res.end(JSON.stringify({ ok: true }));
397
+ return;
398
+ }
399
+ if (verdict.kind === "locked") {
400
+ this.afterFailure(res, "locked", 0);
401
+ return;
402
+ }
403
+ if (verdict.kind === "rejected") {
404
+ this.afterFailure(res, "rejected", verdict.attemptsLeft);
405
+ return;
406
+ }
407
+ this.deny(res, 400, "enter the setup code");
408
+ });
409
+ }
410
+ /**
411
+ * Let go of the port, then hand it over.
412
+ *
413
+ * `closeAllConnections()` first, and not as a nicety: a browser polling for
414
+ * the fleet holds a connection open, and `close()` waits for it. Measured, a
415
+ * single open poll stops the close from ever completing — so the form would
416
+ * hang at "starting…" and the fleet would never be spawned.
417
+ */
418
+ async shutdown(spawnSuccessor, reason) {
419
+ if (this.stopping)
420
+ return;
421
+ this.stopping = true;
422
+ // A handover asked for by the wizard survives a shutdown that began for a
423
+ // different reason.
424
+ const handOver = spawnSuccessor || this.handoverRequested;
425
+ if (this.ttlTimer)
426
+ clearTimeout(this.ttlTimer);
427
+ if (this.idleTimer)
428
+ clearTimeout(this.idleTimer);
429
+ // 1. Capability first. Anything still in flight — including a request
430
+ // arriving through a tunnel that is still up — is already unauthorized
431
+ // from here, so the order below can take its time without a window.
432
+ this.credentials.revoke();
433
+ // 2. The listener, and its open connections. From this point the public URL
434
+ // reaches nothing, whatever the tunnel is doing.
435
+ const server = this.server;
436
+ this.server = null;
437
+ if (server) {
438
+ server.closeAllConnections?.();
439
+ await new Promise(resolve => server.close(() => resolve()));
440
+ }
441
+ // 3. The tunnel, with proof.
442
+ if (this.managedTunnel && this.tunnelHandle) {
443
+ const stopped = await this.managedTunnel.stop(reason);
444
+ this.tunnelStopResult = stopped.confirmed ? "confirmed" : "unconfirmed";
445
+ if (!stopped.confirmed) {
446
+ this.log(`The tunnel could not be confirmed closed${stopped.pid !== null ? ` (pid ${stopped.pid})` : ""}: ${stopped.reason}. `
447
+ + "That URL may keep answering 'origin unavailable' for a while; nothing behind it is reachable. "
448
+ + `To clean up by hand, kill ${stopped.pid !== null ? `pid ${stopped.pid}` : "the cloudflared process"}. `
449
+ + "No new tunnel will be opened until this is resolved.");
450
+ }
451
+ }
452
+ releaseFleetLock(this.lock);
453
+ this.lock = undefined;
454
+ if (!handOver) {
455
+ this.log(`Setup host stopped (${reason}). Run \`agend setup\` again to reopen it.`);
456
+ return;
457
+ }
458
+ if (this.tunnelStopResult === "unconfirmed" && !this.mayHandOverWithUnconfirmedTunnel()) {
459
+ // Fail closed. The config is written; what is missing is a process this
460
+ // one is not willing to start while a tunnel it cannot account for could
461
+ // still be pointed at the port that process would bind.
462
+ this.log("Your configuration has been saved, but AgEnD was NOT started: a tunnel could not be confirmed closed "
463
+ + "and this host is not on an ephemeral port, so starting the fleet could put its dashboard behind that tunnel. "
464
+ + "Kill the cloudflared process, then run `agend start` on this machine.");
465
+ return;
466
+ }
467
+ markSetupComplete(this.opts.dataDir);
468
+ this.log("Setup complete — starting AgEnD.");
469
+ (this.opts.spawnFleet ?? defaultSpawnFleet)();
470
+ }
471
+ /**
472
+ * Whether an unconfirmed tunnel may be left behind while the fleet starts.
473
+ *
474
+ * Ruled on by fable, and conditional by construction. The reasoning only
475
+ * holds because a leftover tunnel points at an ephemeral port this process
476
+ * owned and nothing will deliberately bind again — so the damage of being
477
+ * wrong is a URL that answers "unavailable", not a public dashboard.
478
+ *
479
+ * **If anyone puts this host back on the health port, this ruling is void**
480
+ * and the path must return to refusing the handover. That is the first
481
+ * condition below rather than a note in a comment, precisely so the change
482
+ * that breaks the reasoning also flips the behaviour.
483
+ */
484
+ mayHandOverWithUnconfirmedTunnel() {
485
+ // Each of these reads the thing itself rather than a flag set alongside it.
486
+ // A boolean assigned on the same line that does the work is true whenever
487
+ // the line ran, which makes "all four conditions are checked" a sentence
488
+ // about nothing — remove the work and the flag goes with it.
489
+ const onEphemeralPort = this.opts.tunnel === true && this.opts.port === 0;
490
+ const listenerClosed = this.server === null;
491
+ const credentialsRevoked = this.credentials.revokedNow;
492
+ const leaseKept = existsSync(leasePath(this.opts.dataDir));
493
+ return onEphemeralPort && listenerClosed && credentialsRevoked && leaseKept;
494
+ }
495
+ }
496
+ //# sourceMappingURL=setup-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-host.js","sourceRoot":"","sources":["../src/setup-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface SetupRecord {
2
+ completedAt: string;
3
+ version?: string;
4
+ }
5
+ export declare function isSetupComplete(dataDir: string): boolean;
6
+ export declare function markSetupComplete(dataDir: string, version?: string): void;
7
+ /** Only `agend setup --reset` calls this; no route may. */
8
+ export declare function clearSetupComplete(dataDir: string): boolean;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * "Setup has been done once."
3
+ *
4
+ * Deliberately not fleet.yaml's existence: a user who deletes or moves
5
+ * fleet.yaml — or whose disk hands back an empty one — would otherwise reopen a
6
+ * pre-fleet setup host, which is an unauthenticated-by-default surface. The
7
+ * marker is its own file and only `agend setup --reset` clears it.
8
+ */
9
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ const FILE = "setup-complete.json";
12
+ function path(dataDir) {
13
+ return join(dataDir, FILE);
14
+ }
15
+ export function isSetupComplete(dataDir) {
16
+ try {
17
+ const parsed = JSON.parse(readFileSync(path(dataDir), "utf-8"));
18
+ return typeof parsed.completedAt === "string";
19
+ }
20
+ catch {
21
+ // Unreadable is not "not done". A corrupt marker must not reopen setup;
22
+ // `agend setup --reset` is the way back, and it is a local command.
23
+ return existsSync(path(dataDir));
24
+ }
25
+ }
26
+ export function markSetupComplete(dataDir, version) {
27
+ writeFileSync(path(dataDir), JSON.stringify({ completedAt: new Date().toISOString(), version }) + "\n", { mode: 0o600 });
28
+ }
29
+ /** Only `agend setup --reset` calls this; no route may. */
30
+ export function clearSetupComplete(dataDir) {
31
+ try {
32
+ unlinkSync(path(dataDir));
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ //# sourceMappingURL=setup-marker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-marker.js","sourceRoot":"","sources":["../src/setup-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,qBAAqB,CAAC;AAOnC,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAyB,CAAC;QACxF,OAAO,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,oEAAoE;QACpE,OAAO,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,OAAgB;IACjE,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAwB,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACjJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,CAAC;QAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,KAAK,CAAC;IAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The question asked before a setup page is put on the public internet.
3
+ *
4
+ * It is asked every single time. There is no configuration that turns it off
5
+ * and no flag that answers it in advance: `allow_public` defaults to true
6
+ * because opening from a phone is the product, not because the consequences
7
+ * stop mattering after the first time. And a pre-fleet host has no chat channel
8
+ * to put a confirmation button in, so the only place this can be asked is the
9
+ * terminal that ran the command.
10
+ *
11
+ * Which means a run with no terminal cannot be consented to, and is refused.
12
+ * Not "defaults to allow because nobody is there to object" — nobody being
13
+ * there is the reason to refuse.
14
+ *
15
+ * The wording is deliberately not sol's Web Terminal text. That one covers a
16
+ * terminal session that ends; this one carries a long-lived bot token and an
17
+ * admin id through somebody else's edge, which is a different thing to consent
18
+ * to and has to say so.
19
+ */
20
+ export interface ConsentIo {
21
+ readonly isTTY: boolean;
22
+ ask(question: string): Promise<string>;
23
+ say(line: string): void;
24
+ }
25
+ export type ConsentResult = {
26
+ readonly granted: true;
27
+ } | {
28
+ readonly granted: false;
29
+ readonly reason: "declined" | "no-tty";
30
+ };
31
+ /**
32
+ * Everything a person needs to decide, in the order they need it.
33
+ *
34
+ * Each line is here because leaving it out would make the answer uninformed:
35
+ * the token really does cross Cloudflare, the page really can be closed by a
36
+ * stranger, and the link really does work for anyone who sees it.
37
+ */
38
+ export declare function publicTunnelWarning(ttlMinutes: number): string[];
39
+ /**
40
+ * Ask, and treat anything that is not a clear yes as a no.
41
+ *
42
+ * Including the empty answer: a person who presses enter to get past a prompt
43
+ * has not read it, and this is not a prompt to get past.
44
+ */
45
+ export declare function confirmPublicTunnel(io: ConsentIo, ttlMinutes: number): Promise<ConsentResult>;
46
+ /**
47
+ * What the CLI adds when a tunnel was asked for and there is none.
48
+ *
49
+ * The host already says the link is local-only and a phone cannot open it —
50
+ * that is true wherever SetupHost is used. What only the CLI can usefully add
51
+ * is what to do about it, so it does not repeat the rest.
52
+ */
53
+ export declare function noTunnelBinaryMessage(): string[];