@computesdk/loops 0.1.0 → 0.1.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 (68) hide show
  1. package/API.md +5 -2
  2. package/README.md +6 -1
  3. package/bin/loops.mjs +15 -2
  4. package/dist/assets/{abnfDiagram-VCTEODGH-DPUvPEpH.js → abnfDiagram-VCTEODGH-CceVg1jC.js} +1 -1
  5. package/dist/assets/{arc-C71M1We_.js → arc-CurbUKc7.js} +1 -1
  6. package/dist/assets/{architectureDiagram-5GKGNRK7-C3PLmrM3.js → architectureDiagram-5GKGNRK7-BG6bMida.js} +1 -1
  7. package/dist/assets/{blockDiagram-I7D4REHJ-DFshwmKE.js → blockDiagram-I7D4REHJ-B11cpZbJ.js} +1 -1
  8. package/dist/assets/{c4Diagram-7LVT6UL2-Dh29QjGI.js → c4Diagram-7LVT6UL2-DiXBjlUf.js} +1 -1
  9. package/dist/assets/channel-Cy2zP8Uh.js +1 -0
  10. package/dist/assets/{chunk-2Q5K7J3B-qZ7USlsH.js → chunk-2Q5K7J3B-DMtau_ZO.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-YQpRymiv.js → chunk-5VM5RSS4-CkDw9Dwr.js} +1 -1
  12. package/dist/assets/{chunk-F27PBJKO-D2O95m9O.js → chunk-F27PBJKO-DduXbVaR.js} +1 -1
  13. package/dist/assets/{chunk-IMKFNOWR-CftSjfdl.js → chunk-IMKFNOWR-CA3FEpcX.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-_1wtK-EO.js → chunk-JWPE2WC7-_aPFiznH.js} +1 -1
  15. package/dist/assets/{chunk-POPQ4Y6H-DJ2Xmp8X.js → chunk-POPQ4Y6H-BJ1FxtOa.js} +1 -1
  16. package/dist/assets/{chunk-SVP7TREG-CAtSHcje.js → chunk-SVP7TREG-BISx4Qsv.js} +1 -1
  17. package/dist/assets/{chunk-TICWLB2K-1BASqzB5.js → chunk-TICWLB2K-jN0ScesT.js} +1 -1
  18. package/dist/assets/{chunk-XXDRQBXY-CWs6lP_i.js → chunk-XXDRQBXY-Bp2w1xsm.js} +1 -1
  19. package/dist/assets/classDiagram-ZZMXUADV-BHTKitbG.js +1 -0
  20. package/dist/assets/classDiagram-v2-VYDZK3BY-BHTKitbG.js +1 -0
  21. package/dist/assets/{cose-bilkent-JH36ORCC-9dCpEIjz.js → cose-bilkent-JH36ORCC-RJxH4pHE.js} +1 -1
  22. package/dist/assets/{cynefin-OW5HDTMX-EFt_R8HZ.js → cynefin-OW5HDTMX-RQb2d53E.js} +1 -1
  23. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BuManjb1.js → cynefinDiagram-5FMLGOSQ-BkWuDtvv.js} +1 -1
  24. package/dist/assets/{dagre-GXQ25YYZ-MovLAGVL.js → dagre-GXQ25YYZ-Dw_p1MY7.js} +1 -1
  25. package/dist/assets/{diagram-S7CK7UJ4-BiJw5LQ6.js → diagram-S7CK7UJ4-CNvafPnD.js} +1 -1
  26. package/dist/assets/{diagram-UQ7AKVKN-DRsX_xa6.js → diagram-UQ7AKVKN-Ca50sGck.js} +1 -1
  27. package/dist/assets/{diagram-VSXAHHWV-B7m6F8Dm.js → diagram-VSXAHHWV-B9EkCAhx.js} +1 -1
  28. package/dist/assets/{diagram-VX7I27RA-x4wioeVA.js → diagram-VX7I27RA-Be2j3KkX.js} +1 -1
  29. package/dist/assets/{diagram-Z3DM3KII-0KYjyvJF.js → diagram-Z3DM3KII-Iki9IPof.js} +1 -1
  30. package/dist/assets/{ebnfDiagram-PWID7BFC-BZXmt3oU.js → ebnfDiagram-PWID7BFC-C_DacKX3.js} +1 -1
  31. package/dist/assets/{erDiagram-RLTQ6QDP-CrfcYtD6.js → erDiagram-RLTQ6QDP-CN090mdT.js} +1 -1
  32. package/dist/assets/{flowDiagram-HODETNUW-BqudKoEy.js → flowDiagram-HODETNUW-z9R5Hzp_.js} +1 -1
  33. package/dist/assets/{ganttDiagram-EL5Y4UJY-OBWjueb8.js → ganttDiagram-EL5Y4UJY-Ctsoo8fA.js} +1 -1
  34. package/dist/assets/{gitGraphDiagram-WWUBYQGX-B30e199R.js → gitGraphDiagram-WWUBYQGX-BAfnTLBI.js} +1 -1
  35. package/dist/assets/{highlighted-body-JLMTFACH-DapGkHdb.js → highlighted-body-JLMTFACH-JGCpsH-e.js} +1 -1
  36. package/dist/assets/{index-C_bKTXmh.js → index-DkxpcXIf.js} +123 -123
  37. package/dist/assets/{index-hKoeppfe.css → index-NzHlk7dF.css} +1 -1
  38. package/dist/assets/{infoDiagram-27XIBGKW-DPwVvB4K.js → infoDiagram-27XIBGKW-BMe5_1z8.js} +1 -1
  39. package/dist/assets/{ishikawaDiagram-5VMMS53U-B0fvRdQL.js → ishikawaDiagram-5VMMS53U-BRSqbbUo.js} +1 -1
  40. package/dist/assets/{journeyDiagram-3NMN7TZE-CZBLF7ld.js → journeyDiagram-3NMN7TZE-WkAorMt-.js} +1 -1
  41. package/dist/assets/{kanban-definition-UXKFOSKX-wTBJWh9T.js → kanban-definition-UXKFOSKX-aa6aiAN2.js} +1 -1
  42. package/dist/assets/{layout-DYBs-gVA.js → layout-BDC_N0BU.js} +1 -1
  43. package/dist/assets/{linear-DTE3zVP_.js → linear-CznrJ42T.js} +1 -1
  44. package/dist/assets/{mindmap-definition-YA3MSWOX-tV3HPpzU.js → mindmap-definition-YA3MSWOX-CW6ybBUC.js} +1 -1
  45. package/dist/assets/{pegDiagram-XKGWAZYB-B6Yznbb5.js → pegDiagram-XKGWAZYB-Dr3qzrfb.js} +1 -1
  46. package/dist/assets/{pieDiagram-E7YTZNPT-B6QW8pJH.js → pieDiagram-E7YTZNPT-BkgMYztr.js} +1 -1
  47. package/dist/assets/{quadrantDiagram-AXDQQJYC-DgHGTtj0.js → quadrantDiagram-AXDQQJYC-BTswMqBf.js} +1 -1
  48. package/dist/assets/{railroadDiagram-O6MQD6OU-CgqVFEBK.js → railroadDiagram-O6MQD6OU-DVCTkg3c.js} +1 -1
  49. package/dist/assets/{requirementDiagram-BXWQKSXE-FmXMY-6j.js → requirementDiagram-BXWQKSXE-DDSFlh61.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-P5KCCOFB-AVMEYVKy.js → sankeyDiagram-P5KCCOFB-DHP89LFt.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-WJ2MYXX4-uOObReYi.js → sequenceDiagram-WJ2MYXX4-CUcSveH3.js} +1 -1
  52. package/dist/assets/{sizeCapture-INFHLROL-BPOPqqxk.js → sizeCapture-INFHLROL-fGlaZz_7.js} +1 -1
  53. package/dist/assets/{stateDiagram-D77RDMKH-C1lXHNdu.js → stateDiagram-D77RDMKH-DqcVPNAG.js} +1 -1
  54. package/dist/assets/stateDiagram-v2-MP3YSRHH-_t9xbbjh.js +1 -0
  55. package/dist/assets/{swimlanes-42K2YHIH-C52GOi05.js → swimlanes-42K2YHIH-L-pgkzCN.js} +1 -1
  56. package/dist/assets/swimlanesDiagram-VR7AAH4N-6eL6UTv7.js +8 -0
  57. package/dist/assets/{timeline-definition-24CTP7MA-T0BhAPL-.js → timeline-definition-24CTP7MA-C__pbpoz.js} +1 -1
  58. package/dist/assets/{vennDiagram-4TSXK5OY-DSGCn4iB.js → vennDiagram-4TSXK5OY-B4f-azPR.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-VM6X3IG4-BaCYx_03.js → wardleyDiagram-VM6X3IG4-01ZelHA6.js} +1 -1
  60. package/dist/assets/{xychartDiagram-S5SC5T6Z-CgPwXvFv.js → xychartDiagram-S5SC5T6Z-CNfj8huj.js} +1 -1
  61. package/dist/index.html +2 -2
  62. package/dist/server.mjs +265 -147
  63. package/package.json +1 -1
  64. package/dist/assets/channel-6AEG2wiQ.js +0 -1
  65. package/dist/assets/classDiagram-ZZMXUADV-WtwW3JUM.js +0 -1
  66. package/dist/assets/classDiagram-v2-VYDZK3BY-WtwW3JUM.js +0 -1
  67. package/dist/assets/stateDiagram-v2-MP3YSRHH-BHg8O_NZ.js +0 -1
  68. package/dist/assets/swimlanesDiagram-VR7AAH4N-DLB_mWZL.js +0 -8
