@kky42/pievo-server 2.4.14 → 3.0.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 (90) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{actionUi-5PtES4q0.js → actionUi-BZrLpgHT.js} +1 -1
  3. package/.output/public/assets/app-DXLuQNb9.css +2 -0
  4. package/.output/public/assets/index-Di_wpAtL.js +10 -0
  5. package/.output/public/assets/loops._loopId-X3ClNPMY.js +67 -0
  6. package/.output/public/assets/loops._loopId_.runs._runId-ByU2BSyF.js +3 -0
  7. package/.output/public/assets/preload-helper-hRTYB_ju.js +3 -0
  8. package/.output/public/assets/{routes-Dm_3J2aV.js → routes-BIOJZsig.js} +1 -1
  9. package/.output/public/assets/routes-K8D5uBiV.js +1 -0
  10. package/.output/server/_chunks/ssr-renderer.mjs +1 -1
  11. package/.output/server/_libs/{drizzle-orm+postgres.mjs → drizzle-orm.mjs} +93 -2218
  12. package/.output/server/_ssr/{MachinesModal-BaEtzycr.mjs → MachinesModal-CmVMec6B.mjs} +46 -46
  13. package/.output/server/_ssr/{actionUi-5HOCZnom.mjs → actionUi-DE-Pb2tv.mjs} +1 -2
  14. package/.output/server/_ssr/{artifactFiles-DO43wwXA.mjs → artifactFiles-Di1xDKvX.mjs} +5 -8
  15. package/.output/server/_ssr/{artifactKind-DwDr8IYc.mjs → artifactKind-DqpmtxDU.mjs} +3 -9
  16. package/.output/server/_ssr/{boot-C472kLwY.mjs → boot-CYRi-wez.mjs} +2152 -351
  17. package/.output/server/_ssr/lifecycleUi-BsbUsi5K.mjs +106 -0
  18. package/.output/server/_ssr/{loopApi-DT751TJA.mjs → loopApi-Bez1rHV0.mjs} +50 -66
  19. package/.output/server/_ssr/{loopProjection-qaD2I48a.mjs → loopProjection-esCjyZJg.mjs} +4 -23
  20. package/.output/server/_ssr/{loops._loopId-CXgw2Rz1.mjs → loops._loopId-KUezNY69.mjs} +8 -23
  21. package/.output/server/_ssr/{loops._loopId_.runs._runId-Dk94Z0Jq.mjs → loops._loopId_.runs._runId-BFP9oMrp.mjs} +2 -7
  22. package/.output/server/_ssr/{loops._loopId_.runs._runId-SASSqOfy.mjs → loops._loopId_.runs._runId-DOkUEesG.mjs} +18 -33
  23. package/.output/server/_ssr/{machineFns-BSrIgn3f.mjs → machineFns-CniGcY8s.mjs} +13 -50
  24. package/.output/server/_ssr/networkAccess-CIlK3ION.mjs +30 -0
  25. package/.output/server/_ssr/router-BFIH2UAE.mjs +362 -0
  26. package/.output/server/_ssr/routes-BSVBANI3.mjs +12 -0
  27. package/.output/server/_ssr/{routes-Cr-9yDEH.mjs → routes-DnE7XDe_.mjs} +91 -50
  28. package/.output/server/_ssr/{routes-DRBS4Zi4.mjs → routes-ngpX7h8D.mjs} +2 -4
  29. package/.output/server/_ssr/ssr.mjs +25 -34
  30. package/.output/server/_ssr/start-B5_9Z5W-.mjs +35 -0
  31. package/.output/server/{_tanstack-start-manifest_v-DxLIQ0cc.mjs → _tanstack-start-manifest_v-DnwAupbZ.mjs} +8 -13
  32. package/.output/server/drizzle/0000_baseline.sql +6 -97
  33. package/.output/server/drizzle/meta/_journal.json +2 -23
  34. package/.output/server/index.mjs +45 -52
  35. package/drizzle/0000_baseline.sql +6 -97
  36. package/drizzle/meta/_journal.json +2 -23
  37. package/package.json +4 -8
  38. package/scripts/pievo-server.mjs +49 -47
  39. package/scripts/server-cli-lib.mjs +21 -12
  40. package/scripts/server-network.mjs +139 -0
  41. package/.output/public/assets/SignIn-CIFwnZtF.js +0 -1
  42. package/.output/public/assets/app-DvHMhs7S.css +0 -2
  43. package/.output/public/assets/index-CRX2pu0W.js +0 -10
  44. package/.output/public/assets/loops._loopId-BsuV-Rmf.js +0 -67
  45. package/.output/public/assets/loops._loopId_.runs._runId-DpK_fzmZ.js +0 -3
  46. package/.output/public/assets/preload-helper-sAZOZb3G.js +0 -5
  47. package/.output/public/assets/routes-DxF_s3cD.js +0 -1
  48. package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -419
  49. package/.output/server/_libs/@aws-sdk/checksums+[...].mjs +0 -10732
  50. package/.output/server/_libs/@aws-sdk/client-s3+[...].mjs +0 -9475
  51. package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -137
  52. package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -440
  53. package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -62
  54. package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -818
  55. package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -59
  56. package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -342
  57. package/.output/server/_libs/aws-crypto__crc32c.mjs +0 -314
  58. package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -2671
  59. package/.output/server/_ssr/SignIn-sHQaBQJC.mjs +0 -53
  60. package/.output/server/_ssr/artifacts-Cd7Qx3Qp.mjs +0 -2
  61. package/.output/server/_ssr/auth-0AamrwHo.mjs +0 -38355
  62. package/.output/server/_ssr/auth-client-lSMb_Idt.mjs +0 -1070
  63. package/.output/server/_ssr/bun-sqlite-dialect-BW9W1_Ps-DLIXocA0.mjs +0 -162
  64. package/.output/server/_ssr/core-RrmWoKne.mjs +0 -5
  65. package/.output/server/_ssr/d1-sqlite-dialect-BLC8LXE6-BoFMIPr_.mjs +0 -112
  66. package/.output/server/_ssr/db-BzvMQ2kx.mjs +0 -2
  67. package/.output/server/_ssr/dist-ClzqkwxD.mjs +0 -342
  68. package/.output/server/_ssr/factory-ixUjZqJn.mjs +0 -6903
  69. package/.output/server/_ssr/kysely-adapter-DkZbmKgV.mjs +0 -2
  70. package/.output/server/_ssr/kysely-migration-tables-JkVUjPF_-bPAX9LR_.mjs +0 -19
  71. package/.output/server/_ssr/logger-DDjsYB2X.mjs +0 -2
  72. package/.output/server/_ssr/loopTags-CXx5UdGf.mjs +0 -3356
  73. package/.output/server/_ssr/node-sqlite-dialect-BbUqnnYv.mjs +0 -162
  74. package/.output/server/_ssr/router-BlLlhfJe.mjs +0 -543
  75. package/.output/server/_ssr/routes-DTBltabU.mjs +0 -27
  76. package/.output/server/_ssr/start-4ifArtKx.mjs +0 -4
  77. package/.output/server/_ssr/store-vDZ9INSa.mjs +0 -2
  78. package/.output/server/_ssr/tokens-BHiCElYD.mjs +0 -2
  79. package/.output/server/drizzle/0001_add_pi_agent.sql +0 -9
  80. package/.output/server/drizzle/0002_remove_teams.sql +0 -15
  81. package/.output/server/drizzle/0003_add_loop_tags.sql +0 -5
  82. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  83. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  84. package/.output/server/node_modules/tslib/package.json +0 -47
  85. package/.output/server/node_modules/tslib/tslib.js +0 -484
  86. package/.output/server/package.json +0 -9
  87. package/drizzle/0001_add_pi_agent.sql +0 -9
  88. package/drizzle/0002_remove_teams.sql +0 -15
  89. package/drizzle/0003_add_loop_tags.sql +0 -5
  90. package/scripts/prestart.mjs +0 -66
