@kky42/pievo-server 2.2.0 → 2.4.1

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 (199) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/MachinesModal-CEddj7la.js +1 -0
  3. package/.output/public/assets/SignIn-BdkKWOSs.js +1 -0
  4. package/.output/public/assets/actionUi-BYBeYy7U.js +1 -0
  5. package/.output/public/assets/app-BAgHQVvr.css +2 -0
  6. package/.output/public/assets/index-BZLqD3JI.js +12 -0
  7. package/.output/public/assets/invite._token-BCLgmTO7.js +1 -0
  8. package/.output/public/assets/invite._token-ZnVQqqq4.js +1 -0
  9. package/.output/public/assets/lifecycleUi-C7dwHtbw.js +1 -0
  10. package/.output/public/assets/loops._loopId-X8PSlgdB.js +67 -0
  11. package/.output/public/assets/loops._loopId_.runs._runId-CA0-q1y5.js +3 -0
  12. package/.output/public/assets/preload-helper-CUIO87nJ.js +5 -0
  13. package/.output/public/assets/routes-BjKcDy6D.js +1 -0
  14. package/.output/public/assets/routes-bfMcArTT.js +1 -0
  15. package/.output/public/assets/t._teamId-B75J8h0x.js +1 -0
  16. package/.output/public/assets/t._teamId-D7WVPMDr.js +1 -0
  17. package/.output/public/assets/ui-DGOT8AoZ.js +1 -0
  18. package/.output/server/_libs/@base-ui/react+[...].mjs +4130 -4130
  19. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  20. package/.output/server/_libs/drizzle-orm+postgres.mjs +28 -1
  21. package/.output/server/_ssr/DashboardView-D_hKsCsv.mjs +1098 -0
  22. package/.output/server/_ssr/{notifyFns-DXMbgYu2.mjs → MachinesModal-DZ_9ErAU.mjs} +57 -35
  23. package/.output/server/_ssr/{SignIn-C4fSJl_j.mjs → SignIn-DjSrg0nO.mjs} +10 -24
  24. package/.output/server/_ssr/SocialLinks-BTjsnVOm.mjs +1051 -0
  25. package/.output/server/_ssr/{actionUi-BKF6xI3d.mjs → actionUi-D15R8L7R.mjs} +3 -3
  26. package/.output/server/_ssr/{artifactFiles-C8PQKN6V.mjs → artifactFiles-CV3OXxRD.mjs} +7 -8
  27. package/.output/server/_ssr/{artifactKind-BW0isV8L.mjs → artifactKind-ByzVa7fg.mjs} +27 -2
  28. package/.output/server/_ssr/artifacts-BhQvaGjF.mjs +2 -0
  29. package/.output/server/_ssr/{auth-Sie6sQHF.mjs → auth-CPCDf6Wr.mjs} +16 -19
  30. package/.output/server/_ssr/{boot-akwC2sQg.mjs → boot-BbWxaF-z.mjs} +1359 -3843
  31. package/.output/server/_ssr/boot-COHf8Gv3.mjs +2 -0
  32. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CLY8cORt.mjs → bun-sqlite-dialect-BW9W1_Ps-8MIh9jN1.mjs} +2 -2
  33. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-CRsXQNRe.mjs → d1-sqlite-dialect-BLC8LXE6-CcMQ01_d.mjs} +2 -2
  34. package/.output/server/_ssr/db-dw61xIt1.mjs +2 -0
  35. package/.output/server/_ssr/{dist-Cc_x6bBb.mjs → dist-AxCsRawm.mjs} +2 -2
  36. package/.output/server/_ssr/{factory-CDwHL_zY.mjs → factory-S7MaMVC2.mjs} +2 -2
  37. package/.output/server/_ssr/{invite._token-BlbVjz-o.mjs → invite._token-B1UgMbNQ.mjs} +3 -3
  38. package/.output/server/_ssr/{invite._token-BE_QUAVb.mjs → invite._token-BliJIGk4.mjs} +4 -5
  39. package/.output/server/_ssr/{invite._token-C7GjrJxr.mjs → invite._token-D296YVGd.mjs} +7 -7
  40. package/.output/server/_ssr/{kysely-adapter-zflsyYBA.mjs → kysely-adapter-DUdpqcCJ.mjs} +1 -1
  41. package/.output/server/_ssr/{lifecycleUi-Djb44Aj3.mjs → lifecycleUi-Dad3hvhm.mjs} +12 -11
  42. package/.output/server/_ssr/logger-DdUWuMdh.mjs +2 -0
  43. package/.output/server/_ssr/loopApi-Dy8ztANM.mjs +389 -0
  44. package/.output/server/_ssr/loopProjection-BZ1_IYCr.mjs +110 -0
  45. package/.output/server/_ssr/loops._loopId-CKsAnxMl.mjs +1255 -0
  46. package/.output/server/_ssr/{loops._loopId_.runs._runId-rBHW41dx.mjs → loops._loopId_.runs._runId-DG405pQs.mjs} +29 -74
  47. package/.output/server/_ssr/{loops._loopId_.runs._runId-Dk1QY49p.mjs → loops._loopId_.runs._runId-a_SyY8rX.mjs} +4 -5
  48. package/.output/server/_ssr/{machineFns-CoqLRLRh.mjs → machineFns-BSaE7K88.mjs} +3 -5
  49. package/.output/server/_ssr/{node-sqlite-dialect-dmrazfbM.mjs → node-sqlite-dialect-rWwVyt2J.mjs} +2 -2
  50. package/.output/server/_ssr/router-lMkobh5r.mjs +618 -0
  51. package/.output/server/_ssr/{routes-DebVUmCS.mjs → routes-CRHc9cLG.mjs} +6 -10
  52. package/.output/server/_ssr/{routes-Bx0bRxt9.mjs → routes-DNCZrs8z.mjs} +3 -3
  53. package/.output/server/_ssr/{routes-dQCBLTxY.mjs → routes-ufPbrGHs.mjs} +6 -6
  54. package/.output/server/_ssr/runDiff-CrZzjokj.mjs +2 -0
  55. package/.output/server/_ssr/ssr.mjs +65 -97
  56. package/.output/server/_ssr/store-CirPHuib.mjs +2 -0
  57. package/.output/server/_ssr/{t._teamId-BqOUF-0J.mjs → t._teamId-BoBoYEHL.mjs} +7 -7
  58. package/.output/server/_ssr/{t._teamId-D9nCEa2h.mjs → t._teamId-DcWOYjPf.mjs} +4 -4
  59. package/.output/server/_ssr/{t._teamId-BzWy4Ft2.mjs → t._teamId-QD2nN0wn.mjs} +7 -11
  60. package/.output/server/_ssr/{teamFns-BcnGMC-g.mjs → teamFns-BsmconJ1.mjs} +4 -4
  61. package/.output/server/_ssr/{teamFns-bm82twd5.mjs → teamFns-DcoVuWyH.mjs} +2 -2
  62. package/.output/server/_ssr/tokens-XngoyCU8.mjs +2 -0
  63. package/.output/server/_ssr/{ui-B6mE5-5v.mjs → ui-DmFKW2H_.mjs} +33 -47
  64. package/.output/server/{_tanstack-start-manifest_v-COr27bxK.mjs → _tanstack-start-manifest_v-Xd5bMpJe.mjs} +25 -33
  65. package/.output/server/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
  66. package/.output/server/drizzle/meta/_journal.json +3 -115
  67. package/.output/server/index.mjs +82 -131
  68. package/drizzle/{0000_round_marauders.sql → 0000_baseline.sql} +102 -44
  69. package/drizzle/meta/_journal.json +3 -115
  70. package/package.json +2 -10
  71. package/scripts/pievo-server.mjs +3 -1
  72. package/scripts/server-cli-lib.mjs +23 -16
  73. package/.output/public/assets/CodeField-dOzyFQNH.js +0 -34
  74. package/.output/public/assets/LoopView-BTXoqfzM.js +0 -59
  75. package/.output/public/assets/Modal-Ba36NAkv.js +0 -1
  76. package/.output/public/assets/SignIn-Cc1zxGnA.js +0 -1
  77. package/.output/public/assets/actionUi-DDDfD0WX.js +0 -1
  78. package/.output/public/assets/app-D1cij_NJ.css +0 -2
  79. package/.output/public/assets/artifactView-Cd5wWPil.js +0 -66
  80. package/.output/public/assets/index-BGM1XGY0.js +0 -12
  81. package/.output/public/assets/invite._token-C3t4JfAY.js +0 -1
  82. package/.output/public/assets/invite._token-CUCNcN89.js +0 -1
  83. package/.output/public/assets/jsx-runtime-BseJUIpC.js +0 -1
  84. package/.output/public/assets/lifecycleUi-CMFw5Nlj.js +0 -1
  85. package/.output/public/assets/loopApi-CT1mMhlb.js +0 -3
  86. package/.output/public/assets/loops._loopId-CrSSvSVl.js +0 -4
  87. package/.output/public/assets/loops._loopId_.runs._runId-B5Qacloe.js +0 -3
  88. package/.output/public/assets/notifyFns-CYiZT7CK.js +0 -1
  89. package/.output/public/assets/preload-helper-DFkkpP6W.js +0 -3
  90. package/.output/public/assets/routes-B6Kg4mFC.js +0 -1
  91. package/.output/public/assets/routes-DsdcURfk.js +0 -1
  92. package/.output/public/assets/t._teamId-C9uEyAh_.js +0 -1
  93. package/.output/public/assets/t._teamId-Cmm4xdKj.js +0 -1
  94. package/.output/public/assets/ui-Gb1413-F.js +0 -1
  95. package/.output/public/assets/useRouter-DsjH8G0p.js +0 -1
  96. package/.output/server/_libs/@codemirror/autocomplete+[...].mjs +0 -19713
  97. package/.output/server/_libs/@codemirror/lang-css+[...].mjs +0 -2636
  98. package/.output/server/_libs/@codemirror/lang-html+[...].mjs +0 -2247
  99. package/.output/server/_libs/@codemirror/lang-json+[...].mjs +0 -79
  100. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +0 -1736
  101. package/.output/server/_libs/@uiw/codemirror-extensions-basic-setup+[...].mjs +0 -76
  102. package/.output/server/_libs/babel__runtime.mjs +0 -30
  103. package/.output/server/_libs/clsx.mjs +0 -16
  104. package/.output/server/_libs/codemirror__commands.mjs +0 -1725
  105. package/.output/server/_libs/codemirror__lint.mjs +0 -565
  106. package/.output/server/_libs/codemirror__search.mjs +0 -1138
  107. package/.output/server/_libs/codemirror__theme-one-dark.mjs +0 -161
  108. package/.output/server/_libs/d3-array.mjs +0 -231
  109. package/.output/server/_libs/d3-color.mjs +0 -330
  110. package/.output/server/_libs/d3-format.mjs +0 -252
  111. package/.output/server/_libs/d3-interpolate.mjs +0 -163
  112. package/.output/server/_libs/d3-path.mjs +0 -77
  113. package/.output/server/_libs/d3-scale+[...].mjs +0 -1907
  114. package/.output/server/_libs/d3-shape.mjs +0 -818
  115. package/.output/server/_libs/decimal.js-light.mjs +0 -884
  116. package/.output/server/_libs/dom-serializer+[...].mjs +0 -708
  117. package/.output/server/_libs/domhandler.mjs +0 -461
  118. package/.output/server/_libs/domutils.mjs +0 -868
  119. package/.output/server/_libs/es-toolkit.mjs +0 -1737
  120. package/.output/server/_libs/eventemitter3.mjs +0 -236
  121. package/.output/server/_libs/html-dom-parser+htmlparser2.mjs +0 -1560
  122. package/.output/server/_libs/html-react-parser+[...].mjs +0 -1360
  123. package/.output/server/_libs/react-is.mjs +0 -48
  124. package/.output/server/_libs/react-redux+[...].mjs +0 -205
  125. package/.output/server/_libs/recharts+victory-vendor.mjs +0 -17303
  126. package/.output/server/_libs/uiw__codemirror-themes.mjs +0 -32
  127. package/.output/server/_libs/uiw__react-codemirror.mjs +0 -354
  128. package/.output/server/_ssr/CodeField-B7oOmmwH.mjs +0 -143
  129. package/.output/server/_ssr/DashboardView-0yoKh8Em.mjs +0 -2733
  130. package/.output/server/_ssr/LoopPlaybook-uq_sFfgU.mjs +0 -2105
  131. package/.output/server/_ssr/LoopView-BlOY-jfJ.mjs +0 -1300
  132. package/.output/server/_ssr/Modal-Bc_5TYGr.mjs +0 -49
  133. package/.output/server/_ssr/adapters-RReIIwAx.mjs +0 -134
  134. package/.output/server/_ssr/artifactView-iuPsp5aa.mjs +0 -304
  135. package/.output/server/_ssr/artifacts-B_F6vSih.mjs +0 -2
  136. package/.output/server/_ssr/boot-pVS0wjd0.mjs +0 -2
  137. package/.output/server/_ssr/db-Bdm9PFZF.mjs +0 -2
  138. package/.output/server/_ssr/logger-tfiliKTi.mjs +0 -2
  139. package/.output/server/_ssr/loopApi-8_KgBapr.mjs +0 -109
  140. package/.output/server/_ssr/loopApi-JzmqWENY.mjs +0 -564
  141. package/.output/server/_ssr/loops._loopId-D-aCCtrX.mjs +0 -1477
  142. package/.output/server/_ssr/loops._loopId-DZ-FjscA.mjs +0 -30
  143. package/.output/server/_ssr/notifyFns-CH7-bcmd.mjs +0 -118
  144. package/.output/server/_ssr/router-BFPzSTFx.mjs +0 -679
  145. package/.output/server/_ssr/runDiff-qA1C8R_L.mjs +0 -2
  146. package/.output/server/_ssr/store-DWUdkIq2.mjs +0 -2
  147. package/.output/server/_ssr/tokens-DG8z1GIg.mjs +0 -2
  148. package/.output/server/drizzle/0001_naive_silver_fox.sql +0 -25
  149. package/.output/server/drizzle/0002_funny_sage.sql +0 -12
  150. package/.output/server/drizzle/0003_optimal_slapstick.sql +0 -102
  151. package/.output/server/drizzle/0004_remove_grok_agent.sql +0 -5
  152. package/.output/server/drizzle/0005_heavy_gauntlet.sql +0 -8
  153. package/.output/server/drizzle/0006_tranquil_sharon_carter.sql +0 -1
  154. package/.output/server/drizzle/0007_faulty_starbolt.sql +0 -30
  155. package/.output/server/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
  156. package/.output/server/drizzle/0009_ordinary_electro.sql +0 -1
  157. package/.output/server/drizzle/0010_workable_rage.sql +0 -14
  158. package/.output/server/drizzle/0011_steep_marvel_zombies.sql +0 -3
  159. package/.output/server/drizzle/0012_blocked_run_status.sql +0 -12
  160. package/.output/server/drizzle/0013_drop_run_outcome.sql +0 -2
  161. package/.output/server/drizzle/0014_luxuriant_scrambler.sql +0 -5
  162. package/.output/server/drizzle/0015_run_history_foundation.sql +0 -43
  163. package/.output/server/drizzle/0016_plural_machine_runs.sql +0 -4
  164. package/.output/server/drizzle/meta/0000_snapshot.json +0 -1333
  165. package/.output/server/drizzle/meta/0001_snapshot.json +0 -1498
  166. package/.output/server/drizzle/meta/0002_snapshot.json +0 -1576
  167. package/.output/server/drizzle/meta/0003_snapshot.json +0 -1666
  168. package/.output/server/drizzle/meta/0005_snapshot.json +0 -1666
  169. package/.output/server/drizzle/meta/0006_snapshot.json +0 -1672
  170. package/.output/server/drizzle/meta/0007_snapshot.json +0 -1787
  171. package/.output/server/drizzle/meta/0009_snapshot.json +0 -1793
  172. package/.output/server/drizzle/meta/0010_snapshot.json +0 -1891
  173. package/.output/server/drizzle/meta/0011_snapshot.json +0 -1872
  174. package/drizzle/0001_naive_silver_fox.sql +0 -25
  175. package/drizzle/0002_funny_sage.sql +0 -12
  176. package/drizzle/0003_optimal_slapstick.sql +0 -102
  177. package/drizzle/0004_remove_grok_agent.sql +0 -5
  178. package/drizzle/0005_heavy_gauntlet.sql +0 -8
  179. package/drizzle/0006_tranquil_sharon_carter.sql +0 -1
  180. package/drizzle/0007_faulty_starbolt.sql +0 -30
  181. package/drizzle/0008_durable_lifecycle_evidence.sql +0 -15
  182. package/drizzle/0009_ordinary_electro.sql +0 -1
  183. package/drizzle/0010_workable_rage.sql +0 -14
  184. package/drizzle/0011_steep_marvel_zombies.sql +0 -3
  185. package/drizzle/0012_blocked_run_status.sql +0 -12
  186. package/drizzle/0013_drop_run_outcome.sql +0 -2
  187. package/drizzle/0014_luxuriant_scrambler.sql +0 -5
  188. package/drizzle/0015_run_history_foundation.sql +0 -43
  189. package/drizzle/0016_plural_machine_runs.sql +0 -4
  190. package/drizzle/meta/0000_snapshot.json +0 -1333
  191. package/drizzle/meta/0001_snapshot.json +0 -1498
  192. package/drizzle/meta/0002_snapshot.json +0 -1576
  193. package/drizzle/meta/0003_snapshot.json +0 -1666
  194. package/drizzle/meta/0005_snapshot.json +0 -1666
  195. package/drizzle/meta/0006_snapshot.json +0 -1672
  196. package/drizzle/meta/0007_snapshot.json +0 -1787
  197. package/drizzle/meta/0009_snapshot.json +0 -1793
  198. package/drizzle/meta/0010_snapshot.json +0 -1891
  199. package/drizzle/meta/0011_snapshot.json +0 -1872