package/dist/server.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  // server/index.ts
2
- import fs7 from "node:fs";
2
+ import fs8 from "node:fs";
3
3
  import { randomBytes } from "node:crypto";
4
4
  import http from "node:http";
5
- import path7 from "node:path";
5
+ import path8 from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { execFile, execFileSync as execFileSync2, spawn as spawn5 } from "node:child_process";
7
+ import { execFile, execFileSync as execFileSync2, spawn as spawn6 } from "node:child_process";
8
8
 
9
9
  // ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/request/constants.js
10
10
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
@@ -79,26 +79,26 @@ var throwNestingLimitExceeded = () => {
79
79
  };
80
80
 
81
81
  // ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/utils/url.js
82
- var splitPath = (path8) => {
83
- const paths = path8.split("/");
82
+ var splitPath = (path9) => {
83
+ const paths = path9.split("/");
84
84
  if (paths[0] === "") paths.shift();
85
85
  return paths;
86
86
  };
87
87
  var splitRoutingPath = (routePath) => {
88
- const { groups, path: path8 } = extractGroupsFromPath(routePath);
89
- const paths = splitPath(path8);
88
+ const { groups, path: path9 } = extractGroupsFromPath(routePath);
89
+ const paths = splitPath(path9);
90
90
  return replaceGroupMarks(paths, groups);
91
91
  };
92
- var extractGroupsFromPath = (path8) => {
92
+ var extractGroupsFromPath = (path9) => {
93
93
  const groups = [];
94
- path8 = path8.replace(/\{[^}]+\}/g, (match2, index) => {
94
+ path9 = path9.replace(/\{[^}]+\}/g, (match2, index) => {
95
95
  const mark = `@${index}`;
96
96
  groups.push([mark, match2]);
97
97
  return mark;
98
98
  });
99
99
  return {
100
100
  groups,
101
- path: path8
101
+ path: path9
102
102
  };
103
103
  };
104
104
  var replaceGroupMarks = (paths, groups) => {
@@ -161,8 +161,8 @@ var getPath = (request) => {
161
161
  const queryIndex = url.indexOf("?", i);
162
162
  const hashIndex = url.indexOf("#", i);
163
163
  const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
164
- const path8 = url.slice(start, end);
165
- return tryDecodeURI(path8.includes("%25") ? path8.replace(/%25/g, "%2525") : path8);
164
+ const path9 = url.slice(start, end);
165
+ return tryDecodeURI(path9.includes("%25") ? path9.replace(/%25/g, "%2525") : path9);
166
166
  } else if (charCode === 63 || charCode === 35) break;
167
167
  }
168
168
  return url.slice(start, i);
@@ -175,9 +175,9 @@ var mergePath = (base, sub, ...rest) => {
175
175
  if (rest.length) sub = mergePath(sub, ...rest);
176
176
  return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
177
177
  };
178
- var checkOptionalParameter = (path8) => {
179
- if (path8.charCodeAt(path8.length - 1) !== 63 || !path8.includes(":")) return null;
180
- const segments = path8.split("/");
178
+ var checkOptionalParameter = (path9) => {
179
+ if (path9.charCodeAt(path9.length - 1) !== 63 || !path9.includes(":")) return null;
180
+ const segments = path9.split("/");
181
181
  const results = [];
182
182
  let basePath = "";
183
183
  segments.forEach((segment) => {
@@ -288,9 +288,9 @@ var HonoRequest = class {
288
288
  */
289
289
  path;
290
290
  bodyCache = {};
291
- constructor(request, path8 = "/", matchResult = [[]]) {
291
+ constructor(request, path9 = "/", matchResult = [[]]) {
292
292
  this.raw = request;
293
- this.path = path8;
293
+ this.path = path9;
294
294
  this.#matchResult = matchResult;
295
295
  }
296
296
  param(key) {
@@ -1020,8 +1020,8 @@ var Hono = class Hono2 {
1020
1020
  return this;
1021
1021
  };
1022
1022
  });
1023
- this.on = (method, path8, ...handlers) => {
1024
- for (const p of [path8].flat()) {
1023
+ this.on = (method, path9, ...handlers) => {
1024
+ for (const p of [path9].flat()) {
1025
1025
  this.#path = p;
1026
1026
  for (const m of [method].flat()) {
1027
1027
  const methodName = m.toUpperCase();
@@ -1075,8 +1075,8 @@ var Hono = class Hono2 {
1075
1075
  * app.route("/api", app2) // GET /api/user
1076
1076
  * ```
1077
1077
  */
1078
- route(path8, app2) {
1079
- const subApp = this.basePath(path8);
1078
+ route(path9, app2) {
1079
+ const subApp = this.basePath(path9);
1080
1080
  app2.routes.map((r) => {
1081
1081
  let handler;
1082
1082
  if (app2.errorHandler === errorHandler) handler = r.handler;
@@ -1101,9 +1101,9 @@ var Hono = class Hono2 {
1101
1101
  * const api = new Hono().basePath('/api')
1102
1102
  * ```
1103
1103
  */
1104
- basePath(path8) {
1104
+ basePath(path9) {
1105
1105
  const subApp = this.#clone();
1106
- subApp._basePath = mergePath(this._basePath, path8);
1106
+ subApp._basePath = mergePath(this._basePath, path9);
1107
1107
  return subApp;
1108
1108
  }
1109
1109
  /**
@@ -1179,7 +1179,7 @@ var Hono = class Hono2 {
1179
1179
  * })
1180
1180
  * ```
1181
1181
  */
1182
- mount(path8, applicationHandler, options) {
1182
+ mount(path9, applicationHandler, options) {
1183
1183
  let replaceRequest;
1184
1184
  let optionHandler;
1185
1185
  if (options) {
@@ -1202,7 +1202,7 @@ var Hono = class Hono2 {
1202
1202
  return [c.env, executionContext];
1203
1203
  };
1204
1204
  replaceRequest ||= (() => {
1205
- const mergedPath = mergePath(this._basePath, path8);
1205
+ const mergedPath = mergePath(this._basePath, path9);
1206
1206
  const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
1207
1207
  return (request) => {
1208
1208
  const url = new URL(request.url);
@@ -1215,18 +1215,18 @@ var Hono = class Hono2 {
1215
1215
  if (res) return res;
1216
1216
  await next();
1217
1217
  };
1218
- this.#addRoute("ALL", mergePath(path8, "*"), handler);
1218
+ this.#addRoute("ALL", mergePath(path9, "*"), handler);
1219
1219
  return this;
1220
1220
  }
1221
- #addRoute(method, path8, handler, baseRoutePath) {
1222
- path8 = mergePath(this._basePath, path8);
1221
+ #addRoute(method, path9, handler, baseRoutePath) {
1222
+ path9 = mergePath(this._basePath, path9);
1223
1223
  const r = {
1224
1224
  basePath: baseRoutePath !== void 0 ? mergePath(this._basePath, baseRoutePath) : this._basePath,
1225
- path: path8,
1225
+ path: path9,
1226
1226
  method,
1227
1227
  handler
1228
1228
  };
1229
- this.router.add(method, path8, [handler, r]);
1229
+ this.router.add(method, path9, [handler, r]);
1230
1230
  this.routes.push(r);
1231
1231
  }
1232
1232
  #handleError(err, c) {
@@ -1235,10 +1235,10 @@ var Hono = class Hono2 {
1235
1235
  }
1236
1236
  #dispatch(request, executionCtx, env, method) {
1237
1237
  if (method === "HEAD") return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
1238
- const path8 = this.getPath(request, { env });
1239
- const matchResult = this.router.match(method, path8);
1238
+ const path9 = this.getPath(request, { env });
1239
+ const matchResult = this.router.match(method, path9);
1240
1240
  const c = new Context(request, {
1241
- path: path8,
1241
+ path: path9,
1242
1242
  matchResult,
1243
1243
  env,
1244
1244
  executionCtx,
@@ -1326,19 +1326,19 @@ var createNullObject = () => /* @__PURE__ */ Object.create(null);
1326
1326
 
1327
1327
  // ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/matcher.js
1328
1328
  var emptyParam = [];
1329
- function match(method, path8) {
1329
+ function match(method, path9) {
1330
1330
  const matchers = this.buildAllMatchers();
1331
- const match2 = ((method2, path9) => {
1331
+ const match2 = ((method2, path10) => {
1332
1332
  const matcher = matchers[method2] || matchers["ALL"];
1333
- const staticMatch = matcher[2][path9];
1333
+ const staticMatch = matcher[2][path10];
1334
1334
  if (staticMatch) return staticMatch;
1335
- const match3 = path9.match(matcher[0]);
1335
+ const match3 = path10.match(matcher[0]);
1336
1336
  if (!match3) return [[], emptyParam];
1337
1337
  const index = match3.indexOf("", 1);
1338
1338
  return [matcher[1][index], match3];
1339
1339
  });
1340
1340
  this.match = match2;
1341
- return match2(method, path8);
1341
+ return match2(method, path9);
1342
1342
  }
1343
1343
 
1344
1344
  // ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/node.js
@@ -1428,14 +1428,14 @@ var Trie = class {
1428
1428
  #root = new Node();
1429
1429
  #index = 0;
1430
1430
  paths = createNullObject();
1431
- insert(path8, isStatic) {
1431
+ insert(path9, isStatic) {
1432
1432
  if (isStatic) {
1433
- this.#root.insert(path8.split(""), 0, [], this.#context, true);
1433
+ this.#root.insert(path9.split(""), 0, [], this.#context, true);
1434
1434
  return;
1435
1435
  }
1436
1436
  const paramAssoc = [];
1437
1437
  const groups = [];
1438
- let markedPath = path8;
1438
+ let markedPath = path9;
1439
1439
  for (let i = 0; ; ) {
1440
1440
  let replaced = false;
1441
1441
  markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
@@ -1456,7 +1456,7 @@ var Trie = class {
1456
1456
  }
1457
1457
  }
1458
1458
  this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
1459
- this.paths[path8] = [this.#index++, paramAssoc];
1459
+ this.paths[path9] = [this.#index++, paramAssoc];
1460
1460
  }
1461
1461
  buildRegExp() {
1462
1462
  let regexp = this.#root.buildRegExpStr();
@@ -1489,11 +1489,11 @@ var Trie = class {
1489
1489
 
1490
1490
  // ../../node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/router/reg-exp-router/router.js
1491
1491
  var wildcardRegExpCache = createNullObject();
1492
- function buildWildcardRegExp(path8) {
1493
- return wildcardRegExpCache[path8] ??= new RegExp(`^${path8.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
1492
+ function buildWildcardRegExp(path9) {
1493
+ return wildcardRegExpCache[path9] ??= new RegExp(`^${path9.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
1494
1494
  }
1495
- function findMiddleware(middleware, path8) {
1496
- for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(path8)) return [...middleware[k]];
1495
+ function findMiddleware(middleware, path9) {
1496
+ for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) if (buildWildcardRegExp(k).test(path9)) return [...middleware[k]];
1497
1497
  }
1498
1498
  var RegExpRouter = class {
1499
1499
  name = "RegExpRouter";
@@ -1505,14 +1505,14 @@ var RegExpRouter = class {
1505
1505
  this.#routes = { ["ALL"]: createNullObject() };
1506
1506
  this.#tries = { ["ALL"]: new Trie() };
1507
1507
  }
1508
- #insertPath(method, path8) {
1508
+ #insertPath(method, path9) {
1509
1509
  try {
1510
- this.#tries[method].insert(path8, !/\*|\/:/.test(path8));
1510
+ this.#tries[method].insert(path9, !/\*|\/:/.test(path9));
1511
1511
  } catch (e) {
1512
- throw e === PATH_ERROR ? new UnsupportedPathError(path8) : e;
1512
+ throw e === PATH_ERROR ? new UnsupportedPathError(path9) : e;
1513
1513
  }
1514
1514
  }
1515
- add(method, path8, handler) {
1515
+ add(method, path9, handler) {
1516
1516
  const middleware = this.#middleware;
1517
1517
  const routes = this.#routes;
1518
1518
  if (!middleware) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
@@ -1526,24 +1526,24 @@ var RegExpRouter = class {
1526
1526
  }
1527
1527
  }
1528
1528
  }
1529
- if (path8 === "/*") path8 = "*";
1529
+ if (path9 === "/*") path9 = "*";
1530
1530
  const methods = method === "ALL" ? Object.keys(middleware) : [method];
1531
- if (/\*$/.test(path8)) {
1532
- const re = buildWildcardRegExp(path8);
1533
- for (const m of methods) if (!middleware[m][path8]) {
1534
- this.#insertPath(m, path8);
1535
- middleware[m][path8] = findMiddleware(middleware[m], path8) || findMiddleware(middleware["ALL"], path8) || [];
1531
+ if (/\*$/.test(path9)) {
1532
+ const re = buildWildcardRegExp(path9);
1533
+ for (const m of methods) if (!middleware[m][path9]) {
1534
+ this.#insertPath(m, path9);
1535
+ middleware[m][path9] = findMiddleware(middleware[m], path9) || findMiddleware(middleware["ALL"], path9) || [];
1536
1536
  }
1537
- for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler, path8]);
1537
+ for (const handlerMap of [middleware, routes]) for (const m of methods) for (const p in handlerMap[m]) re.test(p) && handlerMap[m][p].push([handler, path9]);
1538
1538
  return;
1539
1539
  }
1540
- const paths = checkOptionalParameter(path8) || [path8];
1541
- for (const path9 of paths) for (const m of methods) {
1542
- if (!routes[m][path9]) {
1543
- this.#insertPath(m, path9);
1544
- routes[m][path9] = findMiddleware(middleware[m], path9) || findMiddleware(middleware["ALL"], path9) || [];
1540
+ const paths = checkOptionalParameter(path9) || [path9];
1541
+ for (const path10 of paths) for (const m of methods) {
1542
+ if (!routes[m][path10]) {
1543
+ this.#insertPath(m, path10);
1544
+ routes[m][path10] = findMiddleware(middleware[m], path10) || findMiddleware(middleware["ALL"], path10) || [];
1545
1545
  }
1546
- routes[m][path9].push([handler, path9]);
1546
+ routes[m][path10].push([handler, path10]);
1547
1547
  }
1548
1548
  }
1549
1549
  match = match;
@@ -1561,11 +1561,11 @@ var RegExpRouter = class {
1561
1561
  const staticMap = createNullObject();
1562
1562
  const handlerData = [];
1563
1563
  const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
1564
- for (const r of [middleware, routes]) for (const path8 in r) {
1565
- const handlers = r[path8];
1566
- const pathData = trie.paths[path8];
1564
+ for (const r of [middleware, routes]) for (const path9 in r) {
1565
+ const handlers = r[path9];
1566
+ const pathData = trie.paths[path9];
1567
1567
  if (!pathData) {
1568
- staticMap[path8] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
1568
+ staticMap[path9] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
1569
1569
  continue;
1570
1570
  }
1571
1571
  handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [h, trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
@@ -1589,15 +1589,15 @@ var SmartRouter = class {
1589
1589
  constructor(init) {
1590
1590
  this.#routers = init.routers;
1591
1591
  }
1592
- add(method, path8, handler) {
1592
+ add(method, path9, handler) {
1593
1593
  if (!this.#routes) throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
1594
1594
  this.#routes.push([
1595
1595
  method,
1596
- path8,
1596
+ path9,
1597
1597
  handler
1598
1598
  ]);
1599
1599
  }
1600
- match(method, path8) {
1600
+ match(method, path9) {
1601
1601
  if (!this.#routes) throw new Error("Fatal error");
1602
1602
  const routers = this.#routers;
1603
1603
  const routes = this.#routes;
@@ -1608,7 +1608,7 @@ var SmartRouter = class {
1608
1608
  const router = routers[i];
1609
1609
  try {
1610
1610
  for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) router.add(...routes[i2]);
1611
- res = router.match(method, path8);
1611
+ res = router.match(method, path9);
1612
1612
  } catch (e) {
1613
1613
  if (e instanceof UnsupportedPathError) continue;
1614
1614
  throw e;
@@ -1637,9 +1637,9 @@ var Node3 = class Node4 {
1637
1637
  #patterns = [];
1638
1638
  #pattern;
1639
1639
  #params = emptyParams;
1640
- insert(method, path8, handler) {
1640
+ insert(method, path9, handler) {
1641
1641
  let curNode = this;
1642
- const parts = splitRoutingPath(path8);
1642
+ const parts = splitRoutingPath(path9);
1643
1643
  const possibleKeys = /* @__PURE__ */ new Set();
1644
1644
  let i = 0;
1645
1645
  for (const p of parts) {
@@ -1675,11 +1675,11 @@ var Node3 = class Node4 {
1675
1675
  }
1676
1676
  }
1677
1677
  }
1678
- search(method, path8) {
1678
+ search(method, path9) {
1679
1679
  const handlerSets = [];
1680
1680
  this.#params = emptyParams;
1681
1681
  let curNodes = [this];
1682
- const parts = splitPath(path8);
1682
+ const parts = splitPath(path9);
1683
1683
  const curNodesQueue = [];
1684
1684
  const len = parts.length;
1685
1685
  let partOffsets = null;
@@ -1715,13 +1715,13 @@ var Node3 = class Node4 {
1715
1715
  if (matcher !== true) {
1716
1716
  if (!partOffsets) {
1717
1717
  partOffsets = [];
1718
- let offset = path8[0] === "/" ? 1 : 0;
1718
+ let offset = path9[0] === "/" ? 1 : 0;
1719
1719
  for (let p = 0; p < len; p++) {
1720
1720
  partOffsets[p] = offset;
1721
1721
  offset += parts[p].length + 1;
1722
1722
  }
1723
1723
  }
1724
- const restPathString = path8.slice(partOffsets[i]);
1724
+ const restPathString = path9.slice(partOffsets[i]);
1725
1725
  const m = matcher.exec(restPathString);
1726
1726
  if (m) {
1727
1727
  if (!Object.hasOwn(params, name)) params[name] = m[0];
@@ -1762,11 +1762,11 @@ var Node3 = class Node4 {
1762
1762
  var TrieRouter = class {
1763
1763
  name = "TrieRouter";
1764
1764
  #node = new Node3();
1765
- add(method, path8, handler) {
1766
- for (const result of checkOptionalParameter(path8) || [path8]) this.#node.insert(method, result, handler);
1765
+ add(method, path9, handler) {
1766
+ for (const result of checkOptionalParameter(path9) || [path9]) this.#node.insert(method, result, handler);
1767
1767
  }
1768
- match(method, path8) {
1769
- return this.#node.search(method, path8);
1768
+ match(method, path9) {
1769
+ return this.#node.search(method, path9);
1770
1770
  }
1771
1771
  };
1772
1772
 
@@ -3449,8 +3449,8 @@ import path3 from "node:path";
3449
3449
  import { spawn as spawn2 } from "node:child_process";
3450
3450
  var lives = /* @__PURE__ */ new Map();
3451
3451
  var IDLE_MS = 10 * 6e4;
3452
- function serveCommand(cwd, agentDir2 = process.env.PI_AGENT_DIR ?? path3.join(process.env.HOME ?? "", ".pi", "agent")) {
3453
- for (const f of [path3.join(cwd, ".pi", "loops.json"), path3.join(agentDir2, "loops.json")]) {
3452
+ function serveCommand(cwd, agentDir3 = process.env.PI_AGENT_DIR ?? path3.join(process.env.HOME ?? "", ".pi", "agent")) {
3453
+ for (const f of [path3.join(cwd, ".pi", "loops.json"), path3.join(agentDir3, "loops.json")]) {
3454
3454
  try {
3455
3455
  const j = JSON.parse(fs3.readFileSync(f, "utf8"));
3456
3456
  if (typeof j?.serve === "string" && j.serve.trim()) return j.serve.trim();
@@ -4085,11 +4085,101 @@ async function doctor(cwd) {
4085
4085
  return { checks, verdict: fatal ? "will-fail" : checks.every((c) => c.ok) ? "ready" : "incomplete" };
4086
4086
  }
4087
4087
 
4088
- // server/workspaces.ts
4088
+ // ../pi-loops/lib/credentials.ts
4089
4089
  import fs6 from "node:fs";
4090
+ import os3 from "node:os";
4090
4091
  import path6 from "node:path";
4092
+ import { spawn as spawn5 } from "node:child_process";
4093
+ var PROVIDERS = [
4094
+ { id: "particle", label: "Particle AI" },
4095
+ { id: "morph", label: "Morph" },
4096
+ { id: "openrouter", label: "OpenRouter" },
4097
+ { id: "anthropic", label: "Anthropic" },
4098
+ { id: "openai", label: "OpenAI" },
4099
+ { id: "deepseek", label: "DeepSeek" }
4100
+ ];
4101
+ var agentDir2 = () => process.env.PI_AGENT_DIR ?? path6.join(os3.homedir(), ".pi", "agent");
4102
+ var authFile = (dir = agentDir2()) => path6.join(dir, "auth.json");
4103
+ var envVar = (provider) => `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
4104
+ function credentialStatus(dir = agentDir2()) {
4105
+ let auth = {};
4106
+ try {
4107
+ const raw2 = fs6.readFileSync(authFile(dir), "utf8");
4108
+ auth = JSON.parse(raw2) ?? {};
4109
+ } catch {
4110
+ }
4111
+ return PROVIDERS.map((p) => {
4112
+ const env = envVar(p.id);
4113
+ const entry = auth?.[p.id];
4114
+ const inAuth = !!(entry && typeof entry === "object" && (entry.key || entry.type === "oauth"));
4115
+ return { id: p.id, label: p.label, env, configured: inAuth || !!process.env[env], source: inAuth ? "auth" : process.env[env] ? "env" : "none" };
4116
+ });
4117
+ }
4118
+ var chain = Promise.resolve();
4119
+ function queued(fn) {
4120
+ const next = chain.then(fn, fn);
4121
+ chain = next.catch(() => {
4122
+ });
4123
+ return next;
4124
+ }
4125
+ async function writeApiKey(provider, key, dir = agentDir2()) {
4126
+ const trimmed = key.trim();
4127
+ if (!trimmed) throw new Error("a key is required");
4128
+ if (!PROVIDERS.some((p) => p.id === provider)) throw new Error(`unknown provider "${provider}"`);
4129
+ return queued(async () => {
4130
+ const file = authFile(dir);
4131
+ let auth = {};
4132
+ try {
4133
+ const raw2 = fs6.readFileSync(file, "utf8");
4134
+ const parsed = raw2.trim() ? JSON.parse(raw2) : {};
4135
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) auth = parsed;
4136
+ } catch {
4137
+ }
4138
+ auth[provider] = { ...auth[provider] && typeof auth[provider] === "object" ? auth[provider] : {}, type: "api_key", key: trimmed };
4139
+ fs6.mkdirSync(dir, { recursive: true });
4140
+ const tmp = `${file}.${process.pid}.tmp`;
4141
+ fs6.writeFileSync(tmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
4142
+ fs6.renameSync(tmp, file);
4143
+ try {
4144
+ fs6.chmodSync(file, 384);
4145
+ } catch {
4146
+ }
4147
+ return file;
4148
+ });
4149
+ }
4150
+ async function verifyModel({ pi, model, cwd, timeoutMs = 6e4 }) {
4151
+ const args = ["--print", "--no-session", "--model", model, "Reply with the single word: ok"];
4152
+ return new Promise((resolve) => {
4153
+ let out = "";
4154
+ const child = spawn5(pi, args, { cwd, stdio: ["ignore", "pipe", "ignore"] });
4155
+ const timer = setTimeout(() => {
4156
+ try {
4157
+ child.kill("SIGKILL");
4158
+ } catch {
4159
+ }
4160
+ resolve({ ok: false, detail: `no answer in ${Math.round(timeoutMs / 1e3)}s` });
4161
+ }, timeoutMs);
4162
+ child.stdout.on("data", (b) => {
4163
+ out += String(b);
4164
+ });
4165
+ child.on("error", (e) => {
4166
+ clearTimeout(timer);
4167
+ resolve({ ok: false, detail: `could not run ${pi}: ${e.message}` });
4168
+ });
4169
+ child.on("close", (code) => {
4170
+ clearTimeout(timer);
4171
+ const said = out.split("\n").map((l) => l.trim()).filter(Boolean).slice(-2).join(" \xB7 ").slice(-160);
4172
+ if (code === 0 && /\bok\b/i.test(said)) return resolve({ ok: true, detail: `answered: ${said}` });
4173
+ resolve({ ok: false, detail: `exit ${code ?? "?"}${said ? ` \u2014 ${said}` : ""}`.trim() });
4174
+ });
4175
+ });
4176
+ }
4177
+
4178
+ // server/workspaces.ts
4179
+ import fs7 from "node:fs";
4180
+ import path7 from "node:path";
4091
4181
  import { execFileSync } from "node:child_process";
4092
- var slug = (p) => path6.basename(p).replace(/[^a-z0-9._-]+/gi, "-").toLowerCase() || "repo";
4182
+ var slug = (p) => path7.basename(p).replace(/[^a-z0-9._-]+/gi, "-").toLowerCase() || "repo";
4093
4183
  var Workspaces = class {
4094
4184
  /** Where workspaces.json and the per-repo session dirs live. */
4095
4185
  root;
@@ -4099,15 +4189,15 @@ var Workspaces = class {
4099
4189
  chatMap;
4100
4190
  constructor({ root, cwd }) {
4101
4191
  this.root = root;
4102
- this.file = path6.join(root, "workspaces.json");
4103
- fs6.mkdirSync(root, { recursive: true });
4192
+ this.file = path7.join(root, "workspaces.json");
4193
+ fs7.mkdirSync(root, { recursive: true });
4104
4194
  let data = { workspaces: [], chats: {} };
4105
- if (fs6.existsSync(this.file)) {
4195
+ if (fs7.existsSync(this.file)) {
4106
4196
  try {
4107
- data = JSON.parse(fs6.readFileSync(this.file, "utf8"));
4197
+ data = JSON.parse(fs7.readFileSync(this.file, "utf8"));
4108
4198
  } catch {
4109
4199
  try {
4110
- fs6.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
4200
+ fs7.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
4111
4201
  } catch {
4112
4202
  }
4113
4203
  console.error(`[workspaces] could not read ${this.file}; kept a copy and rebuilding from sessions`);
@@ -4124,8 +4214,8 @@ var Workspaces = class {
4124
4214
  this.data.chats = Object.fromEntries(this.chatMap);
4125
4215
  try {
4126
4216
  const tmp = `${this.file}.tmp`;
4127
- fs6.writeFileSync(tmp, JSON.stringify(this.data, null, 2) + "\n");
4128
- fs6.renameSync(tmp, this.file);
4217
+ fs7.writeFileSync(tmp, JSON.stringify(this.data, null, 2) + "\n");
4218
+ fs7.renameSync(tmp, this.file);
4129
4219
  } catch {
4130
4220
  }
4131
4221
  }
@@ -4140,18 +4230,18 @@ var Workspaces = class {
4140
4230
  }
4141
4231
  /** Add (or re-find) a repo. `persist: false` is used for the startup cwd. */
4142
4232
  add(dir, { persist = true } = {}) {
4143
- const abs = path6.resolve(dir);
4233
+ const abs = path7.resolve(dir);
4144
4234
  const existing = [...this.byId.values()].find((w) => w.path === abs);
4145
4235
  if (existing) return existing;
4146
- if (!fs6.existsSync(abs) || !fs6.statSync(abs).isDirectory()) throw new Error(`${abs} is not a directory`);
4147
- const st = fs6.statSync(abs);
4236
+ if (!fs7.existsSync(abs) || !fs7.statSync(abs).isDirectory()) throw new Error(`${abs} is not a directory`);
4237
+ const st = fs7.statSync(abs);
4148
4238
  if (!st.isDirectory()) throw new Error(`${abs} is not a directory`);
4149
4239
  let id = slug(abs);
4150
4240
  for (let i = 2; this.byId.has(id); i++) id = `${slug(abs)}-${i}`;
4151
- const legacy = path6.join(abs, ".pi", "loops-ui", "sessions");
4152
- const sessionDir = fs6.existsSync(legacy) ? legacy : path6.join(this.root, "workspaces", id, "sessions");
4153
- const ws = { id, path: abs, sessionDir, title: path6.basename(abs) || abs };
4154
- fs6.mkdirSync(ws.sessionDir, { recursive: true });
4241
+ const legacy = path7.join(abs, ".pi", "loops-ui", "sessions");
4242
+ const sessionDir = fs7.existsSync(legacy) ? legacy : path7.join(this.root, "workspaces", id, "sessions");
4243
+ const ws = { id, path: abs, sessionDir, title: path7.basename(abs) || abs };
4244
+ fs7.mkdirSync(ws.sessionDir, { recursive: true });
4155
4245
  this.byId.set(id, ws);
4156
4246
  if (persist) {
4157
4247
  this.data.workspaces = [...this.data.workspaces.filter((w) => w.path !== abs), { id, path: abs }];
@@ -4175,25 +4265,25 @@ var Workspaces = class {
4175
4265
  * workspace. This runs on startup, so a lost or stale `workspaces.json` is a cache miss, not lost history.
4176
4266
  */
4177
4267
  recoverSessions() {
4178
- const root = path6.join(this.root, "workspaces");
4268
+ const root = path7.join(this.root, "workspaces");
4179
4269
  let ids = [];
4180
4270
  try {
4181
- ids = fs6.readdirSync(root);
4271
+ ids = fs7.readdirSync(root);
4182
4272
  } catch {
4183
4273
  return;
4184
4274
  }
4185
4275
  for (const id of ids) {
4186
- const dir = path6.join(root, id, "sessions");
4276
+ const dir = path7.join(root, id, "sessions");
4187
4277
  let files = [];
4188
4278
  try {
4189
- files = fs6.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
4279
+ files = fs7.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
4190
4280
  } catch {
4191
4281
  continue;
4192
4282
  }
4193
4283
  let cwd = null;
4194
4284
  for (const f of files) {
4195
4285
  try {
4196
- const head = JSON.parse(fs6.readFileSync(path6.join(dir, f), "utf8").split("\n")[0]);
4286
+ const head = JSON.parse(fs7.readFileSync(path7.join(dir, f), "utf8").split("\n")[0]);
4197
4287
  if (typeof head?.cwd === "string" && head.cwd) {
4198
4288
  cwd = head.cwd;
4199
4289
  break;
@@ -4248,8 +4338,8 @@ var Workspaces = class {
4248
4338
  };
4249
4339
 
4250
4340
  // server/index.ts
4251
- var HERE = path7.dirname(fileURLToPath(import.meta.url));
4252
- var APP_ROOT = path7.resolve(HERE, "..");
4341
+ var HERE = path8.dirname(fileURLToPath(import.meta.url));
4342
+ var APP_ROOT = path8.resolve(HERE, "..");
4253
4343
  var arg = (name, fallback) => {
4254
4344
  const i = process.argv.indexOf(`--${name}`);
4255
4345
  return i >= 0 ? process.argv[i + 1] : process.env[`LOOPS_UI_${name.toUpperCase().replace(/-/g, "_")}`] ?? fallback;
@@ -4257,16 +4347,16 @@ var arg = (name, fallback) => {
4257
4347
  var PORT = Number(arg("port", "8001"));
4258
4348
  var HOST = arg("host", "127.0.0.1");
4259
4349
  var LOOPBACK = ["127.0.0.1", "localhost", "::1"].includes(HOST);
4260
- var CWD = path7.resolve(arg("cwd", process.cwd()));
4350
+ var CWD = path8.resolve(arg("cwd", process.cwd()));
4261
4351
  var DEV = process.argv.includes("--dev") || process.env.NODE_ENV === "development";
4262
4352
  var VITE = arg("vite", "http://localhost:5173");
4263
4353
  var PI = arg("pi", "pi");
4264
- var DATA = path7.resolve(arg("data", process.env.LOOPS_UI_DATA ?? path7.join(process.env.HOME ?? process.cwd(), ".pi", "loops-ui")));
4354
+ var DATA = path8.resolve(arg("data", process.env.LOOPS_UI_DATA ?? path8.join(process.env.HOME ?? process.cwd(), ".pi", "loops-ui")));
4265
4355
  var MODEL = arg("model", void 0) || null;
4266
- var EXTENSIONS = (arg("extensions", path7.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
4356
+ var EXTENSIONS = (arg("extensions", path8.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
4267
4357
  function prerequisites() {
4268
4358
  const out = [];
4269
- const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs7.existsSync(PI) : (() => {
4359
+ const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs8.existsSync(PI) : (() => {
4270
4360
  try {
4271
4361
  execFileSync2(process.platform === "win32" ? "where" : "which", [PI], { stdio: ["ignore", "pipe", "ignore"] });
4272
4362
  return true;
@@ -4276,18 +4366,18 @@ function prerequisites() {
4276
4366
  })();
4277
4367
  if (!onPath) out.push({ name: "pi", detail: `\`${PI}\` is not on PATH`, fix: "install Pi, or pass --pi <path to the pi binary>" });
4278
4368
  for (const e of EXTENSIONS) {
4279
- if (!fs7.existsSync(e)) out.push({ name: "extension", detail: `${e} does not exist`, fix: "pi install npm:@computesdk/pi-loops, or pass --extensions <path to extensions/loop.ts>" });
4369
+ if (!fs8.existsSync(e)) out.push({ name: "extension", detail: `${e} does not exist`, fix: "pi install npm:@computesdk/pi-loops, or pass --extensions <path to extensions/loop.ts>" });
4280
4370
  }
4281
4371
  return out;
4282
4372
  }
4283
4373
  var PROBLEMS = prerequisites();
4284
- fs7.mkdirSync(DATA, { recursive: true });
4374
+ fs8.mkdirSync(DATA, { recursive: true });
4285
4375
  var TOKEN = (() => {
4286
4376
  const explicit = arg("token", void 0);
4287
- const file = path7.join(DATA, "token");
4377
+ const file = path8.join(DATA, "token");
4288
4378
  const stored = (() => {
4289
4379
  try {
4290
- return fs7.readFileSync(file, "utf8").trim() || null;
4380
+ return fs8.readFileSync(file, "utf8").trim() || null;
4291
4381
  } catch {
4292
4382
  return null;
4293
4383
  }
@@ -4295,7 +4385,7 @@ var TOKEN = (() => {
4295
4385
  if (explicit) {
4296
4386
  if (!LOOPBACK) {
4297
4387
  try {
4298
- fs7.writeFileSync(file, explicit + "\n", { mode: 384 });
4388
+ fs8.writeFileSync(file, explicit + "\n", { mode: 384 });
4299
4389
  } catch {
4300
4390
  }
4301
4391
  }
@@ -4304,7 +4394,7 @@ var TOKEN = (() => {
4304
4394
  if (stored) return stored;
4305
4395
  if (LOOPBACK) return null;
4306
4396
  const made = randomBytes(24).toString("base64url");
4307
- fs7.writeFileSync(file, made + "\n", { mode: 384 });
4397
+ fs8.writeFileSync(file, made + "\n", { mode: 384 });
4308
4398
  return made;
4309
4399
  })();
4310
4400
  if (!LOOPBACK && !TOKEN) {
@@ -4324,7 +4414,7 @@ var manager = new ChatManager({
4324
4414
  });
4325
4415
  var wsOf = (chat) => chat ? workspaces.ofChat(chat) : workspaces.get();
4326
4416
  var wsOfRun = (run3) => {
4327
- const byIndex = workspaces.list().find((w) => fs7.existsSync(path7.join(w.path, STATE_DIR, `${run3}.json`)));
4417
+ const byIndex = workspaces.list().find((w) => fs8.existsSync(path8.join(w.path, STATE_DIR, `${run3}.json`)));
4328
4418
  if (byIndex) return byIndex;
4329
4419
  const chat = manager.findRun(run3);
4330
4420
  return chat ? workspaces.ofChat(chat.id) : workspaces.get();
@@ -4352,7 +4442,7 @@ var watchOf = (wsId) => {
4352
4442
  if (!w) {
4353
4443
  const read = () => {
4354
4444
  try {
4355
- return JSON.parse(fs7.readFileSync(path7.join(ws.path, ".pi", "loops.json"), "utf8"));
4445
+ return JSON.parse(fs8.readFileSync(path8.join(ws.path, ".pi", "loops.json"), "utf8"));
4356
4446
  } catch {
4357
4447
  return {};
4358
4448
  }
@@ -4360,7 +4450,7 @@ var watchOf = (wsId) => {
4360
4450
  w = { cwd: ws.path, accepted: read() };
4361
4451
  watches.set(ws.id, w);
4362
4452
  try {
4363
- fs7.watch(path7.join(ws.path, ".pi"), (_e, name) => {
4453
+ fs8.watch(path8.join(ws.path, ".pi"), (_e, name) => {
4364
4454
  if (name && name !== "loops.json") return;
4365
4455
  clearTimeout(watchTimers.get(ws.id));
4366
4456
  watchTimers.set(ws.id, setTimeout(() => checkConfig(ws.id), 120));
@@ -4382,10 +4472,10 @@ function notifyProposals() {
4382
4472
  }
4383
4473
  function checkConfig(wsId) {
4384
4474
  const w = watchOf(wsId);
4385
- const CONFIG = path7.join(w.cwd, ".pi", "loops.json");
4475
+ const CONFIG = path8.join(w.cwd, ".pi", "loops.json");
4386
4476
  const raw2 = (() => {
4387
4477
  try {
4388
- return fs7.readFileSync(CONFIG, "utf8");
4478
+ return fs8.readFileSync(CONFIG, "utf8");
4389
4479
  } catch {
4390
4480
  return "";
4391
4481
  }
@@ -4406,16 +4496,29 @@ function checkConfig(wsId) {
4406
4496
  notifyProposals();
4407
4497
  }
4408
4498
  var inside = (root, p) => {
4409
- const abs = path7.resolve(root, p);
4410
- return abs === root || abs.startsWith(root + path7.sep) ? abs : null;
4499
+ const abs = path8.resolve(root, p);
4500
+ return abs === root || abs.startsWith(root + path8.sep) ? abs : null;
4411
4501
  };
4412
4502
  var stateOf = (run3) => manager.findRun(run3)?.stateFor(run3) ?? readState(wsOfRun(run3).path, run3);
4413
4503
  var app = new Hono3();
4504
+ var OWN_HOSTS = /* @__PURE__ */ new Set([`127.0.0.1:${PORT}`, `localhost:${PORT}`, `[::1]:${PORT}`]);
4505
+ var OWN_ORIGINS = new Set([...OWN_HOSTS].map((h) => `http://${h}`));
4414
4506
  var ALLOWED_ORIGINS = (arg("allow-origin", "") ?? "").split(",").map((o) => o.trim()).filter(Boolean);
4415
- var originAllowed = (origin) => !ALLOWED_ORIGINS.length || ALLOWED_ORIGINS.includes(origin) || ALLOWED_ORIGINS.includes("*");
4507
+ var originAllowed = (origin) => {
4508
+ if (ALLOWED_ORIGINS.includes("*")) return true;
4509
+ if (ALLOWED_ORIGINS.length) return ALLOWED_ORIGINS.includes(origin);
4510
+ return !LOOPBACK || OWN_ORIGINS.has(origin);
4511
+ };
4416
4512
  app.use("*", async (c, next) => {
4513
+ const host = c.req.header("host") ?? "";
4514
+ if (LOOPBACK && !OWN_HOSTS.has(host)) {
4515
+ return c.text(`this server answers as ${[...OWN_HOSTS].join(", ")}, not "${host}"`, 403);
4516
+ }
4417
4517
  const origin = c.req.header("origin");
4418
- if (origin && originAllowed(origin)) {
4518
+ if (origin && !originAllowed(origin)) {
4519
+ return c.json({ error: "origin not allowed", origin }, 403);
4520
+ }
4521
+ if (origin) {
4419
4522
  c.header("access-control-allow-origin", origin);
4420
4523
  c.header("access-control-allow-headers", "authorization, content-type, last-event-id");
4421
4524
  c.header("access-control-allow-methods", "GET, POST, OPTIONS");
@@ -4438,6 +4541,21 @@ app.use("/api/v1/*", async (c, next) => {
4438
4541
  });
4439
4542
  var api = new Hono3();
4440
4543
  api.get("/health", (c) => c.json({ ok: PROBLEMS.length === 0, problems: PROBLEMS, cwd: CWD, data: DATA, pi: PI, extensions: EXTENSIONS, workspaces: workspaces.list() }));
4544
+ api.get("/credentials", (c) => c.json({ dir: agentDir2(), providers: credentialStatus() }));
4545
+ api.post("/credentials", async (c) => {
4546
+ const body = await c.req.json().catch(() => ({}));
4547
+ const provider = String(body?.provider ?? "");
4548
+ const key = String(body?.key ?? "").trim();
4549
+ if (!PROVIDERS.some((p) => p.id === provider)) return c.json({ error: `unknown provider "${provider}" \u2014 one of ${PROVIDERS.map((p) => p.id).join(", ")}` }, 400);
4550
+ if (!key) return c.json({ error: "a key is required" }, 400);
4551
+ try {
4552
+ const file = await writeApiKey(provider, key);
4553
+ const verified = body?.model ? await verifyModel({ pi: PI, model: String(body.model), cwd: CWD }) : null;
4554
+ return c.json({ ok: true, provider, file, configured: credentialStatus().find((s) => s.id === provider)?.configured ?? true, verified });
4555
+ } catch (e) {
4556
+ return c.json({ error: String(e.message ?? e).slice(0, 200) }, 400);
4557
+ }
4558
+ });
4441
4559
  api.get("/chats", (c) => c.json(chatsOf(c.req.query("ws"))));
4442
4560
  api.get("/chats/:id/events", async (c) => {
4443
4561
  const id = c.req.param("id");
@@ -4480,9 +4598,9 @@ api.post("/chat/delete", async (c) => {
4480
4598
  const file = chat?.sessionFile ?? null;
4481
4599
  await manager.close(String(id));
4482
4600
  workspaces.unbindChat(String(id));
4483
- if (file && fs7.existsSync(file)) {
4601
+ if (file && fs8.existsSync(file)) {
4484
4602
  try {
4485
- fs7.rmSync(file);
4603
+ fs8.rmSync(file);
4486
4604
  } catch {
4487
4605
  }
4488
4606
  }
@@ -4492,9 +4610,9 @@ api.get("/workspaces", (c) => c.json(workspaces.list()));
4492
4610
  api.post("/workspaces", async (c) => {
4493
4611
  const body = await c.req.json().catch(() => ({}));
4494
4612
  try {
4495
- const dir = path7.resolve(String(body.path ?? "").replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
4613
+ const dir = path8.resolve(String(body.path ?? "").replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
4496
4614
  if (!dir || dir === "/") throw new Error("a path is required");
4497
- if (!fs7.existsSync(dir) || !fs7.statSync(dir).isDirectory()) throw new Error(`${dir} is not a directory`);
4615
+ if (!fs8.existsSync(dir) || !fs8.statSync(dir).isDirectory()) throw new Error(`${dir} is not a directory`);
4498
4616
  const ws = workspaces.add(dir);
4499
4617
  watchOf(ws.id);
4500
4618
  return c.json({ ok: true, workspace: ws, loopable: Workspaces.loopable(ws.path) });
@@ -4578,7 +4696,7 @@ api.get("/session", async (c) => {
4578
4696
  if (!run3 || !name) return c.json([]);
4579
4697
  const state = stateOf(run3);
4580
4698
  const file = state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label === name)?.session ?? state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label?.toLowerCase() === name.toLowerCase())?.session;
4581
- if (!file || !fs7.existsSync(file)) return c.json([]);
4699
+ if (!file || !fs8.existsSync(file)) return c.json([]);
4582
4700
  const chat = await manager.open(c.req.query("chat") ?? manager.list()[0]?.id ?? "");
4583
4701
  return c.json(chat.sessionItems(file));
4584
4702
  });
@@ -4597,21 +4715,21 @@ api.get("/shot", (c) => {
4597
4715
  const repo = wsOfRun(run3).path;
4598
4716
  if (file.startsWith(STATE_DIR)) {
4599
4717
  const abs2 = inside(repo, file);
4600
- if (!abs2 || !fs7.existsSync(abs2)) return c.notFound();
4601
- return new Response(fs7.readFileSync(abs2), { headers: { "content-type": abs2.endsWith(".jpg") || abs2.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4718
+ if (!abs2 || !fs8.existsSync(abs2)) return c.notFound();
4719
+ return new Response(fs8.readFileSync(abs2), { headers: { "content-type": abs2.endsWith(".jpg") || abs2.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4602
4720
  }
4603
4721
  const dir = attempt === "CURRENT" ? state?.best?.dir ?? null : state?.rounds?.flatMap((r) => r.attempts).find((a) => a.label === attempt || a.label?.toLowerCase() === attempt.toLowerCase())?.dir;
4604
4722
  if (!dir) return c.notFound();
4605
- const abs = inside(repo, path7.join(dir, file));
4606
- if (!abs || !fs7.existsSync(abs)) return c.notFound();
4607
- return new Response(fs7.readFileSync(abs), { headers: { "content-type": abs.endsWith(".jpg") || abs.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4723
+ const abs = inside(repo, path8.join(dir, file));
4724
+ if (!abs || !fs8.existsSync(abs)) return c.notFound();
4725
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": abs.endsWith(".jpg") || abs.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4608
4726
  });
4609
4727
  api.get("/sheet", (c) => {
4610
4728
  const file = c.req.query("f") ?? "";
4611
- const ws = workspaces.list().find((w) => fs7.existsSync(path7.join(w.path, file))) ?? workspaces.get();
4729
+ const ws = workspaces.list().find((w) => fs8.existsSync(path8.join(w.path, file))) ?? workspaces.get();
4612
4730
  const abs = inside(ws.path, file);
4613
- if (!abs || !abs.includes(STATE_DIR) || !fs7.existsSync(abs)) return c.notFound();
4614
- return new Response(fs7.readFileSync(abs), { headers: { "content-type": "image/png", "cache-control": "no-cache" } });
4731
+ if (!abs || !abs.includes(STATE_DIR) || !fs8.existsSync(abs)) return c.notFound();
4732
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": "image/png", "cache-control": "no-cache" } });
4615
4733
  });
4616
4734
  api.get("/diff", async (c) => {
4617
4735
  const run3 = c.req.query("run") ?? "";
@@ -4675,7 +4793,7 @@ api.post("/config", async (c) => {
4675
4793
  writeSetting(ws.path, String(key), value);
4676
4794
  const w = watchOf(ws.id);
4677
4795
  try {
4678
- w.accepted = JSON.parse(fs7.readFileSync(path7.join(ws.path, ".pi", "loops.json"), "utf8"));
4796
+ w.accepted = JSON.parse(fs8.readFileSync(path8.join(ws.path, ".pi", "loops.json"), "utf8"));
4679
4797
  } catch {
4680
4798
  }
4681
4799
  return c.json({ ok: true });
@@ -4722,7 +4840,7 @@ api.get("/meta", (c) => c.json({
4722
4840
  }));
4723
4841
  app.route("/api", api);
4724
4842
  app.route("/api/v1", api);
4725
- var DIST = path7.join(APP_ROOT, "dist");
4843
+ var DIST = path8.join(APP_ROOT, "dist");
4726
4844
  app.get("*", async (c) => {
4727
4845
  const url = c.req.path;
4728
4846
  if (DEV) {
@@ -4731,14 +4849,14 @@ app.get("*", async (c) => {
4731
4849
  return new Response(res.body, { status: res.status, headers: { "content-type": res.headers.get("content-type") ?? "text/html" } });
4732
4850
  }
4733
4851
  const abs = inside(DIST, url === "/" ? "index.html" : url.replace(/^\//, ""));
4734
- if (abs && fs7.existsSync(abs) && fs7.statSync(abs).isFile()) {
4735
- const ext = path7.extname(abs);
4852
+ if (abs && fs8.existsSync(abs) && fs8.statSync(abs).isFile()) {
4853
+ const ext = path8.extname(abs);
4736
4854
  const type = ext === ".js" ? "text/javascript" : ext === ".css" ? "text/css" : ext === ".woff2" ? "font/woff2" : ext === ".svg" ? "image/svg+xml" : ext === ".png" ? "image/png" : "text/html";
4737
- return new Response(fs7.readFileSync(abs), { headers: { "content-type": type } });
4855
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": type } });
4738
4856
  }
4739
- const index = path7.join(DIST, "index.html");
4740
- if (!fs7.existsSync(index)) return c.text("run `npm run build` first", 500);
4741
- return new Response(fs7.readFileSync(index), { headers: { "content-type": "text/html" } });
4857
+ const index = path8.join(DIST, "index.html");
4858
+ if (!fs8.existsSync(index)) return c.text("run `npm run build` first", 500);
4859
+ return new Response(fs8.readFileSync(index), { headers: { "content-type": "text/html" } });
4742
4860
  });
4743
4861
  var server = http.createServer(getRequestListener(app.fetch));
4744
4862
  setInterval(() => {
@@ -4748,7 +4866,7 @@ function openBrowser(url) {
4748
4866
  if (process.argv.includes("--no-open") || process.env.LOOPS_UI_OPEN === "0") return;
4749
4867
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
4750
4868
  try {
4751
- const p = spawn5(cmd, args, { stdio: "ignore", detached: true });
4869
+ const p = spawn6(cmd, args, { stdio: "ignore", detached: true });
4752
4870
  p.on("error", () => {
4753
4871
  });
4754
4872
  p.unref();