@@ -1,17 +1,7 @@
1
- import { Et as listMachinesForUser, G as forceDeleteMachine, Lt as mintDeviceToken, Nt as loopsForMachine, Pt as machineIdFromToken, Sn as sha256, Tt as listMachines$1, et as getMachine, j as createMachine$1, jn as updateMachine } from "./loopTags-CXx5UdGf.mjs";
2
- import { a as daemonNeedsUpdate, n as MIN_DAEMON_VERSION, o as ensureServer } from "./boot-C472kLwY.mjs";
3
- import { requestScope } from "./auth-0AamrwHo.mjs";
4
- import { n as createServerFn } from "./ssr.mjs";
1
+ import { A as listMachines$1, C as getMachine, F as machineIdFromToken, L as mintDeviceToken, P as loopsForMachine, W as sha256, Y as updateMachine, b as forceDeleteMachine, c as ensureServer, n as MIN_DAEMON_VERSION, s as daemonNeedsUpdate, v as createMachine$1 } from "./boot-CYRi-wez.mjs";
2
+ import { r as createServerFn } from "./ssr.mjs";
5
3
  import { t as createServerRpc } from "./createServerRpc-BD8Xn_0_.mjs";
6
- //#region node_modules/.nitro/vite/services/ssr/assets/machineFns-BSrIgn3f.js
7
- /** Open mode sees every machine; auth mode sees only the signed-in owner's rows. */
8
- function machineInScope(machine, scope) {
9
- return !scope.enforce || !!scope.userId && machine.userId === scope.userId;
10
- }
11
- /** A device token fully impersonates its machine, so auth mode keeps it owner-only. */
12
- function tokenVisibleTo(machine, scope) {
13
- return machineInScope(machine, scope);
14
- }
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/machineFns-CniGcY8s.js
15
5
  /**
16
6
  * Latest published @kky42/pievo version — a cached, fail-silent lookup of the
17
7
  * npm dist-tag `latest`, so the web can tell a user their daemon is outdated and
@@ -93,21 +83,10 @@ var LatestDaemonVersion = class {
93
83
  };
94
84
  var latestDaemonVersion = new LatestDaemonVersion();
95
85
  /**
96
- * Machine-management server functions. In auth mode, the signed-in user is the
97
- * sole human authorization boundary. Open mode intentionally shares all machine
98
- * administration, including reconnect tokens.
86
+ * Machine-management server functions. Pievo is single-user: anyone who can
87
+ * reach the dashboard administers every machine, including reconnect tokens.
99
88
  */