@@ -0,0 +1,618 @@
1
+ import { j as sql } from "../_libs/drizzle-orm+postgres.mjs";
2
+ import { A as ensureServer, Dt as readJsonBody, Et as readByteBody, Yt as validOptionalPollString, l as MACHINE_BODY_CAP } from "./boot-BbWxaF-z.mjs";
3
+ import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
4
+ import { c as createFileRoute, i as HeadContent, l as createRootRoute, o as createRouter, r as Scripts } from "../_libs/@tanstack/react-router+[...].mjs";
5
+ import { t as Route$14 } from "./invite._token-BliJIGk4.mjs";
6
+ import { r as imageMime, t as Route$15 } from "./artifactKind-ByzVa7fg.mjs";
7
+ import { t as Route$16 } from "./loops._loopId_.runs._runId-a_SyY8rX.mjs";
8
+ import { t as Route$17 } from "./routes-CRHc9cLG.mjs";
9
+ import { t as Route$18 } from "./t._teamId-QD2nN0wn.mjs";
10
+ //#region node_modules/.nitro/vite/services/ssr/assets/router-lMkobh5r.js
11
+ var import_jsx_runtime = require_jsx_runtime();
12
+ var app_default = "/assets/app-BAgHQVvr.css";
13
+ var Route$13 = createRootRoute({
14
+ head: () => ({
15
+ meta: [
16
+ { charSet: "utf-8" },
17
+ {
18
+ name: "viewport",
19
+ content: "width=device-width, initial-scale=1"
20
+ },
21
+ { title: "Pievo" }
22
+ ],
23
+ links: [{
24
+ rel: "stylesheet",
25
+ href: app_default
26
+ }, {
27
+ rel: "icon",
28
+ type: "image/svg+xml",
29
+ href: "/favicon.svg"
30
+ }]
31
+ }),
32
+ shellComponent: RootDocument
33
+ });
34
+ function RootDocument({ children }) {
35
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("html", {
36
+ lang: "en",
37
+ suppressHydrationWarning: true,
38
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("head", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadContent, {}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("body", {
39
+ className: "dot-grid-subtle bg-paper font-sans text-primary antialiased",
40
+ suppressHydrationWarning: true,
41
+ children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scripts, {})]
42
+ })]
43
+ });
44
+ }
45
+ var TokenBucketLimiter = class {
46
+ capacity;
47
+ refillPerSec;
48
+ maxKeys;
49
+ buckets = /* @__PURE__ */ new Map();
50
+ constructor(capacity, refillPerSec, maxKeys = 2e4) {
51
+ this.capacity = capacity;
52
+ this.refillPerSec = refillPerSec;
53
+ this.maxKeys = maxKeys;
54
+ }
55
+ /** Consume one token for `key`. Returns true if allowed, false if the bucket is dry. */
56
+ allow(key, now = Date.now()) {
57
+ let b = this.buckets.get(key);
58
+ if (!b) {
59
+ if (this.buckets.size >= this.maxKeys) this.evictOldest();
60
+ b = {
61
+ tokens: this.capacity,
62
+ last: now
63
+ };
64
+ this.buckets.set(key, b);
65
+ }
66
+ const elapsedSec = Math.max(0, (now - b.last) / 1e3);
67
+ b.tokens = Math.min(this.capacity, b.tokens + elapsedSec * this.refillPerSec);
68
+ b.last = now;
69
+ if (b.tokens >= 1) {
70
+ b.tokens -= 1;
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+ /** Drop the least-recently-touched key (bounded memory under a distinct-key flood). */
76
+ evictOldest() {
77
+ let oldestKey;
78
+ let oldest = Number.POSITIVE_INFINITY;
79
+ for (const [k, b] of this.buckets) if (b.last < oldest) {
80
+ oldest = b.last;
81
+ oldestKey = k;
82
+ }
83
+ if (oldestKey !== void 0) this.buckets.delete(oldestKey);
84
+ }
85
+ /** Test seam — forget all state. */
86
+ reset() {
87
+ this.buckets.clear();
88
+ }
89
+ };
90
+ /** Parse a positive-number env override, falling back to `dflt` on absent/invalid. */
91
+ function envNum(name, dflt) {
92
+ const raw = process.env[name]?.trim();
93
+ if (!raw) return dflt;
94
+ const n = Number(raw);
95
+ return Number.isFinite(n) && n > 0 ? n : dflt;
96
+ }
97
+ /**
98
+ * Per-IP: default 240 burst + 8/sec sustained (≈480 req/min). One daemon does
99
+ * ~20 polls/min plus sync bursts; this comfortably covers a dozen daemons behind a
100
+ * shared NAT while still capping a single-IP flood.
101
+ */
102
+ var ipLimiter = new TokenBucketLimiter(envNum("PIEVO_RL_IP_BURST", 240), envNum("PIEVO_RL_IP_PER_SEC", 8));
103
+ /**
104
+ * Per-token (per-machine): default 120 burst + 4/sec sustained. A single daemon
105
+ * never approaches this; it bounds one credential's share of the process.
106
+ */
107
+ var tokenLimiter = new TokenBucketLimiter(envNum("PIEVO_RL_TOKEN_BURST", 120), envNum("PIEVO_RL_TOKEN_PER_SEC", 4));
108
+ /** Whether rate limiting is active. Off by default in tests unless opted in, so the
109
+ * large existing suites don't trip it; ON in a real server process. */
110
+ function rateLimitEnabled() {
111
+ const flag = process.env.PIEVO_RATE_LIMIT?.trim().toLowerCase();
112
+ if (flag === "0" || flag === "false" || flag === "off") return false;
113
+ if (flag === "1" || flag === "true" || flag === "on") return true;
114
+ return process.env.VITEST !== "true";
115
+ }
116
+ /**
117
+ * Best-effort client IP from the request, honoring the usual proxy headers (Fly
118
+ * sets `Fly-Client-IP`; a generic proxy sets `X-Forwarded-For`). Falls back to a
119
+ * single shared key so a missing IP still shares ONE bucket (fail-closed: unknown
120
+ * origins can't each get a private allowance). Only the FIRST `X-Forwarded-For` hop
121
+ * is trusted (the client-facing edge appends left-to-right).
122
+ */
123
+ function clientIp(request) {
124
+ const h = request.headers;
125
+ const fly = h.get("fly-client-ip");
126
+ if (fly) return fly.trim();
127
+ const xff = h.get("x-forwarded-for");
128
+ if (xff) {
129
+ const first = xff.split(",")[0]?.trim();
130
+ if (first) return first;
131
+ }
132
+ const real = h.get("x-real-ip");
133
+ if (real) return real.trim();
134
+ return "unknown";
135
+ }
136
+ /**
137
+ * A 429 response with a Retry-After hint. Carries `text` + `exitCode` alongside
138
+ * `error` so `/api/machine/cli` can print the response directly.
139
+ */
140
+ function tooMany() {
141
+ return Response.json({
142
+ error: "rate limited — slow down",
143
+ text: "rate limited — slow down",
144
+ exitCode: 1
145
+ }, {
146
+ status: 429,
147
+ headers: { "retry-after": "1" }
148
+ });
149
+ }
150
+ /**
151
+ * Enforce the machine-route rate limits for one request. Call it at the top of a
152
+ * machine route handler, right after reading the Bearer token (pass it so a valid
153
+ * daemon keys off its own per-machine bucket). Returns a 429 `Response` to return
154
+ * verbatim, or null to proceed. Both tiers apply: the per-IP flood guard always,
155
+ * and the per-token fairness tier whenever a credential is present. (The byte-
156
+ * ingress routes are exempt entirely and simply do not call this — see the module
157
+ * doc.)
158
+ */
159
+ function machineRouteLimit(request, token, opts = {}) {
160
+ if (!rateLimitEnabled()) return null;
161
+ const now = opts.now ?? Date.now();
162
+ if (!ipLimiter.allow(clientIp(request), now)) return tooMany();
163
+ if (token && !tokenLimiter.allow(token, now)) return tooMany();
164
+ return null;
165
+ }
166
+ /** POST /machine/report — finalize a run (Bearer run token). */
167
+ var Route$12 = createFileRoute("/machine/report")({ server: { handlers: { POST: async ({ request }) => {
168
+ const auth = request.headers.get("authorization") ?? "";
169
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
170
+ const limited = machineRouteLimit(request, token || void 0);
171
+ if (limited) return limited;
172
+ if (!token) return Response.json({ error: "missing token" }, { status: 401 });
173
+ const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
174
+ if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
175
+ const body = parsed.kind === "ok" && parsed.body && typeof parsed.body === "object" ? parsed.body : {};
176
+ const { getGateway } = await import("./boot-COHf8Gv3.mjs");
177
+ const r = await (await getGateway()).report(token, body);
178
+ return Response.json(r.body, { status: r.status });
179
+ } } } });
180
+ /**
181
+ * Deep readiness for process launchers. Unlike /api/health, this waits for the
182
+ * one-time backend boot (migrations, blob-store selection, and scheduler start).
183
+ * The per-launch nonce lets a launcher reject a different process already bound
184
+ * to the requested port.
185
+ */
186
+ async function readyResponse(ensure = ensureServer, env = process.env) {
187
+ await ensure();
188
+ return Response.json({
189
+ ok: true,
190
+ nonce: env.PIEVO_SERVER_LAUNCH_NONCE ?? null
191
+ });
192
+ }
193
+ var Route$11 = createFileRoute("/api/ready")({ server: { handlers: { GET: () => readyResponse() } } });
194
+ var buildSha = () => process.env.GIT_SHA || "unknown";
195
+ var buildAt = () => process.env.BUILT_AT || "unknown";
196
+ var Route$10 = createFileRoute("/api/health")({ server: { handlers: { GET: async () => Response.json({
197
+ ok: true,
198
+ sha: buildSha(),
199
+ builtAt: buildAt()
200
+ }) } } });
201
+ var bootstrap_default = "# Pievo — connect this machine and create a scheduled prompt\n\nYou're reading this because the user pasted a Pievo capture snippet (`Fetch\n<server-url>/api/bootstrap and help me build a loop.`). Their Pievo web tab is waiting\nfor the machine and new loop. Work end to end, with short confirmations rather than a\nlong interview.\n\n## Pasted values\n\nUse these values exactly as supplied:\n\n- **server-url** — the Pievo server base URL.\n- **connect-key** — the one-time machine connection and web-dialog claim.\n- **pievo-cli** *(optional)* — the command prefix for every Pievo invocation. If it is\n absent, use `pievo` after installing the current package.\n- An optional task description below the values — the requested scheduled prompt.\n\n## 1 · Connect\n\nFetch and follow the connection reference:\n\n```text\n<server-url>/api/skill/references/connect.md\n```\n\nPass the pasted `server-url` and `connect-key` to its daemon start command. Do not\nreplace a supplied custom `pievo-cli` prefix.\n\n## 2 · Create\n\nAfter the daemon reports the machine online, fetch and follow:\n\n```text\n<server-url>/api/skill/references/create.md\n```\n\nThe create reference owns the remaining flow: identify the concrete prompt, confirm\nits exclusive cron or continuous schedule and three status definitions, choose the\nworking directory and coding agent, optionally select exact artifact files, preview the\ncanonical JSON, and run `pievo new`. On this first-capture path, pass the same\n`connect-key` to `pievo new` so the created loop appears in the waiting web dialog.\n\nPievo is a scheduled prompt runner. Configure only the fields listed in the create\nreference. The stored user prompt is delivered unchanged, followed only by Pievo's\ncomplete status/report contract.\n";
202
+ /** GET /api/bootstrap — the first-capture bootstrap doc Claude Code follows (see ComposeModal). */
203
+ var Route$9 = createFileRoute("/api/bootstrap")({ server: { handlers: { GET: () => new Response(bootstrap_default, { headers: {
204
+ "content-type": "text/markdown; charset=utf-8",
205
+ "cache-control": "no-cache"
206
+ } }) } } });
207
+ /**
208
+ * POST /api/machine/sync — exact artifact sync (Bearer DEVICE token). The daemon
209
+ * posts the complete configured-path manifest after a run; the server reconciles
210
+ * artifact_files and replies with needHashes for raw blob PUTs.
211
+ * The manifest route retains its dedicated 32 MB JSON-body limit.
212
+ *
213
+ * This byte-ingress route is deliberately NOT rate limited (same rationale as the
214
+ * blob PUT): it requires a valid registered device token (unknown ⇒ 401) and is
215
+ * already bounded by the exact-path/hash handshake and 32MB sync-body cap, so a
216
+ * limiter would only throttle a legitimate large sync.
217
+ */
218
+ var Route$8 = createFileRoute("/api/machine/sync")({ server: { handlers: { POST: async ({ request }) => {
219
+ const auth = request.headers.get("authorization") ?? "";
220
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
221
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
222
+ const { SYNC_BODY_CAP, parseArtifactSyncBody } = await import("./artifacts-BhQvaGjF.mjs");
223
+ const parsed = await readJsonBody(request, SYNC_BODY_CAP);
224
+ if (parsed.kind === "too-large") return Response.json({ error: "sync body too large" }, { status: 413 });
225
+ if (parsed.kind === "invalid") return Response.json({ error: "invalid JSON" }, { status: 400 });
226
+ const body = parseArtifactSyncBody(parsed.body);
227
+ if (!body) return Response.json({ error: "invalid sync body" }, { status: 400 });
228
+ const { getArtifactSync } = await import("./boot-COHf8Gv3.mjs");
229
+ const r = await (await getArtifactSync()).sync(token, body);
230
+ return Response.json(r.body, { status: r.status });
231
+ } } } });
232
+ /** GET /api/machine/status — is this machine's daemon live? (Bearer device token) */
233
+ var Route$7 = createFileRoute("/api/machine/status")({ server: { handlers: { GET: async ({ request }) => {
234
+ const auth = request.headers.get("authorization") ?? "";
235
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
236
+ const limited = machineRouteLimit(request, token || void 0);
237
+ if (limited) return limited;
238
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
239
+ const { getGateway } = await import("./boot-COHf8Gv3.mjs");
240
+ const r = await (await getGateway()).status(token);
241
+ return Response.json(r.body, { status: r.status });
242
+ } } } });
243
+ var POLL_BODY_FIELDS = new Set([
244
+ "host",
245
+ "platform",
246
+ "arch",
247
+ "version",
248
+ "protocolVersion",
249
+ "currentRuns",
250
+ "daemonInstanceId",
251
+ "recoveryComplete"
252
+ ]);
253
+ var validWireId = (value) => typeof value === "string" && value.length <= 200 && value.trim().length > 0 && !value.includes("\0");
254
+ var validCurrentRun = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 2 && Object.prototype.hasOwnProperty.call(value, "runId") && Object.prototype.hasOwnProperty.call(value, "stage") && validWireId(value.runId) && (value.stage === "executing" || value.stage === "reporting");
255
+ /** Reject non-object bodies, additions, coercible types, over-cap strings, and
256
+ * NUL-bearing text before the gateway is loaded or any state can mutate. */
257
+ function parsePollBody(value) {
258
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
259
+ if (Object.keys(value).some((key) => !POLL_BODY_FIELDS.has(key))) return null;
260
+ const body = value;
261
+ if (!validOptionalPollString(body.host, 524288) || !validOptionalPollString(body.platform, 524288) || !validOptionalPollString(body.arch, 524288) || !validOptionalPollString(body.version, 64)) return null;
262
+ if (body.protocolVersion !== void 0 && (typeof body.protocolVersion !== "number" || !Number.isInteger(body.protocolVersion))) return null;
263
+ if (body.currentRuns !== void 0 && (!Array.isArray(body.currentRuns) || body.currentRuns.some((run) => !validCurrentRun(run)))) return null;
264
+ if (body.daemonInstanceId !== void 0 && !validWireId(body.daemonInstanceId)) return null;
265
+ if (body.recoveryComplete !== void 0 && typeof body.recoveryComplete !== "boolean") return null;
266
+ return body;
267
+ }
268
+ /** Keep the HTTP adapter's protocol forwarding independently testable. */
269
+ function gatewayPollRequest(body) {
270
+ return {
271
+ protocolVersion: body.protocolVersion,
272
+ currentRuns: body.currentRuns,
273
+ daemonInstanceId: body.daemonInstanceId,
274
+ recoveryComplete: body.recoveryComplete,
275
+ info: {
276
+ host: body.host,
277
+ platform: body.platform,
278
+ arch: body.arch,
279
+ version: body.version
280
+ }
281
+ };
282
+ }
283
+ /** POST /api/machine/poll — daemon claims this machine's pending runs (Bearer device token). */
284
+ var Route$6 = createFileRoute("/api/machine/poll")({ server: { handlers: { POST: async ({ request }) => {
285
+ const auth = request.headers.get("authorization") ?? "";
286
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
287
+ const limited = machineRouteLimit(request, token || void 0);
288
+ if (limited) return limited;
289
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
290
+ const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
291
+ if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
292
+ if (parsed.kind !== "ok") return Response.json({ error: "invalid JSON body" }, { status: 400 });
293
+ const body = parsePollBody(parsed.body);
294
+ if (!body) return Response.json({ error: "unknown or invalid poll body fields" }, { status: 400 });
295
+ const { getGateway } = await import("./boot-COHf8Gv3.mjs");
296
+ const r = await (await getGateway()).pollV4Wait(token, gatewayPollRequest(body));
297
+ return Response.json(r.body, { status: r.status });
298
+ } } } });
299
+ function parseCliBody(value) {
300
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
301
+ const body = value;
302
+ if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "argv")) return null;
303
+ if (!Array.isArray(body.argv) || body.argv.some((entry) => typeof entry !== "string")) return null;
304
+ return { argv: body.argv };
305
+ }
306
+ /**
307
+ * POST /api/machine/cli — the ONE unified CLI dispatch (Bearer credential + `{argv}`).
308
+ * `CliGateway.cli()` branches by credential type: a `dk_` device token takes owner
309
+ * commands, while an `rk_` run credential can only report its run. Uses the standard
310
+ * 2MB machine JSON body cap.
311
+ */
312
+ var Route$5 = createFileRoute("/api/machine/cli")({ server: { handlers: { POST: async ({ request }) => {
313
+ const auth = request.headers.get("authorization") ?? "";
314
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
315
+ const limited = machineRouteLimit(request, token || void 0);
316
+ if (limited) return limited;
317
+ if (!token) return Response.json({ error: "missing credential" }, { status: 401 });
318
+ const parsed = await readJsonBody(request, MACHINE_BODY_CAP);
319
+ if (parsed.kind === "too-large") return Response.json({ error: "body too large" }, { status: 413 });
320
+ const body = parsed.kind === "ok" ? parseCliBody(parsed.body) : null;
321
+ if (!body) return Response.json({ error: "body must be exactly {argv:string[]}" }, { status: 400 });
322
+ const { getCliGateway } = await import("./boot-COHf8Gv3.mjs");
323
+ const r = await (await getCliGateway()).cli(token, body.argv);
324
+ return Response.json(r.body, { status: r.status });
325
+ } } } });
326
+ /**
327
+ * DB-touching liveness probe — the ONE home for the wedged-pool incident rationale
328
+ * (db/index.ts and the Fly checks point here). Unlike /api/health (which never
329
+ * touches the DB and so reported `ok` right through a ~5.5h outage while a dead
330
+ * postgres-js pool masked it), this runs a trivial `select 1` through the runtime
331
+ * pool.
332
+ *
333
+ * This route SURFACES a wedged pool (Fly's check goes critical, which de-routes the
334
+ * machine); it does NOT by itself recover it. Fly's failing `[http_service.checks]`
335
+ * only pull the machine from load balancing — they never restart the VM (the
336
+ * 2026-07-12 outage: pool wedged, check critical, box down ~9h with no auto-restart
337
+ * until a manual `fly machine restart`). The ACTUAL auto-recovery is the in-process
338
+ * DB watchdog (`server/dbWatchdog.ts`), which exits the process on a sustained wedge
339
+ * so Fly's `restart.policy = "on-failure"` brings up a fresh pool.
340
+ *
341
+ * A wedged pool makes `select 1` HANG (queued behind stuck connections; the pool's
342
+ * `statement_timeout` is unenforced under the Supabase transaction pooler). So the
343
+ * probe races the query against a hard client-side deadline (`PROBE_TIMEOUT_MS`, <
344
+ * Fly's 10s check timeout) and returns a fast 503 instead of eating the full timeout
345
+ * "awaiting headers". 200 → DB reachable; 503 → the query failed or timed out.
346
+ * Drizzle imported inside the handler so it stays out of the client bundle.
347
+ */
348
+ var PROBE_TIMEOUT_MS = 5e3;
349
+ var Route$4 = createFileRoute("/api/health/db")({ server: { handlers: { GET: async () => {
350
+ try {
351
+ const { db } = await import("./db-dw61xIt1.mjs");
352
+ const ping = db.execute(sql`select 1`);
353
+ const timeout = new Promise((_, reject) => {
354
+ setTimeout(() => reject(/* @__PURE__ */ new Error(`db ping timed out after ${PROBE_TIMEOUT_MS}ms`)), PROBE_TIMEOUT_MS).unref?.();
355
+ });
356
+ await Promise.race([ping, timeout]);
357
+ return Response.json({
358
+ ok: true,
359
+ db: "up"
360
+ });
361
+ } catch (err) {
362
+ return Response.json({
363
+ ok: false,
364
+ db: "down",
365
+ error: err instanceof Error ? err.message : String(err)
366
+ }, { status: 503 });
367
+ }
368
+ } } } });
369
+ /** Better Auth handler — GET/POST /api/auth/* (OAuth callbacks, session, sign-out). */
370
+ var Route$3 = createFileRoute("/api/auth/$")({ server: { handlers: {
371
+ GET: async ({ request }) => (await import("./auth-CPCDf6Wr.mjs")).auth.handler(request),
372
+ POST: async ({ request }) => (await import("./auth-CPCDf6Wr.mjs")).auth.handler(request)
373
+ } } });
374
+ var REFERENCES = {
375
+ "connect.md": "# Connect a machine\n\nUse the **pievo-cli** command prefix supplied by the user; otherwise use `pievo`.\nA first-capture snippet supplies a **server-url** and one-time **connect-key**. Use\nboth verbatim.\n\nIf no custom command prefix was supplied, install the current CLI:\n\n```bash\nnpm install -g @kky42/pievo@latest\n```\n\nStart the daemon:\n\n```bash\n<pievo-cli> daemon start --server-url <server-url> --connect-key <connect-key>\n```\n\nThe command is idempotent: it reuses this machine's identity, starts one detached\ndaemon if needed, and waits for the server to see it online. It exits successfully\nwith `daemon online …` or `daemon already running …`. If it cannot connect, follow\nthe printed log path rather than guessing credentials or server settings.\n\nOnce connected, continue with `create.md` when the user wants a new loop. Existing\nconnections normally need no server URL or key on later `pievo` commands.\n",
376
+ "create.md": "# Create a loop\n\nA Pievo loop runs one server-stored prompt with one selected coding agent in a fixed\nworking directory. Use the **pievo-cli** prefix supplied by the user (default `pievo`).\nIf this machine is not connected, follow `connect.md` first.\n\n## 1 · Identify the prompt\n\nStart from concrete evidence in the current session:\n\n- If the user just completed or described a recurring task, turn that task into a\n standalone prompt grounded in its real paths, commands, URLs, constraints, and\n expected result.\n- If there is no task yet, inspect the project, propose a few useful scheduled prompts,\n and let the user choose. Do not invent and create one silently.\n\nThe stored `prompt` is sent unchanged. Make it self-contained for a fresh coding-agent\nprocess, but do not add Pievo runtime instructions: the server appends the complete\nstatus definitions and required `pievo report` command.\n\n## 2 · Confirm schedule and outcomes\n\nBefore creating, propose any missing choices in one short message and get confirmation:\n\n- **Schedule:** use cron for wall-clock work or continuous for a fixed delay after the\n prior run finishes. For cron, confirm the timezone and whether an occurrence while a\n run is still open should `skip` or `queue-one` coalesced follow-up. Continuous never\n overlaps.\n- **Statuses:** define what `keep`, `no-change`, and `block` mean for this task. All\n three definitions are required and must be non-empty. `block` means owner attention\n is required and pauses the loop.\n- **Artifacts:** if the user wants files visible in Pievo, list exact paths relative to\n `workdir`. Use stable filenames. Globs, absolute paths, traversal, and directories\n are not supported. Missing or unreadable files do not fail a run.\n\n## 3 · Author the canonical config\n\nCron example:\n\n```json\n{\n \"name\": \"Daily dependency check\",\n \"schedule\": {\n \"mode\": \"cron\",\n \"cron\": \"0 9 * * *\",\n \"timezone\": \"America/Los_Angeles\",\n \"overlap\": \"skip\"\n },\n \"workdir\": \"/absolute/path/to/project\",\n \"agent\": \"claude-code\",\n \"model\": null,\n \"reasoningEffort\": null,\n \"prompt\": \"Inspect the repository's open dependency updates. Verify each update against its exact diff and current checks, then write any safe action or reason to defer it to dependency-check.md.\",\n \"statusDefinitions\": {\n \"keep\": \"A verified action or useful finding was produced.\",\n \"noChange\": \"The check completed and found no actionable change.\",\n \"block\": \"Owner input or unavailable access prevents a safe decision.\"\n },\n \"artifacts\": [\"dependency-check.md\"],\n \"enabled\": true\n}\n```\n\nContinuous schedule shape:\n\n```json\n{\n \"mode\": \"continuous\",\n \"delayMinutes\": 30\n}\n```\n\nRules:\n\n- Required: `name`, `schedule`, absolute `workdir`, `agent`, non-empty `prompt`, and\n all three `statusDefinitions`.\n- `agent` is `claude-code` or `codex`.\n- `model` and `reasoningEffort` are optional strings. Omit them or use `null` to let\n the selected coding-agent CLI use its default.\n- `artifacts` is optional and contains only exact workdir-relative file paths.\n- `enabled` is optional and defaults to `true`.\n- Cron and continuous are exclusive shapes. Do not mix their fields.\n\n## 4 · Validate, then create\n\nPreview the exact config first:\n\n```bash\n<pievo-cli> new --json '<config>' --dry-run\n```\n\nFix every rejection. Then create it:\n\n```bash\n<pievo-cli> new --json '<config>'\n```\n\nOn a first-capture flow, include the pasted claim so the web dialog resolves:\n\n```bash\n<pievo-cli> new --json '<config>' --connect-key <connect-key>\n```\n\nOn success, tell the user the loop name, schedule, working directory, selected agent,\nand whether any artifact files were configured.\n",
377
+ "update.md": "# Edit an existing loop\n\nUse the same **pievo-cli** prefix used to connect the machine (default `pievo`). The\nmachine's persisted device credential authorizes edits to loops bound to it.\n\nFind the loop and inspect its current editable envelope:\n\n```bash\n<pievo-cli> loops\n<pievo-cli> show <loop-id> --json\n```\n\nPatch only the requested fields with one JSON object. Preview before applying:\n\n```bash\n<pievo-cli> edit <loop-id> --json '<patch>' --dry-run\n<pievo-cli> edit <loop-id> --json '<patch>'\n```\n\nAccepted fields are:\n\n| field | value |\n|---|---|\n| `name` | non-empty string |\n| `schedule` | complete cron or continuous schedule object |\n| `workdir` | absolute working-directory path |\n| `agent` | `claude-code` or `codex` |\n| `model` | string or `null` for provider default |\n| `reasoningEffort` | string or `null` for provider default |\n| `prompt` | non-empty string, preserved as written |\n| `statusDefinitions` | complete `{keep,noChange,block}` object with non-empty strings |\n| `artifacts` | array of exact paths relative to `workdir` |\n| `enabled` | boolean |\n\nSchedule examples:\n\n```bash\n<pievo-cli> edit <loop-id> --json '{\"schedule\":{\"mode\":\"cron\",\"cron\":\"0 9 * * 1-5\",\"timezone\":\"Europe/London\",\"overlap\":\"queue-one\"}}'\n<pievo-cli> edit <loop-id> --json '{\"schedule\":{\"mode\":\"continuous\",\"delayMinutes\":15}}'\n```\n\nOther examples:\n\n```bash\n<pievo-cli> edit <loop-id> --json '{\"prompt\":\"Run the verified release check and summarize only actionable findings.\"}'\n<pievo-cli> edit <loop-id> --json '{\"statusDefinitions\":{\"keep\":\"A verified action was completed.\",\"noChange\":\"The check completed with nothing actionable.\",\"block\":\"Owner input is required.\"}}'\n<pievo-cli> edit <loop-id> --json '{\"artifacts\":[\"release-check.md\"]}'\n<pievo-cli> edit <loop-id> --json '{\"model\":null,\"reasoningEffort\":null}'\n```\n\nImportant constraints:\n\n- Replacing `schedule` requires one complete exclusive shape. Cron requires `cron`,\n `timezone`, and `overlap`; continuous requires only `delayMinutes`.\n- Replacing `statusDefinitions` requires all three definitions.\n- Artifact entries are exact stable file paths, not globs. They must be relative to\n `workdir` and cannot traverse outside it.\n- The server validates every field and rejects unknown keys without mutating the loop.\n- Pievo appends the runtime report contract to `prompt`; do not duplicate it in the\n stored prompt.\n\nRun once is intentionally a Web action: use **Run once** on the loop detail page.\nFor Pause, Start, Stop, Delete, and history, use `<pievo-cli> --help` and the\nrelevant command's `--help` instead of treating lifecycle operations as\nprompt-authoring fields.\n"
378
+ };
379
+ var PREFIX$1 = "/api/skill/references/";
380
+ /**
381
+ * GET /api/skill/references/:file — serve one pievo skill reference file. Path-safe:
382
+ * only an exact, single-segment name from the static REFERENCES map resolves; anything
383
+ * else (nested paths, traversal, unknown names) is 404. The server only reads bytes it
384
+ * shipped — no filesystem access, no user input reaches disk (zero-exec invariant holds).
385
+ */
386
+ var Route$2 = createFileRoute("/api/skill/references/$")({ server: { handlers: { GET: ({ request }) => {
387
+ const pathname = new URL(request.url).pathname;
388
+ if (!pathname.startsWith(PREFIX$1)) return Response.json({ error: "not found" }, { status: 404 });
389
+ const name = pathname.slice(22);
390
+ const body = Object.prototype.hasOwnProperty.call(REFERENCES, name) ? REFERENCES[name] : void 0;
391
+ if (body === void 0) return Response.json({ error: "not found" }, { status: 404 });
392
+ return new Response(body, { headers: {
393
+ "content-type": "text/markdown; charset=utf-8",
394
+ "cache-control": "no-cache"
395
+ } });
396
+ } } } });
397
+ /**
398
+ * `decodeURIComponent` that returns null on malformed percent-encoding instead
399
+ * of throwing a URIError (which would escape a route handler as a 500). Shared
400
+ * by the routes that decode path segments; the policy is uniform — ANY
401
+ * malformed segment is the caller's clean 400, never silently kept raw.
402
+ */
403
+ function safeDecode(s) {
404
+ try {
405
+ return decodeURIComponent(s);
406
+ } catch {
407
+ return null;
408
+ }
409
+ }
410
+ /**
411
+ * PUT /api/machine/blob/:hash — upload one content-addressed blob's raw bytes
412
+ * (Bearer DEVICE token). The server recomputes sha256(body) and rejects a
413
+ * mismatch before storing in the byte store. The hash is read from the URL (the daemon PUTs
414
+ * exactly the hashes the sync handshake returned in needHashes).
415
+ *
416
+ * This byte-ingress route is deliberately NOT rate limited: it requires a valid
417
+ * registered device token (unknown ⇒ 401, not an unauthenticated surface) and is
418
+ * already bounded by the sync hash-handshake (the server only accepts hashes it
419
+ * asked THIS machine for) plus the per-file 10MB byte cap. A
420
+ * large first sync bursts many concurrent PUTs on one token, so any limiter would
421
+ * only throttle legitimate uploads without adding real protection.
422
+ */
423
+ var Route$1 = createFileRoute("/api/machine/blob/$hash")({ server: { handlers: { PUT: async ({ request }) => {
424
+ const auth = request.headers.get("authorization") ?? "";
425
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
426
+ if (!token) return Response.json({ error: "missing device token" }, { status: 401 });
427
+ const hash = safeDecode(new URL(request.url).pathname.split("/").pop() ?? "");
428
+ if (hash === null) return Response.json({ error: "bad hash" }, { status: 400 });
429
+ const { BLOB_CAP } = await import("./artifacts-BhQvaGjF.mjs");
430
+ const body = await readByteBody(request, BLOB_CAP);
431
+ if (body.kind === "too-large") return Response.json({ error: "blob exceeds size cap" }, { status: 413 });
432
+ if (body.kind === "invalid") return Response.json({ error: "could not read blob body" }, { status: 400 });
433
+ const buf = Buffer.from(body.bytes.buffer, body.bytes.byteOffset, body.bytes.byteLength);
434
+ const { getArtifactSync } = await import("./boot-COHf8Gv3.mjs");
435
+ const r = await (await getArtifactSync()).putBlob(token, hash, buf);
436
+ return Response.json(r.body, { status: r.status });
437
+ } } } });
438
+ /**
439
+ * GET /api/artifact/:loopId/:path - stream one of a loop's live-synced artifact
440
+ * blobs. Two dispositions:
441
+ * - default: `attachment` (the download affordance for binary/oversize files;
442
+ * text is shown inline via getArtifact).
443
+ * - `?view=inline`: for a KNOWN image type only (the `imageMime` allowlist),
444
+ * served `inline` with the real image content-type so an <img> can render it.
445
+ * Hardened against a direct navigation turning the byte stream into a scripted
446
+ * document on the app origin: `X-Content-Type-Options: nosniff` pins the type
447
+ * and `Content-Security-Policy: sandbox` loads it in an opaque origin with no
448
+ * scripts - so an inline SVG can't run script against the session even if the
449
+ * URL is opened directly. Non-image inline requests fall back to attachment.
450
+ *
451
+ * Authed via the WEB SESSION (not a machine token) and scoped to the loop's team,
452
+ * mirroring the server fns' `ownedLoop` gate. Path-safe: the relative path is
453
+ * normalized + traversal-rejected before it ever reaches a blob, and blob keys
454
+ * are content hashes, never the user path. Reads stream from the BlobStore
455
+ * (local by default, R2 when configured); the route never writes.
456
+ */
457
+ var PREFIX = "/api/artifact/";
458
+ var Route = createFileRoute("/api/artifact/$loopId/$")({ server: { handlers: { GET: async ({ request }) => {
459
+ const pathname = new URL(request.url).pathname;
460
+ if (!pathname.startsWith(PREFIX)) return Response.json({ error: "not found" }, { status: 404 });
461
+ const after = pathname.slice(14);
462
+ const slash = after.indexOf("/");
463
+ if (slash < 0) return Response.json({ error: "missing path" }, { status: 400 });
464
+ const loopId = safeDecode(after.slice(0, slash));
465
+ if (loopId === null) return Response.json({ error: "bad loop id" }, { status: 400 });
466
+ const segments = after.slice(slash + 1).split("/").map((s) => safeDecode(s));
467
+ if (segments.some((s) => s === null)) return Response.json({ error: "bad path" }, { status: 400 });
468
+ const relPath = segments.join("/");
469
+ const loop = await (await import("./store-CirPHuib.mjs")).getLoop(loopId);
470
+ if (!loop) return Response.json({ error: "not found" }, { status: 404 });
471
+ const { requestScope, canAccessLoop } = await import("./auth-CPCDf6Wr.mjs");
472
+ if (!await canAccessLoop(loop.teamId, await requestScope())) return Response.json({ error: "not found" }, { status: 404 });
473
+ const { readLoopArtifactBytes } = await import("./artifactFiles-CV3OXxRD.mjs");
474
+ const r = await readLoopArtifactBytes(loopId, relPath);
475
+ if (r.status !== 200 || !r.bytes) return Response.json({ error: "not found" }, { status: r.status });
476
+ const filename = (r.filename || "file").replace(/["\\\r\n]/g, "_");
477
+ const mime = new URL(request.url).searchParams.get("view") === "inline" ? imageMime(relPath) : null;
478
+ if (mime) return new Response(new Uint8Array(r.bytes), {
479
+ status: 200,
480
+ headers: {
481
+ "content-type": mime,
482
+ "content-length": String(r.bytes.length),
483
+ "content-disposition": `inline; filename="${filename}"`,
484
+ "x-content-type-options": "nosniff",
485
+ "content-security-policy": "sandbox",
486
+ "cache-control": "private, no-store"
487
+ }
488
+ });
489
+ return new Response(new Uint8Array(r.bytes), {
490
+ status: 200,
491
+ headers: {
492
+ "content-type": r.binary ? "application/octet-stream" : "text/plain; charset=utf-8",
493
+ "content-length": String(r.bytes.length),
494
+ "content-disposition": `attachment; filename="${filename}"`,
495
+ "cache-control": "private, no-store"
496
+ }
497
+ });
498
+ } } } });
499
+ var IndexRoute = Route$17.update({
500
+ id: "/",
501
+ path: "/",
502
+ getParentRoute: () => Route$13
503
+ });
504
+ var TTeamIdRoute = Route$18.update({
505
+ id: "/t/$teamId",
506
+ path: "/t/$teamId",
507
+ getParentRoute: () => Route$13
508
+ });
509
+ var MachineReportRoute = Route$12.update({
510
+ id: "/machine/report",
511
+ path: "/machine/report",
512
+ getParentRoute: () => Route$13
513
+ });
514
+ var LoopsLoopIdRoute = Route$15.update({
515
+ id: "/loops/$loopId",
516
+ path: "/loops/$loopId",
517
+ getParentRoute: () => Route$13
518
+ });
519
+ var InviteTokenRoute = Route$14.update({
520
+ id: "/invite/$token",
521
+ path: "/invite/$token",
522
+ getParentRoute: () => Route$13
523
+ });
524
+ var ApiReadyRoute = Route$11.update({
525
+ id: "/api/ready",
526
+ path: "/api/ready",
527
+ getParentRoute: () => Route$13
528
+ });
529
+ var ApiHealthRoute = Route$10.update({
530
+ id: "/api/health",
531
+ path: "/api/health",
532
+ getParentRoute: () => Route$13
533
+ });
534
+ var ApiBootstrapRoute = Route$9.update({
535
+ id: "/api/bootstrap",
536
+ path: "/api/bootstrap",
537
+ getParentRoute: () => Route$13
538
+ });
539
+ var ApiMachineSyncRoute = Route$8.update({
540
+ id: "/api/machine/sync",
541
+ path: "/api/machine/sync",
542
+ getParentRoute: () => Route$13
543
+ });
544
+ var ApiMachineStatusRoute = Route$7.update({
545
+ id: "/api/machine/status",
546
+ path: "/api/machine/status",
547
+ getParentRoute: () => Route$13
548
+ });
549
+ var ApiMachinePollRoute = Route$6.update({
550
+ id: "/api/machine/poll",
551
+ path: "/api/machine/poll",
552
+ getParentRoute: () => Route$13
553
+ });
554
+ var ApiMachineCliRoute = Route$5.update({
555
+ id: "/api/machine/cli",
556
+ path: "/api/machine/cli",
557
+ getParentRoute: () => Route$13
558
+ });
559
+ var ApiHealthDbRoute = Route$4.update({
560
+ id: "/db",
561
+ path: "/db",
562
+ getParentRoute: () => ApiHealthRoute
563
+ });
564
+ var ApiAuthSplatRoute = Route$3.update({
565
+ id: "/api/auth/$",
566
+ path: "/api/auth/$",
567
+ getParentRoute: () => Route$13
568
+ });
569
+ var LoopsLoopIdRunsRunIdRoute = Route$16.update({
570
+ id: "/loops/$loopId_/runs/$runId",
571
+ path: "/loops/$loopId/runs/$runId",
572
+ getParentRoute: () => Route$13
573
+ });
574
+ var ApiSkillReferencesSplatRoute = Route$2.update({
575
+ id: "/api/skill/references/$",
576
+ path: "/api/skill/references/$",
577
+ getParentRoute: () => Route$13
578
+ });
579
+ var ApiMachineBlobHashRoute = Route$1.update({
580
+ id: "/api/machine/blob/$hash",
581
+ path: "/api/machine/blob/$hash",
582
+ getParentRoute: () => Route$13
583
+ });
584
+ var ApiArtifactLoopIdSplatRoute = Route.update({
585
+ id: "/api/artifact/$loopId/$",
586
+ path: "/api/artifact/$loopId/$",
587
+ getParentRoute: () => Route$13
588
+ });
589
+ var ApiHealthRouteChildren = { ApiHealthDbRoute };
590
+ var rootRouteChildren = {
591
+ IndexRoute,
592
+ ApiBootstrapRoute,
593
+ ApiHealthRoute: ApiHealthRoute._addFileChildren(ApiHealthRouteChildren),
594
+ ApiReadyRoute,
595
+ InviteTokenRoute,
596
+ LoopsLoopIdRoute,
597
+ MachineReportRoute,
598
+ TTeamIdRoute,
599
+ ApiAuthSplatRoute,
600
+ ApiMachineCliRoute,
601
+ ApiMachinePollRoute,
602
+ ApiMachineStatusRoute,
603
+ ApiMachineSyncRoute,
604
+ ApiArtifactLoopIdSplatRoute,
605
+ ApiMachineBlobHashRoute,
606
+ ApiSkillReferencesSplatRoute,
607
+ LoopsLoopIdRunsRunIdRoute
608
+ };
609
+ var routeTree = Route$13._addFileChildren(rootRouteChildren)._addFileTypes();
610
+ function getRouter() {
611
+ return createRouter({
612
+ routeTree,
613
+ scrollRestoration: true,
614
+ defaultPreload: "intent"
615
+ });
616
+ }
617
+ //#endregion
618
+ export { getRouter };
@@ -1,10 +1,9 @@
1
1
  import { c as createFileRoute, p as redirect, s as lazyRouteComponent } from "../_libs/@tanstack/react-router+[...].mjs";
2
- import { c as getAuthState, d as getDefaultTeam, g as listTemplates } from "./loopApi-8_KgBapr.mjs";
3
- import { a as authClient } from "./LoopPlaybook-uq_sFfgU.mjs";
4
- import { n as fetchLiveData } from "./DashboardView-0yoKh8Em.mjs";
5
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-DebVUmCS.js
6
- var $$splitErrorComponentImporter = () => import("./routes-Bx0bRxt9.mjs");
7
- var $$splitComponentImporter = () => import("./routes-dQCBLTxY.mjs");
2
+ import { d as getAuthState, i as authClient, p as getDefaultTeam } from "./SocialLinks-BTjsnVOm.mjs";
3
+ import { n as fetchLiveData } from "./DashboardView-D_hKsCsv.mjs";
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-CRHc9cLG.js
5
+ var $$splitErrorComponentImporter = () => import("./routes-DNCZrs8z.mjs");
6
+ var $$splitComponentImporter = () => import("./routes-ufPbrGHs.mjs");
8
7
  /**
9
8
  * The home route. Under the auth gate it is now a THIN redirect to the explicit
10
9
  * team URL (`/t/<lastUsed|personal>`) so the dashboard's team lives in the path
@@ -30,10 +29,7 @@ var Route = createFileRoute("/")({
30
29
  return {
31
30
  mode: "dashboard",
32
31
  auth,
33
- initial: {
34
- ...await fetchLiveData(),
35
- templates: await listTemplates()
36
- }
32
+ initial: await fetchLiveData()
37
33
  };
38
34
  },
39
35
  component: lazyRouteComponent($$splitComponentImporter, "component"),