@netnodeag/kraftwerk 0.76.0 → 0.77.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 (45) hide show
  1. package/README.md +33 -27
  2. package/dist/cli/cloud.d.ts +2 -0
  3. package/dist/cli/cloud.js +262 -0
  4. package/dist/cli/daemon.js +9 -0
  5. package/dist/cli/doctor.js +16 -8
  6. package/dist/cli/init.js +2 -2
  7. package/dist/cli/kraftwerk.js +4 -0
  8. package/dist/client/routes.d.ts +4 -0
  9. package/dist/client/routes.js +4 -0
  10. package/dist/config.d.ts +15 -28
  11. package/dist/config.js +5 -19
  12. package/dist/core/cloud.d.ts +115 -0
  13. package/dist/core/cloud.js +94 -0
  14. package/dist/server/api/cloud.d.ts +72 -0
  15. package/dist/server/api/cloud.js +38 -0
  16. package/dist/server/api/devices.d.ts +8 -8
  17. package/dist/server/api/index.d.ts +88 -10
  18. package/dist/server/api/index.js +2 -0
  19. package/dist/server/api/workspace.d.ts +16 -1
  20. package/dist/server/api/workspace.js +2 -1
  21. package/dist/server/cloud.d.ts +65 -33
  22. package/dist/server/cloud.js +207 -188
  23. package/dist/server/hub-context.d.ts +1 -1
  24. package/dist/server/hub-context.js +3 -3
  25. package/dist/server/relay.d.ts +13 -0
  26. package/dist/server/relay.js +29 -1
  27. package/dist/server/server.d.ts +3 -2
  28. package/dist/server/server.js +6 -6
  29. package/package.json +1 -1
  30. package/schema/kraftwerk.schema.json +4 -4
  31. package/web/dist/assets/{dist-T1sNLC62.js → dist-BGHweZKi.js} +1 -1
  32. package/web/dist/assets/{dist-DFXmaDc5.js → dist-BVPxOqcx.js} +1 -1
  33. package/web/dist/assets/{dist-BYkcY-Vl.js → dist-CCNyRSAA.js} +1 -1
  34. package/web/dist/assets/{dist-flnLXEzU.js → dist-CXoAMU1b.js} +1 -1
  35. package/web/dist/assets/{dist-l7foWlFx.js → dist-DCFREMXk.js} +1 -1
  36. package/web/dist/assets/{dist-BQpWYkxm.js → dist-DT2Tjvwx.js} +1 -1
  37. package/web/dist/assets/{dist-BwIca_Uj.js → dist-DdA5PoVJ.js} +1 -1
  38. package/web/dist/assets/{dist-fgB7OEJy.js → dist-DoD3qX1P.js} +1 -1
  39. package/web/dist/assets/{dist-vuciRCSz.js → dist-FFA4-S6d.js} +1 -1
  40. package/web/dist/assets/{dist-BTVP-6On.js → dist-yGx0qYbw.js} +1 -1
  41. package/web/dist/assets/dist-zjUUP5a9.js +1 -0
  42. package/web/dist/assets/{editor-C6Ok8NPo.js → editor-CyWapLQr.js} +3 -3
  43. package/web/dist/assets/{index-z4tpCCzk.js → index-CNXcNoKJ.js} +4 -4
  44. package/web/dist/index.html +1 -1
  45. package/web/dist/assets/dist-BURf1l-A.js +0 -1
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The kraftwerk cloud, machine side: the settings and the wire contract the
3
+ * daemon's machine client (server/cloud.ts) and the CLI (`kraftwerk cloud`,
4
+ * `login`, `logout`) share.
5
+ *
6
+ * The daemon registers this MACHINE once; the workspaces it serves ride
7
+ * along as children of that record. The machine's id is its relay id (the
8
+ * hash of the X25519 key in ~/.kraftwerk/relay.json) and the proof is the
9
+ * relay secret — the key cannot sign. Nothing here opens the relay link.
10
+ *
11
+ * ~/.kraftwerk/cloud.json — the machine's switch and the cloud's origin:
12
+ *
13
+ * { "enabled": false, "url": "https://cloud.example.com" }
14
+ *
15
+ * `enabled` absent = on. The origin: KRAFTWERK_CLOUD_URL (env) wins, then
16
+ * cloud.json `url`, then a kraftwerk.yml `cloud.url` of an open workspace
17
+ * (deprecated, one release), then the default. KRAFTWERK_CLOUD_URL=off
18
+ * turns the cloud off for the process (what tests and CI set). kraftwerk.yml
19
+ * `cloud.enabled: false` is a per-workspace veto: that workspace is left out
20
+ * of the machine's reports.
21
+ *
22
+ * The contract (all POST, JSON):
23
+ *
24
+ * /api/machines/register {id, key, secret, name, handle, hostname?, platform?, version, interval?, workspaces}
25
+ * → {ok, interval, claimCode|null, account|null} (403: wrong secret)
26
+ * /api/machines/heartbeat {id, secret, version?, workspaces?} → {ok, interval, account, claimCode}
27
+ * /api/machines/goodbye {id, secret}
28
+ * /api/machines/unlink {id, secret} → {ok, claimCode}
29
+ *
30
+ * workspaces: [{id?: "ws_…", name, slug, root?}] — `root` only once the
31
+ * machine is claimed (account set): an unclaimed machine tells the cloud no
32
+ * paths.
33
+ */
34
+ export interface CloudFile {
35
+ enabled?: boolean;
36
+ url?: string;
37
+ }
38
+ /** Where the effective origin came from. */
39
+ export type CloudUrlSource = "env" | "cloud.json" | "kraftwerk.yml" | "default";
40
+ export interface MachineCloud {
41
+ enabled: boolean;
42
+ url: string;
43
+ source: CloudUrlSource;
44
+ /** Why it is off, when it is. */
45
+ reason?: string;
46
+ }
47
+ export interface MachineWorkspace {
48
+ id?: string;
49
+ name: string;
50
+ slug: string;
51
+ root?: string;
52
+ }
53
+ export interface Credentials {
54
+ id: string;
55
+ key: string;
56
+ secret: string;
57
+ }
58
+ export interface RegisterResponse {
59
+ ok: boolean;
60
+ interval: number;
61
+ claimCode: string | null;
62
+ account: string | null;
63
+ }
64
+ export interface HeartbeatResponse {
65
+ ok: boolean;
66
+ interval: number;
67
+ account: string | null;
68
+ claimCode: string | null;
69
+ }
70
+ export interface UnlinkResponse {
71
+ ok: boolean;
72
+ claimCode: string | null;
73
+ }
74
+ export declare const cloudFilePath: () => string;
75
+ /** The retired per-workspace identities (before machines): deleted at the daemon's start. */
76
+ export declare const retiredCloudDir: () => string;
77
+ export declare function readCloudFile(): Promise<CloudFile>;
78
+ /** Merge a change into cloud.json (a `url: undefined` leaves the URL as it is). */
79
+ export declare function writeCloudFile(change: CloudFile): Promise<CloudFile>;
80
+ /** Whether the environment turned the cloud off for this process. */
81
+ export declare const envCloudOff: () => boolean;
82
+ /**
83
+ * The machine's effective cloud. `ymlUrls`: `cloud.url` of open workspaces
84
+ * (deprecated) — used only when neither the environment nor cloud.json names
85
+ * an origin.
86
+ */
87
+ export declare function machineCloud(ymlUrls?: string[]): Promise<MachineCloud>;
88
+ export declare const claimUrlFor: (url: string, code: string | null | undefined) => string | null;
89
+ export declare const GOODBYE_TIMEOUT_MS = 800;
90
+ /** A refused or failed call; `status` is the HTTP status (0: no answer). */
91
+ export declare class CloudError extends Error {
92
+ readonly status: number;
93
+ constructor(message: string, status: number);
94
+ }
95
+ /** The cloud no longer knows this machine (its data was reset): registering again fixes it. */
96
+ export declare const unknownMachine: (err: unknown) => boolean;
97
+ export declare function cloudPost<T>(base: string, route: string, body: unknown, opts?: {
98
+ version?: string;
99
+ timeout?: number;
100
+ }): Promise<T>;
101
+ /** What register says about the machine besides its credentials. */
102
+ export interface MachineHello {
103
+ name: string;
104
+ handle: string;
105
+ hostname: string;
106
+ platform: string;
107
+ version: string;
108
+ }
109
+ export declare const machineRegister: (base: string, creds: Credentials, hello: MachineHello, workspaces: MachineWorkspace[]) => Promise<RegisterResponse>;
110
+ export declare const machineHeartbeat: (base: string, creds: Credentials, extra: {
111
+ version?: string;
112
+ workspaces?: MachineWorkspace[];
113
+ }) => Promise<HeartbeatResponse>;
114
+ export declare const machineGoodbye: (base: string, creds: Credentials, version?: string) => Promise<unknown>;
115
+ export declare const machineUnlink: (base: string, creds: Credentials, version?: string) => Promise<UnlinkResponse>;
@@ -0,0 +1,94 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { CLOUD_DEFAULT_URL } from "../config.js";
6
+ // Resolved on every use, not at import: a test swaps HOME after some module already imported this one.
7
+ const kwHome = () => path.join(os.homedir(), ".kraftwerk");
8
+ export const cloudFilePath = () => path.join(kwHome(), "cloud.json");
9
+ /** The retired per-workspace identities (before machines): deleted at the daemon's start. */
10
+ export const retiredCloudDir = () => path.join(kwHome(), "cloud");
11
+ export async function readCloudFile() {
12
+ try {
13
+ const raw = JSON.parse(await fs.readFile(cloudFilePath(), "utf8"));
14
+ return {
15
+ ...(typeof raw.enabled === "boolean" ? { enabled: raw.enabled } : {}),
16
+ ...(typeof raw.url === "string" && raw.url ? { url: raw.url.replace(/\/+$/, "") } : {}),
17
+ };
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ /** Merge a change into cloud.json (a `url: undefined` leaves the URL as it is). */
24
+ export async function writeCloudFile(change) {
25
+ const cur = await readCloudFile();
26
+ const next = { ...cur, ...(change.enabled !== undefined ? { enabled: change.enabled } : {}), ...(change.url ? { url: change.url.replace(/\/+$/, "") } : {}) };
27
+ const file = cloudFilePath();
28
+ await fs.mkdir(path.dirname(file), { recursive: true });
29
+ const tmp = `${file}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
30
+ await fs.writeFile(tmp, JSON.stringify(next, null, 2) + "\n");
31
+ await fs.rename(tmp, file);
32
+ return next;
33
+ }
34
+ /** Whether the environment turned the cloud off for this process. */
35
+ export const envCloudOff = () => process.env.KRAFTWERK_CLOUD_URL?.trim().toLowerCase() === "off";
36
+ /**
37
+ * The machine's effective cloud. `ymlUrls`: `cloud.url` of open workspaces
38
+ * (deprecated) — used only when neither the environment nor cloud.json names
39
+ * an origin.
40
+ */
41
+ export async function machineCloud(ymlUrls = []) {
42
+ const file = await readCloudFile();
43
+ const env = process.env.KRAFTWERK_CLOUD_URL?.trim();
44
+ const [url, source] = env && !envCloudOff() ? [env, "env"] : file.url ? [file.url, "cloud.json"] : ymlUrls[0] ? [ymlUrls[0], "kraftwerk.yml"] : [CLOUD_DEFAULT_URL, "default"];
45
+ const base = { url: url.replace(/\/+$/, ""), source };
46
+ if (envCloudOff())
47
+ return { ...base, enabled: false, reason: "KRAFTWERK_CLOUD_URL=off" };
48
+ if (file.enabled === false)
49
+ return { ...base, enabled: false, reason: "turned off for this machine (`kraftwerk cloud on` turns it on)" };
50
+ return { ...base, enabled: true };
51
+ }
52
+ export const claimUrlFor = (url, code) => (code ? `${url}/claim/${encodeURIComponent(code)}` : null);
53
+ const REQUEST_TIMEOUT_MS = 8_000;
54
+ export const GOODBYE_TIMEOUT_MS = 800;
55
+ /** A refused or failed call; `status` is the HTTP status (0: no answer). */
56
+ export class CloudError extends Error {
57
+ status;
58
+ constructor(message, status) {
59
+ super(message);
60
+ this.status = status;
61
+ }
62
+ }
63
+ /** The cloud no longer knows this machine (its data was reset): registering again fixes it. */
64
+ export const unknownMachine = (err) => err instanceof CloudError && (err.status === 404 || /unknown machine|not registered/i.test(err.message));
65
+ export async function cloudPost(base, route, body, opts = {}) {
66
+ const url = `${base}${route}`;
67
+ let r;
68
+ try {
69
+ r = await fetch(url, {
70
+ method: "POST",
71
+ headers: { "content-type": "application/json", "user-agent": `kraftwerk/${opts.version ?? "dev"}` },
72
+ body: JSON.stringify(body),
73
+ signal: AbortSignal.timeout(opts.timeout ?? REQUEST_TIMEOUT_MS),
74
+ });
75
+ }
76
+ catch (err) {
77
+ throw new CloudError(err.message, 0);
78
+ }
79
+ const text = await r.text();
80
+ let data;
81
+ try {
82
+ data = text ? JSON.parse(text) : {};
83
+ }
84
+ catch {
85
+ throw new CloudError(`${r.status} from ${url}: not JSON`, r.status);
86
+ }
87
+ if (!r.ok)
88
+ throw new CloudError(data.error ?? `${r.status} from ${url}`, r.status);
89
+ return data;
90
+ }
91
+ export const machineRegister = (base, creds, hello, workspaces) => cloudPost(base, "/api/machines/register", { ...creds, ...hello, workspaces }, { version: hello.version });
92
+ export const machineHeartbeat = (base, creds, extra) => cloudPost(base, "/api/machines/heartbeat", { id: creds.id, secret: creds.secret, ...extra }, { version: extra.version });
93
+ export const machineGoodbye = (base, creds, version) => cloudPost(base, "/api/machines/goodbye", { id: creds.id, secret: creds.secret }, { version, timeout: GOODBYE_TIMEOUT_MS });
94
+ export const machineUnlink = (base, creds, version) => cloudPost(base, "/api/machines/unlink", { id: creds.id, secret: creds.secret }, { version });
@@ -0,0 +1,72 @@
1
+ import * as z from "zod";
2
+ /**
3
+ * The machine in the kraftwerk cloud (server/cloud.ts): its record, turning
4
+ * it on or off, a round now, releasing the claim. What `kraftwerk cloud`,
5
+ * `login` and `logout` ask the running daemon; a standalone server answers
6
+ * "off" (only the daemon registers). This machine only: the claim code
7
+ * claims the machine.
8
+ */
9
+ export declare const cloudRoutes: (import("./router.js").Route<"cloud.set", {
10
+ daemon: boolean;
11
+ url: string;
12
+ state: import("../cloud.js").CloudState;
13
+ reason?: string;
14
+ source?: import("../../core/cloud.js").CloudUrlSource;
15
+ error?: string;
16
+ id?: string;
17
+ machine?: string;
18
+ account?: string | null;
19
+ interval?: number;
20
+ lastSeen?: string;
21
+ claimCode?: string | null;
22
+ claimUrl?: string | null;
23
+ workspaces?: number;
24
+ }, "/api/cloud", z.ZodObject<{
25
+ enabled: z.ZodBoolean;
26
+ url: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>> | import("./router.js").Route<"cloud.status", {
28
+ daemon: boolean;
29
+ url: string;
30
+ state: import("../cloud.js").CloudState;
31
+ reason?: string;
32
+ source?: import("../../core/cloud.js").CloudUrlSource;
33
+ error?: string;
34
+ id?: string;
35
+ machine?: string;
36
+ account?: string | null;
37
+ interval?: number;
38
+ lastSeen?: string;
39
+ claimCode?: string | null;
40
+ claimUrl?: string | null;
41
+ workspaces?: number;
42
+ }, "/api/cloud", undefined> | import("./router.js").Route<"cloud.sync", {
43
+ daemon: boolean;
44
+ url: string;
45
+ state: import("../cloud.js").CloudState;
46
+ reason?: string;
47
+ source?: import("../../core/cloud.js").CloudUrlSource;
48
+ error?: string;
49
+ id?: string;
50
+ machine?: string;
51
+ account?: string | null;
52
+ interval?: number;
53
+ lastSeen?: string;
54
+ claimCode?: string | null;
55
+ claimUrl?: string | null;
56
+ workspaces?: number;
57
+ }, "/api/cloud/sync", undefined> | import("./router.js").Route<"cloud.unlink", {
58
+ daemon: boolean;
59
+ url: string;
60
+ state: import("../cloud.js").CloudState;
61
+ reason?: string;
62
+ source?: import("../../core/cloud.js").CloudUrlSource;
63
+ error?: string;
64
+ id?: string;
65
+ machine?: string;
66
+ account?: string | null;
67
+ interval?: number;
68
+ lastSeen?: string;
69
+ claimCode?: string | null;
70
+ claimUrl?: string | null;
71
+ workspaces?: number;
72
+ }, "/api/cloud/unlink", undefined>)[];
@@ -0,0 +1,38 @@
1
+ import * as z from "zod";
2
+ import { writeCloudFile } from "../../core/cloud.js";
3
+ import { applyMachineCloud, cloudStatus, syncMachineCloud, unlinkMachineCloud } from "../cloud.js";
4
+ import { currentHub } from "../hub-context.js";
5
+ import { route } from "./router.js";
6
+ /**
7
+ * The machine in the kraftwerk cloud (server/cloud.ts): its record, turning
8
+ * it on or off, a round now, releasing the claim. What `kraftwerk cloud`,
9
+ * `login` and `logout` ask the running daemon; a standalone server answers
10
+ * "off" (only the daemon registers). This machine only: the claim code
11
+ * claims the machine.
12
+ */
13
+ export const cloudRoutes = [
14
+ route({ name: "cloud.status", method: "GET", path: "/api/cloud", summary: "the machine's cloud record: state, claim code, account, last heartbeat", access: "local" }, async () => ({
15
+ daemon: currentHub().daemon === true,
16
+ ...cloudStatus(),
17
+ })),
18
+ route({
19
+ name: "cloud.set",
20
+ method: "POST",
21
+ path: "/api/cloud",
22
+ summary: "turn the machine's cloud on or off {enabled, url?} (~/.kraftwerk/cloud.json); off says goodbye",
23
+ access: "local",
24
+ body: z.object({ enabled: z.boolean(), url: z.string().url().optional() }),
25
+ }, async (c) => {
26
+ const { enabled, url } = await c.body();
27
+ await writeCloudFile({ enabled, url });
28
+ return { daemon: currentHub().daemon === true, ...(await applyMachineCloud()) };
29
+ }),
30
+ route({ name: "cloud.sync", method: "POST", path: "/api/cloud/sync", summary: "register or heartbeat now; answers the record (a claim made in the cloud shows here)", access: "local" }, async () => ({
31
+ daemon: currentHub().daemon === true,
32
+ ...(await syncMachineCloud()),
33
+ })),
34
+ route({ name: "cloud.unlink", method: "POST", path: "/api/cloud/unlink", summary: "release the machine from its account; the cloud hands out a fresh claim code", access: "local", errors: 400 }, async () => ({
35
+ daemon: currentHub().daemon === true,
36
+ ...(await unlinkMachineCloud()),
37
+ })),
38
+ ];
@@ -16,16 +16,16 @@ export declare const deviceRoutes: (import("./router.js").Route<"devices.code",
16
16
  code: z.ZodString;
17
17
  name: z.ZodDefault<z.ZodString>;
18
18
  }, z.core.$strip>> | import("./router.js").Route<"devices.relay", {
19
+ enabled: boolean;
20
+ url: string | null;
21
+ fingerprint: string | null;
22
+ daemon: boolean;
19
23
  state: import("../relay.js").RelayState;
20
24
  id?: string;
21
25
  key?: string;
22
26
  error?: string;
23
27
  clients: number;
24
28
  since?: string;
25
- enabled: boolean;
26
- url: string | null;
27
- fingerprint: string | null;
28
- daemon: boolean;
29
29
  }, "/api/devices/relay", undefined> | import("./router.js").Route<"devices.revoke", {
30
30
  ok: boolean;
31
31
  }, "/api/devices/:id", undefined> | import("./router.js").Route<"devices.self", {
@@ -41,16 +41,16 @@ export declare const deviceRoutes: (import("./router.js").Route<"devices.code",
41
41
  }>;
42
42
  key: z.ZodString;
43
43
  }, z.core.$strip>> | import("./router.js").Route<"devices.setRelay", {
44
+ enabled: boolean;
45
+ url: string;
46
+ fingerprint: string;
47
+ daemon: boolean;
44
48
  state: import("../relay.js").RelayState;
45
49
  id?: string;
46
50
  key?: string;
47
51
  error?: string;
48
52
  clients: number;
49
53
  since?: string;
50
- enabled: boolean;
51
- url: string;
52
- fingerprint: string;
53
- daemon: boolean;
54
54
  }, "/api/devices/relay", z.ZodObject<{
55
55
  enabled: z.ZodBoolean;
56
56
  url: z.ZodOptional<z.ZodString>;
@@ -291,7 +291,70 @@ export declare const routes: (Route<"agents.addJournal", {
291
291
  ok: true;
292
292
  }, "/api/chats/:id/task-stop", z.ZodObject<{
293
293
  taskId: z.ZodDefault<z.ZodString>;
294
- }, z.core.$strip>> | Route<"devices.code", {
294
+ }, z.core.$strip>> | Route<"cloud.set", {
295
+ daemon: boolean;
296
+ url: string;
297
+ state: import("../cloud.js").CloudState;
298
+ reason?: string;
299
+ source?: import("../../core/cloud.js").CloudUrlSource;
300
+ error?: string;
301
+ id?: string;
302
+ machine?: string;
303
+ account?: string | null;
304
+ interval?: number;
305
+ lastSeen?: string;
306
+ claimCode?: string | null;
307
+ claimUrl?: string | null;
308
+ workspaces?: number;
309
+ }, "/api/cloud", z.ZodObject<{
310
+ enabled: z.ZodBoolean;
311
+ url: z.ZodOptional<z.ZodString>;
312
+ }, z.core.$strip>> | Route<"cloud.status", {
313
+ daemon: boolean;
314
+ url: string;
315
+ state: import("../cloud.js").CloudState;
316
+ reason?: string;
317
+ source?: import("../../core/cloud.js").CloudUrlSource;
318
+ error?: string;
319
+ id?: string;
320
+ machine?: string;
321
+ account?: string | null;
322
+ interval?: number;
323
+ lastSeen?: string;
324
+ claimCode?: string | null;
325
+ claimUrl?: string | null;
326
+ workspaces?: number;
327
+ }, "/api/cloud", undefined> | Route<"cloud.sync", {
328
+ daemon: boolean;
329
+ url: string;
330
+ state: import("../cloud.js").CloudState;
331
+ reason?: string;
332
+ source?: import("../../core/cloud.js").CloudUrlSource;
333
+ error?: string;
334
+ id?: string;
335
+ machine?: string;
336
+ account?: string | null;
337
+ interval?: number;
338
+ lastSeen?: string;
339
+ claimCode?: string | null;
340
+ claimUrl?: string | null;
341
+ workspaces?: number;
342
+ }, "/api/cloud/sync", undefined> | Route<"cloud.unlink", {
343
+ daemon: boolean;
344
+ url: string;
345
+ state: import("../cloud.js").CloudState;
346
+ reason?: string;
347
+ source?: import("../../core/cloud.js").CloudUrlSource;
348
+ error?: string;
349
+ id?: string;
350
+ machine?: string;
351
+ account?: string | null;
352
+ interval?: number;
353
+ lastSeen?: string;
354
+ claimCode?: string | null;
355
+ claimUrl?: string | null;
356
+ workspaces?: number;
357
+ }, "/api/cloud/unlink", undefined> | Route<"devices.code", {
295
358
  code: string;
296
359
  expiresAt: string;
297
360
  urls: string[];
@@ -307,16 +370,16 @@ export declare const routes: (Route<"agents.addJournal", {
307
370
  code: z.ZodString;
308
371
  name: z.ZodDefault<z.ZodString>;
309
372
  }, z.core.$strip>> | Route<"devices.relay", {
373
+ enabled: boolean;
374
+ url: string | null;
375
+ fingerprint: string | null;
376
+ daemon: boolean;
310
377
  state: import("../relay.js").RelayState;
311
378
  id?: string;
312
379
  key?: string;
313
380
  error?: string;
314
381
  clients: number;
315
382
  since?: string;
316
- enabled: boolean;
317
- url: string | null;
318
- fingerprint: string | null;
319
- daemon: boolean;
320
383
  }, "/api/devices/relay", undefined> | Route<"devices.revoke", {
321
384
  ok: boolean;
322
385
  }, "/api/devices/:id", undefined> | Route<"devices.self", {
@@ -332,16 +395,16 @@ export declare const routes: (Route<"agents.addJournal", {
332
395
  }>;
333
396
  key: z.ZodString;
334
397
  }, z.core.$strip>> | Route<"devices.setRelay", {
398
+ enabled: boolean;
399
+ url: string;
400
+ fingerprint: string;
401
+ daemon: boolean;
335
402
  state: import("../relay.js").RelayState;
336
403
  id?: string;
337
404
  key?: string;
338
405
  error?: string;
339
406
  clients: number;
340
407
  since?: string;
341
- enabled: boolean;
342
- url: string;
343
- fingerprint: string;
344
- daemon: boolean;
345
408
  }, "/api/devices/relay", z.ZodObject<{
346
409
  enabled: z.ZodBoolean;
347
410
  url: z.ZodOptional<z.ZodString>;
@@ -439,7 +502,22 @@ export declare const routes: (Route<"agents.addJournal", {
439
502
  vibeables: boolean;
440
503
  projects: boolean;
441
504
  publicUrl: string;
442
- cloud: import("../cloud.js").CloudStatus;
505
+ cloud: {
506
+ included: boolean;
507
+ url: string;
508
+ state: import("../cloud.js").CloudState;
509
+ reason?: string;
510
+ source?: import("../../core/cloud.js").CloudUrlSource;
511
+ error?: string;
512
+ id?: string;
513
+ machine?: string;
514
+ account?: string | null;
515
+ interval?: number;
516
+ lastSeen?: string;
517
+ claimCode?: string | null;
518
+ claimUrl?: string | null;
519
+ workspaces?: number;
520
+ };
443
521
  switcher: import("../../config.js").SwitcherEntry[];
444
522
  }, "/api/meta", undefined> | Route<"notifications.clear", {
445
523
  ok: boolean;
@@ -3,6 +3,7 @@ import { agentRoutes } from "./agents.js";
3
3
  import { attentionRoutes } from "./attention.js";
4
4
  import { channelRoutes } from "./channels.js";
5
5
  import { chatRoutes } from "./chats.js";
6
+ import { cloudRoutes } from "./cloud.js";
6
7
  import { deviceRoutes } from "./devices.js";
7
8
  import { hubRoutes } from "./hub.js";
8
9
  import { fileRoutes } from "./files.js";
@@ -35,6 +36,7 @@ const domainRoutes = [
35
36
  ...gitRoutes,
36
37
  ...vibeableRoutes,
37
38
  ...deviceRoutes,
39
+ ...cloudRoutes,
38
40
  ...hubRoutes,
39
41
  ];
40
42
  const protocolRoute = route({ name: "protocol.get", method: "GET", path: "/api/protocol", summary: "the protocol version and every route", access: "public" }, async () => ({
@@ -35,7 +35,22 @@ export declare const workspaceRoutes: (import("./router.js").Route<"identity.get
35
35
  projects: boolean;
36
36
  /** @deprecated Always "": the Cloudflare Tunnel that set it was removed in 0.65. Removed with protocol 2. */
37
37
  publicUrl: string;
38
- cloud: import("../cloud.js").CloudStatus;
38
+ cloud: {
39
+ included: boolean;
40
+ url: string;
41
+ state: import("../cloud.js").CloudState;
42
+ reason?: string;
43
+ source?: import("../../core/cloud.js").CloudUrlSource;
44
+ error?: string;
45
+ id?: string;
46
+ machine?: string;
47
+ account?: string | null;
48
+ interval?: number;
49
+ lastSeen?: string;
50
+ claimCode?: string | null;
51
+ claimUrl?: string | null;
52
+ workspaces?: number;
53
+ };
39
54
  switcher: import("../../config.js").SwitcherEntry[];
40
55
  }, "/api/meta", undefined> | import("./router.js").Route<"search.agents", {
41
56
  workspaces: (import("../../core/search.js").WorkspaceAgents & {
@@ -105,7 +105,8 @@ export const workspaceRoutes = [
105
105
  projects: (ws?.config.projects && ws.config.projects.enabled !== false) === true,
106
106
  /** @deprecated Always "": the Cloudflare Tunnel that set it was removed in 0.65. Removed with protocol 2. */
107
107
  publicUrl: "",
108
- cloud: cloudStatus(),
108
+ // The machine's cloud record (only the daemon registers one); `included`: this workspace rides along (no cloud.enabled: false veto).
109
+ cloud: { ...cloudStatus(), included: ws?.config.cloud?.enabled !== false },
109
110
  switcher,
110
111
  };
111
112
  }),