100
- /** Missing and out-of-scope machines both resolve to undefined. */
101
- async function scopedMachine(id) {
102
- const scope = await requestScope();
103
- if (scope.enforce && !scope.userId) return { scope };
104
- const candidate = await getMachine(id);
105
- return {
106
- scope,
107
- machine: candidate && machineInScope(candidate, scope) ? candidate : void 0
108
- };
109
- }
110
- async function toSummary(machine, scope) {
89
+ async function toSummary(machine) {
111
90
  return {
112
91
  id: machine.id,
113
92
  name: machine.name,
@@ -121,7 +100,7 @@ async function toSummary(machine, scope) {
121
100
  latestDaemonVersion: latestDaemonVersion.get(),
122
101
  needsUpdate: daemonNeedsUpdate(machine.daemonVersion),
123
102
  requiredDaemonVersion: MIN_DAEMON_VERSION,
124
- token: tokenVisibleTo(machine, scope) ? machine.token ?? null : null,
103
+ token: machine.token ?? null,
125
104
  loopCount: (await loopsForMachine(machine.id)).length
126
105
  };
127
106
  }
@@ -132,10 +111,8 @@ var listMachines_createServerFn_handler = createServerRpc({
132
111
  }, (opts) => listMachines.__executeServer(opts));
133
112
  var listMachines = createServerFn({ method: "GET" }).handler(listMachines_createServerFn_handler, async () => {
134
113
  await ensureServer();
135
- const scope = await requestScope();
136
- if (scope.enforce && !scope.userId) return [];
137
- const machines = scope.enforce ? await listMachinesForUser(scope.userId) : await listMachines$1();
138
- return Promise.all(machines.filter((machine) => machine.name.trim()).map((machine) => toSummary(machine, scope)));
114
+ const machines = await listMachines$1();
115
+ return Promise.all(machines.filter((machine) => machine.name.trim()).map(toSummary));
139
116
  });
140
117
  var createMachine_createServerFn_handler = createServerRpc({
141
118
  id: "ed679b4e1e05e903a5d4ba4a70d9b9d883991ab22ed59b4d2d69078941818415",
@@ -144,13 +121,10 @@ var createMachine_createServerFn_handler = createServerRpc({
144
121
  }, (opts) => createMachine.__executeServer(opts));
145
122
  var createMachine = createServerFn({ method: "POST" }).handler(createMachine_createServerFn_handler, async () => {
146
123
  await ensureServer();
147
- const scope = await requestScope();
148
- if (scope.enforce && !scope.userId) return { error: "not signed in" };
149
124
  const token = mintDeviceToken();
150
125
  const id = machineIdFromToken(token);
151
126
  await createMachine$1({
152
127
  id,
153
- userId: scope.userId ?? "shared",
154
128
  name: "",
155
129
  tokenHash: sha256(token),
156
130
  token,
@@ -168,8 +142,8 @@ var machineStatus_createServerFn_handler = createServerRpc({
168
142
  }, (opts) => machineStatus.__executeServer(opts));
169
143
  var machineStatus = createServerFn({ method: "GET" }).validator((id) => id).handler(machineStatus_createServerFn_handler, async ({ data: id }) => {
170
144
  await ensureServer();
171
- const { scope, machine } = await scopedMachine(id);
172
- return machine ? toSummary(machine, scope) : null;
145
+ const machine = await getMachine(id);
146
+ return machine ? toSummary(machine) : null;
173
147
  });
174
148
  var finalizeMachine_createServerFn_handler = createServerRpc({
175
149
  id: "27d7eba0b6a2257949392fdd56057275a5c5366484ae01faf3b5897a225ecd83",
@@ -180,8 +154,6 @@ var finalizeMachine = createServerFn({ method: "POST" }).validator((data) => dat
180
154
  await ensureServer();
181
155
  const name = data.name?.trim();
182
156
  if (!name) return { ok: false };
183
- const { machine } = await scopedMachine(data.id);
184
- if (!machine) return { ok: false };
185
157
  return { ok: !!await updateMachine(data.id, { name }) };
186
158
  });
187
159
  var deleteMachine_createServerFn_handler = createServerRpc({
@@ -191,17 +163,8 @@ var deleteMachine_createServerFn_handler = createServerRpc({
191
163
  }, (opts) => deleteMachine.__executeServer(opts));
192
164
  var deleteMachine = createServerFn({ method: "POST" }).validator((id) => id).handler(deleteMachine_createServerFn_handler, async ({ data: id }) => {
193
165
  const { scheduler } = await ensureServer();
194
- const { scope, machine } = await scopedMachine(id);
195
- if (scope.enforce && !scope.userId) return {
196
- ok: false,
197
- error: "unauthorized"
198
- };
199
- if (!machine) return {
200
- ok: false,
201
- error: "machine not found"
202
- };
203
- const deleted = scope.enforce ? await forceDeleteMachine(id, scope.userId) : await forceDeleteMachine(id);
204
- if (deleted.state === "forbidden" || deleted.state === "not-found") return {
166
+ const deleted = await forceDeleteMachine(id);
167
+ if (deleted.state === "not-found") return {
205
168
  ok: false,
206
169
  error: "machine not found"
207
170
  };
@@ -0,0 +1,30 @@
1
+ import { isIP } from "node:net";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/networkAccess-CIlK3ION.js
3
+ /**
4
+ * Pievo has no login, so the network is the access boundary: only this
5
+ * machine, private LAN ranges, and Tailscale peers may reach any route.
6
+ * Anyone who passes this check can run prompts on every connected machine.
7
+ * Requests proxied by `tailscale serve` arrive from loopback.
8
+ *
9
+ * IPv4: loopback 127/8, RFC 1918 (10/8, 172.16/12, 192.168/16), link-local
10
+ * 169.254/16, and CGNAT 100.64/10 (Tailscale addresses). IPv6: loopback,
11
+ * link-local fe80::/10, and unique-local fc00::/7 (includes Tailscale's
12
+ * fd7a:115c:a1e0::/48). IPv4-mapped IPv6 is checked as IPv4.
13
+ */
14
+ function isTrustedPeer(address) {
15
+ if (!address) return false;
16
+ const ip = address.toLowerCase().replace(/^::ffff:(?=\d+\.\d+\.\d+\.\d+$)/, "").split("%")[0];
17
+ const family = isIP(ip);
18
+ if (family === 4) {
19
+ const [a, b] = ip.split(".").map(Number);
20
+ return a === 127 || a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254 || a === 100 && b >= 64 && b <= 127;
21
+ }
22
+ if (family === 6) {
23
+ if (ip === "::1") return true;
24
+ const first = parseInt(ip.split(":")[0] || "0", 16);
25
+ return (first & 65472) === 65152 || (first & 65024) === 64512;
26
+ }
27
+ return false;
28
+ }
29
+ //#endregion
30
+ export { isTrustedPeer };
@@ -0,0 +1,362 @@
1
+ import { c as ensureServer, d as readJsonBody, f as validOptionalPollString, m as validPollWireId, p as validPollCurrentRun, r as POLL_HTTP_BODY_FIELDS, t as MACHINE_BODY_CAP, u as readByteBody } from "./boot-CYRi-wez.mjs";
2
+ import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
3
+ import { c as createFileRoute, i as HeadContent, l as createRootRoute, o as createRouter, r as Scripts } from "../_libs/@tanstack/react-router+[...].mjs";
4
+ import { r as imageMime, t as Route$10 } from "./artifactKind-DqpmtxDU.mjs";
5
+ import { t as Route$11 } from "./loops._loopId_.runs._runId-BFP9oMrp.mjs";
6
+ import { n as Route$12 } from "./routes-DnE7XDe_.mjs";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/router-BFIH2UAE.js
8
+ var import_jsx_runtime = require_jsx_runtime();
9
+ var app_default = "/assets/app-DXLuQNb9.css";
10
+ var Route$9 = createRootRoute({
11
+ head: () => ({
12
+ meta: [
13
+ { charSet: "utf-8" },
14
+ {
15
+ name: "viewport",
16
+ content: "width=device-width, initial-scale=1"
17
+ },
18
+ { title: "Pievo" }
19
+ ],
20
+ links: [{
21
+ rel: "stylesheet",
22
+ href: app_default
23
+ }, {
24
+ rel: "icon",
25
+ type: "image/svg+xml",
26
+ href: "/favicon.svg"
27
+ }]
28
+ }),
29
+ shellComponent: RootDocument
30
+ });
31
+ function RootDocument({ children }) {
32
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", {
33
+ lang: "en",
34
+ suppressHydrationWarning: true,
35
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("head", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadContent, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("body", {
36
+ className: "dot-grid-subtle bg-paper font-sans text-primary antialiased",
37
+ suppressHydrationWarning: true,
38
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scripts, {})]
39
+ })]
40
+ });
41
+ }
42
+ async function machineJsonRoute(request, options) {
43
+ const auth = request.headers.get("authorization") ?? "";
44
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
45
+ if (!token) return Response.json({ error: options.missingCredential }, { status: 401 });
46
+ const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
47
+ if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
48
+ if (parsed.kind !== "ok") {
49
+ if (options.invalidBody === void 0) return Response.json({ error: options.invalidJsonError ?? "invalid JSON body" }, { status: 400 });
50
+ return options.handle(token, options.invalidBody);
51
+ }
52
+ const body = options.parseBody(parsed.body);
53
+ if (body === null) return Response.json({ error: options.invalidBodyError ?? "invalid request body" }, { status: 400 });
54
+ return options.handle(token, body);
55
+ }
56
+ var Route$8 = createFileRoute("/machine/report")({ server: { handlers: { POST: async ({ request }) => {
57
+ return machineJsonRoute(request, {
58
+ missingCredential: "missing token",
59
+ invalidBody: {},
60
+ parseBody: (value) => value && typeof value === "object" ? value : {},
61
+ handle: async (token, body) => {
62
+ const { getGateway } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
63
+ const r = await (await getGateway()).report(token, body);
64
+ return Response.json(r.body, { status: r.status });
65
+ }
66
+ });
67
+ } } } });
68
+ /**
69
+ * Deep readiness for process launchers. Unlike /api/health, this waits for the
70
+ * one-time backend boot (migrations, blob-store selection, and scheduler start).
71
+ * The per-launch nonce lets a launcher reject a different process already bound
72
+ * to the requested port.
73
+ */
74
+ async function readyResponse(ensure = ensureServer, env = process.env) {
75
+ await ensure();
76
+ return Response.json({
77
+ ok: true,
78
+ nonce: env.PIEVO_SERVER_LAUNCH_NONCE ?? null
79
+ });
80
+ }
81
+ var Route$7 = createFileRoute("/api/ready")({ server: { handlers: { GET: () => readyResponse() } } });
82
+ var Route$6 = createFileRoute("/api/health")({ server: { handlers: { GET: async () => Response.json({ ok: true }) } } });
83
+ /**
84
+ * POST /api/machine/sync — exact artifact sync (Bearer DEVICE token). The daemon
85
+ * posts the complete configured-path manifest after a run; the server reconciles
86
+ * artifact_files and replies with needHashes for raw blob PUTs.
87
+ * The manifest route retains its dedicated 32 MB JSON-body limit.
88
+ *
89
+ * This byte-ingress route is deliberately NOT rate limited (same rationale as the
90
+ * blob PUT): it requires a valid registered device token (unknown ⇒ 401) and is
91
+ * already bounded by the exact-path/hash handshake and 32MB sync-body cap, so a
92
+ * limiter would only throttle a legitimate large sync.
93
+ */
94
+ var Route$5 = createFileRoute("/api/machine/sync")({ server: { handlers: { POST: async ({ request }) => {
95
+ const auth = request.headers.get("authorization") ?? "";
96
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
97
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
98
+ const { SYNC_BODY_CAP, parseArtifactSyncBody } = await import("./boot-CYRi-wez.mjs").then((n) => n.N).then((n) => n.v);
99
+ const parsed = await readJsonBody(request, SYNC_BODY_CAP);
100
+ if (parsed.kind === "too-large") return Response.json({ error: "sync body too large" }, { status: 413 });
101
+ if (parsed.kind === "invalid") return Response.json({ error: "invalid JSON" }, { status: 400 });
102
+ const body = parseArtifactSyncBody(parsed.body);
103
+ if (!body) return Response.json({ error: "invalid sync body" }, { status: 400 });
104
+ const { getArtifactSync } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
105
+ const r = await (await getArtifactSync()).sync(token, body);
106
+ return Response.json(r.body, { status: r.status });
107
+ } } } });
108
+ var Route$4 = createFileRoute("/api/machine/status")({ server: { handlers: { GET: async ({ request }) => {
109
+ const auth = request.headers.get("authorization") ?? "";
110
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
111
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
112
+ const { getGateway } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
113
+ const r = await (await getGateway()).status(token);
114
+ return Response.json(r.body, { status: r.status });
115
+ } } } });
116
+ /** Reject non-object bodies, additions, coercible types, over-cap strings, and
117
+ * NUL-bearing text before the gateway is loaded or any state can mutate. */
118
+ function parsePollBody(value) {
119
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
120
+ if (Object.keys(value).some((key) => !POLL_HTTP_BODY_FIELDS.has(key))) return null;
121
+ const body = value;
122
+ if (!validOptionalPollString(body.host, 524288) || !validOptionalPollString(body.platform, 524288) || !validOptionalPollString(body.arch, 524288) || !validOptionalPollString(body.version, 64)) return null;
123
+ if (body.protocolVersion !== void 0 && (typeof body.protocolVersion !== "number" || !Number.isInteger(body.protocolVersion))) return null;
124
+ if (body.currentRuns !== void 0 && (!Array.isArray(body.currentRuns) || body.currentRuns.some((run) => !validPollCurrentRun(run)))) return null;
125
+ if (body.daemonInstanceId !== void 0 && !validPollWireId(body.daemonInstanceId)) return null;
126
+ if (body.recoveryComplete !== void 0 && typeof body.recoveryComplete !== "boolean") return null;
127
+ return body;
128
+ }
129
+ function gatewayPollRequest(body) {
130
+ return {
131
+ protocolVersion: body.protocolVersion,
132
+ currentRuns: body.currentRuns,
133
+ daemonInstanceId: body.daemonInstanceId,
134
+ recoveryComplete: body.recoveryComplete,
135
+ info: {
136
+ host: body.host,
137
+ platform: body.platform,
138
+ arch: body.arch,
139
+ version: body.version
140
+ }
141
+ };
142
+ }
143
+ var Route$3 = createFileRoute("/api/machine/poll")({ server: { handlers: { POST: async ({ request }) => {
144
+ return machineJsonRoute(request, {
145
+ missingCredential: "missing device token",
146
+ invalidBodyError: "unknown or invalid poll body fields",
147
+ invalidJsonError: "invalid JSON body",
148
+ parseBody: parsePollBody,
149
+ handle: async (token, body) => {
150
+ const { getGateway } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
151
+ const r = await (await getGateway()).pollV4Wait(token, gatewayPollRequest(body));
152
+ return Response.json(r.body, { status: r.status });
153
+ }
154
+ });
155
+ } } } });
156
+ function parseCliBody(value) {
157
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
158
+ const body = value;
159
+ if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "argv")) return null;
160
+ if (!Array.isArray(body.argv) || body.argv.some((entry) => typeof entry !== "string")) return null;
161
+ return { argv: body.argv };
162
+ }
163
+ /**
164
+ * POST /api/machine/cli — the ONE unified CLI dispatch (Bearer credential + `{argv}`).
165
+ * `CliGateway.cli()` branches by credential type: a `dk_` device token takes owner
166
+ * commands, while an `rk_` run credential can only report its run. Uses the standard
167
+ * 2MB machine JSON body cap.
168
+ */
169
+ var Route$2 = createFileRoute("/api/machine/cli")({ server: { handlers: { POST: async ({ request }) => {
170
+ return machineJsonRoute(request, {
171
+ missingCredential: "missing credential",
172
+ invalidJsonError: "body must be exactly {argv:string[]}",
173
+ invalidBodyError: "body must be exactly {argv:string[]}",
174
+ parseBody: parseCliBody,
175
+ handle: async (token, body) => {
176
+ const { getCliGateway } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
177
+ const r = await (await getCliGateway()).cli(token, body.argv);
178
+ return Response.json(r.body, { status: r.status });
179
+ }
180
+ });
181
+ } } } });
182
+ /**
183
+ * `decodeURIComponent` that returns null on malformed percent-encoding instead
184
+ * of throwing a URIError (which would escape a route handler as a 500). Shared
185
+ * by the routes that decode path segments; the policy is uniform — ANY
186
+ * malformed segment is the caller's clean 400, never silently kept raw.
187
+ */
188
+ function safeDecode(s) {
189
+ try {
190
+ return decodeURIComponent(s);
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * PUT /api/machine/blob/:hash — upload one content-addressed blob's raw bytes
197
+ * (Bearer DEVICE token). The server recomputes sha256(body) and rejects a
198
+ * mismatch before storing in the byte store. The hash is read from the URL (the daemon PUTs
199
+ * exactly the hashes the sync handshake returned in needHashes).
200
+ *
201
+ * This byte-ingress route is deliberately NOT rate limited: it requires a valid
202
+ * registered device token (unknown ⇒ 401, not an unauthenticated surface) and is
203
+ * already bounded by the sync hash-handshake (the server only accepts hashes it
204
+ * asked THIS machine for) plus the per-file 10MB byte cap. A
205
+ * large first sync bursts many concurrent PUTs on one token, so any limiter would
206
+ * only throttle legitimate uploads without adding real protection.
207
+ */
208
+ var Route$1 = createFileRoute("/api/machine/blob/$hash")({ server: { handlers: { PUT: async ({ request }) => {
209
+ const auth = request.headers.get("authorization") ?? "";
210
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
211
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
212
+ const hash = safeDecode(new URL(request.url).pathname.split("/").pop() ?? "");
213
+ if (hash === null) return Response.json({ error: "bad hash" }, { status: 400 });
214
+ const { BLOB_CAP } = await import("./boot-CYRi-wez.mjs").then((n) => n.N).then((n) => n.v);
215
+ const body = await readByteBody(request, BLOB_CAP);
216
+ if (body.kind === "too-large") return Response.json({ error: "blob exceeds size cap" }, { status: 413 });
217
+ if (body.kind === "invalid") return Response.json({ error: "could not read blob body" }, { status: 400 });
218
+ const buf = Buffer.from(body.bytes.buffer, body.bytes.byteOffset, body.bytes.byteLength);
219
+ const { getArtifactSync } = await import("./boot-CYRi-wez.mjs").then((n) => n.i).then((n) => n.t);
220
+ const r = await (await getArtifactSync()).putBlob(token, hash, buf);
221
+ return Response.json(r.body, { status: r.status });
222
+ } } } });
223
+ /**
224
+ * GET /api/artifact/:loopId/:path - stream one of a loop's live-synced artifact
225
+ * blobs. Two dispositions:
226
+ * - default: `attachment` (the download affordance for binary/oversize files;
227
+ * text is shown inline via getArtifact).
228
+ * - `?view=inline`: for a KNOWN image type only (the `imageMime` allowlist),
229
+ * served `inline` with the real image content-type so an <img> can render it.
230
+ * Hardened against a direct navigation turning the byte stream into a scripted
231
+ * document on the app origin: `X-Content-Type-Options: nosniff` pins the type
232
+ * and `Content-Security-Policy: sandbox` loads it in an opaque origin with no
233
+ * scripts - so an inline SVG can't run script against the session even if the
234
+ * URL is opened directly. Non-image inline requests fall back to attachment.
235
+ *
236
+ * Served to dashboard callers like the server functions. Path-safe: the relative path is
237
+ * normalized + traversal-rejected before it ever reaches a blob, and blob keys
238
+ * are content hashes, never the user path. Reads stream from the local
239
+ * BlobStore; the route never writes.
240
+ */
241
+ var PREFIX = "/api/artifact/";
242
+ var Route = createFileRoute("/api/artifact/$loopId/$")({ server: { handlers: { GET: async ({ request }) => {
243
+ const pathname = new URL(request.url).pathname;
244
+ if (!pathname.startsWith(PREFIX)) return Response.json({ error: "not found" }, { status: 404 });
245
+ const after = pathname.slice(14);
246
+ const slash = after.indexOf("/");
247
+ if (slash < 0) return Response.json({ error: "missing path" }, { status: 400 });
248
+ const loopId = safeDecode(after.slice(0, slash));
249
+ if (loopId === null) return Response.json({ error: "bad loop id" }, { status: 400 });
250
+ const segments = after.slice(slash + 1).split("/").map((s) => safeDecode(s));
251
+ if (segments.some((s) => s === null)) return Response.json({ error: "bad path" }, { status: 400 });
252
+ const relPath = segments.join("/");
253
+ if (!await (await import("./boot-CYRi-wez.mjs").then((n) => n.N).then((n) => n.It)).getLoop(loopId)) return Response.json({ error: "not found" }, { status: 404 });
254
+ const { readLoopArtifactBytes } = await import("./artifactFiles-Di1xDKvX.mjs");
255
+ const r = await readLoopArtifactBytes(loopId, relPath);
256
+ if (r.status !== 200 || !r.bytes) return Response.json({ error: "not found" }, { status: r.status });
257
+ const filename = (r.filename || "file").replace(/["\\\r\n]/g, "_");
258
+ const mime = new URL(request.url).searchParams.get("view") === "inline" ? imageMime(relPath) : null;
259
+ if (mime) return new Response(new Uint8Array(r.bytes), {
260
+ status: 200,
261
+ headers: {
262
+ "content-type": mime,
263
+ "content-length": String(r.bytes.length),
264
+ "content-disposition": `inline; filename="${filename}"`,
265
+ "x-content-type-options": "nosniff",
266
+ "content-security-policy": "sandbox",
267
+ "cache-control": "private, no-store"
268
+ }
269
+ });
270
+ return new Response(new Uint8Array(r.bytes), {
271
+ status: 200,
272
+ headers: {
273
+ "content-type": r.binary ? "application/octet-stream" : "text/plain; charset=utf-8",
274
+ "content-length": String(r.bytes.length),
275
+ "content-disposition": `attachment; filename="${filename}"`,
276
+ "cache-control": "private, no-store"
277
+ }
278
+ });
279
+ } } } });
280
+ var IndexRoute = Route$12.update({
281
+ id: "/",
282
+ path: "/",
283
+ getParentRoute: () => Route$9
284
+ });
285
+ var MachineReportRoute = Route$8.update({
286
+ id: "/machine/report",
287
+ path: "/machine/report",
288
+ getParentRoute: () => Route$9
289
+ });
290
+ var LoopsLoopIdRoute = Route$10.update({
291
+ id: "/loops/$loopId",
292
+ path: "/loops/$loopId",
293
+ getParentRoute: () => Route$9
294
+ });
295
+ var ApiReadyRoute = Route$7.update({
296
+ id: "/api/ready",
297
+ path: "/api/ready",
298
+ getParentRoute: () => Route$9
299
+ });
300
+ var ApiHealthRoute = Route$6.update({
301
+ id: "/api/health",
302
+ path: "/api/health",
303
+ getParentRoute: () => Route$9
304
+ });
305
+ var ApiMachineSyncRoute = Route$5.update({
306
+ id: "/api/machine/sync",
307
+ path: "/api/machine/sync",
308
+ getParentRoute: () => Route$9
309
+ });
310
+ var ApiMachineStatusRoute = Route$4.update({
311
+ id: "/api/machine/status",
312
+ path: "/api/machine/status",
313
+ getParentRoute: () => Route$9
314
+ });
315
+ var ApiMachinePollRoute = Route$3.update({
316
+ id: "/api/machine/poll",
317
+ path: "/api/machine/poll",
318
+ getParentRoute: () => Route$9
319
+ });
320
+ var ApiMachineCliRoute = Route$2.update({
321
+ id: "/api/machine/cli",
322
+ path: "/api/machine/cli",
323
+ getParentRoute: () => Route$9
324
+ });
325
+ var LoopsLoopIdRunsRunIdRoute = Route$11.update({
326
+ id: "/loops/$loopId_/runs/$runId",
327
+ path: "/loops/$loopId/runs/$runId",
328
+ getParentRoute: () => Route$9
329
+ });
330
+ var ApiMachineBlobHashRoute = Route$1.update({
331
+ id: "/api/machine/blob/$hash",
332
+ path: "/api/machine/blob/$hash",
333
+ getParentRoute: () => Route$9
334
+ });
335
+ var rootRouteChildren = {
336
+ IndexRoute,
337
+ ApiHealthRoute,
338
+ ApiReadyRoute,
339
+ LoopsLoopIdRoute,
340
+ MachineReportRoute,
341
+ ApiMachineCliRoute,
342
+ ApiMachinePollRoute,
343
+ ApiMachineStatusRoute,
344
+ ApiMachineSyncRoute,
345
+ ApiArtifactLoopIdSplatRoute: Route.update({
346
+ id: "/api/artifact/$loopId/$",
347
+ path: "/api/artifact/$loopId/$",
348
+ getParentRoute: () => Route$9
349
+ }),
350
+ ApiMachineBlobHashRoute,
351
+ LoopsLoopIdRunsRunIdRoute
352
+ };
353
+ var routeTree = Route$9._addFileChildren(rootRouteChildren)._addFileTypes();
354
+ function getRouter() {
355
+ return createRouter({
356
+ routeTree,
357
+ scrollRestoration: true,
358
+ defaultPreload: "intent"
359
+ });
360
+ }
361
+ //#endregion
362
+ export { getRouter };
@@ -0,0 +1,12 @@
1
+ import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
+ import { n as Route, t as DashboardView } from "./routes-DnE7XDe_.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-BSVBANI3.js
4
+ var import_jsx_runtime = require_jsx_runtime();
5
+ /** First-load failure screen — a calm retry instead of the router's default
6
+ * error dump. Only the initial loader can land here; the in-page poll is
7
+ * fetch-then-set and keeps stale data on a transient blip. */
8
+ function Home() {
9
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DashboardView, { initial: Route.useLoaderData() });
10
+ }
11
+ //#endregion
12
+ export { Home as component };