@computesdk/loops 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-CqEf5Tyi.js} +1 -1
  5. package/dist/assets/{arc-C71M1We_.js → arc-CrYrleHr.js} +1 -1
  6. package/dist/assets/{architectureDiagram-5GKGNRK7-C3PLmrM3.js → architectureDiagram-5GKGNRK7-D5K4RlB6.js} +1 -1
  7. package/dist/assets/{blockDiagram-I7D4REHJ-DFshwmKE.js → blockDiagram-I7D4REHJ-Co-w8wX9.js} +1 -1
  8. package/dist/assets/{c4Diagram-7LVT6UL2-Dh29QjGI.js → c4Diagram-7LVT6UL2-DlcBV2hX.js} +1 -1
  9. package/dist/assets/channel-DoVi6ksL.js +1 -0
  10. package/dist/assets/{chunk-2Q5K7J3B-qZ7USlsH.js → chunk-2Q5K7J3B-Dswi4LXs.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-YQpRymiv.js → chunk-5VM5RSS4-CtLKoCf3.js} +1 -1
  12. package/dist/assets/{chunk-F27PBJKO-D2O95m9O.js → chunk-F27PBJKO-Bbck4p8J.js} +1 -1
  13. package/dist/assets/{chunk-IMKFNOWR-CftSjfdl.js → chunk-IMKFNOWR-CvTEJd26.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-_1wtK-EO.js → chunk-JWPE2WC7-AIHpJEyX.js} +1 -1
  15. package/dist/assets/{chunk-POPQ4Y6H-DJ2Xmp8X.js → chunk-POPQ4Y6H-C_B3I7hU.js} +1 -1
  16. package/dist/assets/{chunk-SVP7TREG-CAtSHcje.js → chunk-SVP7TREG-au1NQO2y.js} +1 -1
  17. package/dist/assets/{chunk-TICWLB2K-1BASqzB5.js → chunk-TICWLB2K-DOQXr0QZ.js} +1 -1
  18. package/dist/assets/{chunk-XXDRQBXY-CWs6lP_i.js → chunk-XXDRQBXY-U36QglrB.js} +1 -1
  19. package/dist/assets/classDiagram-ZZMXUADV-Cyi65D9L.js +1 -0
  20. package/dist/assets/classDiagram-v2-VYDZK3BY-Cyi65D9L.js +1 -0
  21. package/dist/assets/{cose-bilkent-JH36ORCC-9dCpEIjz.js → cose-bilkent-JH36ORCC-D7GIzP1z.js} +1 -1
  22. package/dist/assets/{cynefin-OW5HDTMX-EFt_R8HZ.js → cynefin-OW5HDTMX-CjokfIiy.js} +1 -1
  23. package/dist/assets/{cynefinDiagram-5FMLGOSQ-BuManjb1.js → cynefinDiagram-5FMLGOSQ-1aEkMuPh.js} +1 -1
  24. package/dist/assets/{dagre-GXQ25YYZ-MovLAGVL.js → dagre-GXQ25YYZ-BiIp4Lpg.js} +1 -1
  25. package/dist/assets/{diagram-S7CK7UJ4-BiJw5LQ6.js → diagram-S7CK7UJ4-BZSI7zSx.js} +1 -1
  26. package/dist/assets/{diagram-UQ7AKVKN-DRsX_xa6.js → diagram-UQ7AKVKN-BRdBU2w8.js} +1 -1
  27. package/dist/assets/{diagram-VSXAHHWV-B7m6F8Dm.js → diagram-VSXAHHWV-DQ-h_ycl.js} +1 -1
  28. package/dist/assets/{diagram-VX7I27RA-x4wioeVA.js → diagram-VX7I27RA-DmLQWSdL.js} +1 -1
  29. package/dist/assets/{diagram-Z3DM3KII-0KYjyvJF.js → diagram-Z3DM3KII-m8FGF4_n.js} +1 -1
  30. package/dist/assets/{ebnfDiagram-PWID7BFC-BZXmt3oU.js → ebnfDiagram-PWID7BFC-DtQ-Rw5F.js} +1 -1
  31. package/dist/assets/{erDiagram-RLTQ6QDP-CrfcYtD6.js → erDiagram-RLTQ6QDP-DIf-60L3.js} +1 -1
  32. package/dist/assets/{flowDiagram-HODETNUW-BqudKoEy.js → flowDiagram-HODETNUW-COvzkFo5.js} +1 -1
  33. package/dist/assets/{ganttDiagram-EL5Y4UJY-OBWjueb8.js → ganttDiagram-EL5Y4UJY-Dhz_v-Sb.js} +1 -1
  34. package/dist/assets/{gitGraphDiagram-WWUBYQGX-B30e199R.js → gitGraphDiagram-WWUBYQGX-BwkibubM.js} +1 -1
  35. package/dist/assets/{highlighted-body-JLMTFACH-DapGkHdb.js → highlighted-body-JLMTFACH-CbkekRih.js} +1 -1
  36. package/dist/assets/{index-C_bKTXmh.js → index-DhZYU086.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-BltzH8hk.js} +1 -1
  39. package/dist/assets/{ishikawaDiagram-5VMMS53U-B0fvRdQL.js → ishikawaDiagram-5VMMS53U-CKXLbULh.js} +1 -1
  40. package/dist/assets/{journeyDiagram-3NMN7TZE-CZBLF7ld.js → journeyDiagram-3NMN7TZE-Co-OVAQM.js} +1 -1
  41. package/dist/assets/{kanban-definition-UXKFOSKX-wTBJWh9T.js → kanban-definition-UXKFOSKX-QEXNvAHJ.js} +1 -1
  42. package/dist/assets/{layout-DYBs-gVA.js → layout-DlW7eXy-.js} +1 -1
  43. package/dist/assets/{linear-DTE3zVP_.js → linear-DmNmIGNl.js} +1 -1
  44. package/dist/assets/{mindmap-definition-YA3MSWOX-tV3HPpzU.js → mindmap-definition-YA3MSWOX-CfrGbvrI.js} +1 -1
  45. package/dist/assets/{pegDiagram-XKGWAZYB-B6Yznbb5.js → pegDiagram-XKGWAZYB-BRPtQX-b.js} +1 -1
  46. package/dist/assets/{pieDiagram-E7YTZNPT-B6QW8pJH.js → pieDiagram-E7YTZNPT-cyRAi108.js} +1 -1
  47. package/dist/assets/{quadrantDiagram-AXDQQJYC-DgHGTtj0.js → quadrantDiagram-AXDQQJYC-BBwMoijR.js} +1 -1
  48. package/dist/assets/{railroadDiagram-O6MQD6OU-CgqVFEBK.js → railroadDiagram-O6MQD6OU-DicocbzE.js} +1 -1
  49. package/dist/assets/{requirementDiagram-BXWQKSXE-FmXMY-6j.js → requirementDiagram-BXWQKSXE-DDiXCN4F.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-P5KCCOFB-AVMEYVKy.js → sankeyDiagram-P5KCCOFB-DnDGP5-W.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-WJ2MYXX4-uOObReYi.js → sequenceDiagram-WJ2MYXX4-C1NFZjf0.js} +1 -1
  52. package/dist/assets/{sizeCapture-INFHLROL-BPOPqqxk.js → sizeCapture-INFHLROL-CEF6O58z.js} +1 -1
  53. package/dist/assets/{stateDiagram-D77RDMKH-C1lXHNdu.js → stateDiagram-D77RDMKH-Ibszr2iY.js} +1 -1
  54. package/dist/assets/stateDiagram-v2-MP3YSRHH-CCH6TZvE.js +1 -0
  55. package/dist/assets/{swimlanes-42K2YHIH-C52GOi05.js → swimlanes-42K2YHIH-DbvtObGJ.js} +1 -1
  56. package/dist/assets/swimlanesDiagram-VR7AAH4N-DyRa4rww.js +8 -0
  57. package/dist/assets/{timeline-definition-24CTP7MA-T0BhAPL-.js → timeline-definition-24CTP7MA-C6qVzSzu.js} +1 -1
  58. package/dist/assets/{vennDiagram-4TSXK5OY-DSGCn4iB.js → vennDiagram-4TSXK5OY-B3ZZFoCj.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-VM6X3IG4-BaCYx_03.js → wardleyDiagram-VM6X3IG4-C9pFlzx9.js} +1 -1
  60. package/dist/assets/{xychartDiagram-S5SC5T6Z-CgPwXvFv.js → xychartDiagram-S5SC5T6Z-mUkoBUkH.js} +1 -1
  61. package/dist/index.html +2 -2
  62. package/dist/server.mjs +280 -148
  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
 
@@ -2614,7 +2614,11 @@ var PiRpc = class {
2614
2614
  const done = (code) => {
2615
2615
  if (this.exited) return;
2616
2616
  this.exited = { code };
2617
- for (const [, p] of this.pending) p.reject(new Error(`pi exited (code ${code})${this.stderr ? `: ${this.stderr.slice(-400)}` : ""}`));
2617
+ for (const [, p] of this.pending) {
2618
+ const noise = this.stderr.split("\n").map((l) => l.trim()).filter((l) => l && !/^warn(ing)?\b/i.test(l)).slice(-3).join(" \xB7 ").slice(-300);
2619
+ const how = code === 0 ? "pi exited before answering" : `pi exited (code ${code})`;
2620
+ p.reject(new Error(noise ? `${how}: ${noise}` : how));
2621
+ }
2618
2622
  this.pending.clear();
2619
2623
  for (const fn of this.exitListeners) {
2620
2624
  try {
@@ -3449,8 +3453,8 @@ import path3 from "node:path";
3449
3453
  import { spawn as spawn2 } from "node:child_process";
3450
3454
  var lives = /* @__PURE__ */ new Map();
3451
3455
  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")]) {
3456
+ function serveCommand(cwd, agentDir3 = process.env.PI_AGENT_DIR ?? path3.join(process.env.HOME ?? "", ".pi", "agent")) {
3457
+ for (const f of [path3.join(cwd, ".pi", "loops.json"), path3.join(agentDir3, "loops.json")]) {
3454
3458
  try {
3455
3459
  const j = JSON.parse(fs3.readFileSync(f, "utf8"));
3456
3460
  if (typeof j?.serve === "string" && j.serve.trim()) return j.serve.trim();
@@ -4085,11 +4089,101 @@ async function doctor(cwd) {
4085
4089
  return { checks, verdict: fatal ? "will-fail" : checks.every((c) => c.ok) ? "ready" : "incomplete" };
4086
4090
  }
4087
4091
 
4088
- // server/workspaces.ts
4092
+ // ../pi-loops/lib/credentials.ts
4089
4093
  import fs6 from "node:fs";
4094
+ import os3 from "node:os";
4090
4095
  import path6 from "node:path";
4096
+ import { spawn as spawn5 } from "node:child_process";
4097
+ var PROVIDERS = [
4098
+ { id: "particle", label: "Particle AI" },
4099
+ { id: "morph", label: "Morph" },
4100
+ { id: "openrouter", label: "OpenRouter" },
4101
+ { id: "anthropic", label: "Anthropic" },
4102
+ { id: "openai", label: "OpenAI" },
4103
+ { id: "deepseek", label: "DeepSeek" }
4104
+ ];
4105
+ var agentDir2 = () => process.env.PI_AGENT_DIR ?? path6.join(os3.homedir(), ".pi", "agent");
4106
+ var authFile = (dir = agentDir2()) => path6.join(dir, "auth.json");
4107
+ var envVar = (provider) => `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
4108
+ function credentialStatus(dir = agentDir2()) {
4109
+ let auth = {};
4110
+ try {
4111
+ const raw2 = fs6.readFileSync(authFile(dir), "utf8");
4112
+ auth = JSON.parse(raw2) ?? {};
4113
+ } catch {
4114
+ }
4115
+ return PROVIDERS.map((p) => {
4116
+ const env = envVar(p.id);
4117
+ const entry = auth?.[p.id];
4118
+ const inAuth = !!(entry && typeof entry === "object" && (entry.key || entry.type === "oauth"));
4119
+ return { id: p.id, label: p.label, env, configured: inAuth || !!process.env[env], source: inAuth ? "auth" : process.env[env] ? "env" : "none" };
4120
+ });
4121
+ }
4122
+ var chain = Promise.resolve();
4123
+ function queued(fn) {
4124
+ const next = chain.then(fn, fn);
4125
+ chain = next.catch(() => {
4126
+ });
4127
+ return next;
4128
+ }
4129
+ async function writeApiKey(provider, key, dir = agentDir2()) {
4130
+ const trimmed = key.trim();
4131
+ if (!trimmed) throw new Error("a key is required");
4132
+ if (!PROVIDERS.some((p) => p.id === provider)) throw new Error(`unknown provider "${provider}"`);
4133
+ return queued(async () => {
4134
+ const file = authFile(dir);
4135
+ let auth = {};
4136
+ try {
4137
+ const raw2 = fs6.readFileSync(file, "utf8");
4138
+ const parsed = raw2.trim() ? JSON.parse(raw2) : {};
4139
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) auth = parsed;
4140
+ } catch {
4141
+ }
4142
+ auth[provider] = { ...auth[provider] && typeof auth[provider] === "object" ? auth[provider] : {}, type: "api_key", key: trimmed };
4143
+ fs6.mkdirSync(dir, { recursive: true });
4144
+ const tmp = `${file}.${process.pid}.tmp`;
4145
+ fs6.writeFileSync(tmp, JSON.stringify(auth, null, 2) + "\n", { mode: 384 });
4146
+ fs6.renameSync(tmp, file);
4147
+ try {
4148
+ fs6.chmodSync(file, 384);
4149
+ } catch {
4150
+ }
4151
+ return file;
4152
+ });
4153
+ }
4154
+ async function verifyModel({ pi, model, cwd, timeoutMs = 6e4 }) {
4155
+ const args = ["--print", "--no-session", "--model", model, "Reply with the single word: ok"];
4156
+ return new Promise((resolve) => {
4157
+ let out = "";
4158
+ const child = spawn5(pi, args, { cwd, stdio: ["ignore", "pipe", "ignore"] });
4159
+ const timer = setTimeout(() => {
4160
+ try {
4161
+ child.kill("SIGKILL");
4162
+ } catch {
4163
+ }
4164
+ resolve({ ok: false, detail: `no answer in ${Math.round(timeoutMs / 1e3)}s` });
4165
+ }, timeoutMs);
4166
+ child.stdout.on("data", (b) => {
4167
+ out += String(b);
4168
+ });
4169
+ child.on("error", (e) => {
4170
+ clearTimeout(timer);
4171
+ resolve({ ok: false, detail: `could not run ${pi}: ${e.message}` });
4172
+ });
4173
+ child.on("close", (code) => {
4174
+ clearTimeout(timer);
4175
+ const said = out.split("\n").map((l) => l.trim()).filter(Boolean).slice(-2).join(" \xB7 ").slice(-160);
4176
+ if (code === 0 && /\bok\b/i.test(said)) return resolve({ ok: true, detail: `answered: ${said}` });
4177
+ resolve({ ok: false, detail: `exit ${code ?? "?"}${said ? ` \u2014 ${said}` : ""}`.trim() });
4178
+ });
4179
+ });
4180
+ }
4181
+
4182
+ // server/workspaces.ts
4183
+ import fs7 from "node:fs";
4184
+ import path7 from "node:path";
4091
4185
  import { execFileSync } from "node:child_process";
4092
- var slug = (p) => path6.basename(p).replace(/[^a-z0-9._-]+/gi, "-").toLowerCase() || "repo";
4186
+ var slug = (p) => path7.basename(p).replace(/[^a-z0-9._-]+/gi, "-").toLowerCase() || "repo";
4093
4187
  var Workspaces = class {
4094
4188
  /** Where workspaces.json and the per-repo session dirs live. */
4095
4189
  root;
@@ -4099,15 +4193,15 @@ var Workspaces = class {
4099
4193
  chatMap;
4100
4194
  constructor({ root, cwd }) {
4101
4195
  this.root = root;
4102
- this.file = path6.join(root, "workspaces.json");
4103
- fs6.mkdirSync(root, { recursive: true });
4196
+ this.file = path7.join(root, "workspaces.json");
4197
+ fs7.mkdirSync(root, { recursive: true });
4104
4198
  let data = { workspaces: [], chats: {} };
4105
- if (fs6.existsSync(this.file)) {
4199
+ if (fs7.existsSync(this.file)) {
4106
4200
  try {
4107
- data = JSON.parse(fs6.readFileSync(this.file, "utf8"));
4201
+ data = JSON.parse(fs7.readFileSync(this.file, "utf8"));
4108
4202
  } catch {
4109
4203
  try {
4110
- fs6.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
4204
+ fs7.renameSync(this.file, `${this.file}.corrupt-${Date.now()}`);
4111
4205
  } catch {
4112
4206
  }
4113
4207
  console.error(`[workspaces] could not read ${this.file}; kept a copy and rebuilding from sessions`);
@@ -4124,8 +4218,8 @@ var Workspaces = class {
4124
4218
  this.data.chats = Object.fromEntries(this.chatMap);
4125
4219
  try {
4126
4220
  const tmp = `${this.file}.tmp`;
4127
- fs6.writeFileSync(tmp, JSON.stringify(this.data, null, 2) + "\n");
4128
- fs6.renameSync(tmp, this.file);
4221
+ fs7.writeFileSync(tmp, JSON.stringify(this.data, null, 2) + "\n");
4222
+ fs7.renameSync(tmp, this.file);
4129
4223
  } catch {
4130
4224
  }
4131
4225
  }
@@ -4140,18 +4234,18 @@ var Workspaces = class {
4140
4234
  }
4141
4235
  /** Add (or re-find) a repo. `persist: false` is used for the startup cwd. */
4142
4236
  add(dir, { persist = true } = {}) {
4143
- const abs = path6.resolve(dir);
4237
+ const abs = path7.resolve(dir);
4144
4238
  const existing = [...this.byId.values()].find((w) => w.path === abs);
4145
4239
  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);
4240
+ if (!fs7.existsSync(abs) || !fs7.statSync(abs).isDirectory()) throw new Error(`${abs} is not a directory`);
4241
+ const st = fs7.statSync(abs);
4148
4242
  if (!st.isDirectory()) throw new Error(`${abs} is not a directory`);
4149
4243
  let id = slug(abs);
4150
4244
  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 });
4245
+ const legacy = path7.join(abs, ".pi", "loops-ui", "sessions");
4246
+ const sessionDir = fs7.existsSync(legacy) ? legacy : path7.join(this.root, "workspaces", id, "sessions");
4247
+ const ws = { id, path: abs, sessionDir, title: path7.basename(abs) || abs };
4248
+ fs7.mkdirSync(ws.sessionDir, { recursive: true });
4155
4249
  this.byId.set(id, ws);
4156
4250
  if (persist) {
4157
4251
  this.data.workspaces = [...this.data.workspaces.filter((w) => w.path !== abs), { id, path: abs }];
@@ -4175,25 +4269,25 @@ var Workspaces = class {
4175
4269
  * workspace. This runs on startup, so a lost or stale `workspaces.json` is a cache miss, not lost history.
4176
4270
  */
4177
4271
  recoverSessions() {
4178
- const root = path6.join(this.root, "workspaces");
4272
+ const root = path7.join(this.root, "workspaces");
4179
4273
  let ids = [];
4180
4274
  try {
4181
- ids = fs6.readdirSync(root);
4275
+ ids = fs7.readdirSync(root);
4182
4276
  } catch {
4183
4277
  return;
4184
4278
  }
4185
4279
  for (const id of ids) {
4186
- const dir = path6.join(root, id, "sessions");
4280
+ const dir = path7.join(root, id, "sessions");
4187
4281
  let files = [];
4188
4282
  try {
4189
- files = fs6.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
4283
+ files = fs7.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
4190
4284
  } catch {
4191
4285
  continue;
4192
4286
  }
4193
4287
  let cwd = null;
4194
4288
  for (const f of files) {
4195
4289
  try {
4196
- const head = JSON.parse(fs6.readFileSync(path6.join(dir, f), "utf8").split("\n")[0]);
4290
+ const head = JSON.parse(fs7.readFileSync(path7.join(dir, f), "utf8").split("\n")[0]);
4197
4291
  if (typeof head?.cwd === "string" && head.cwd) {
4198
4292
  cwd = head.cwd;
4199
4293
  break;
@@ -4248,8 +4342,8 @@ var Workspaces = class {
4248
4342
  };
4249
4343
 
4250
4344
  // server/index.ts
4251
- var HERE = path7.dirname(fileURLToPath(import.meta.url));
4252
- var APP_ROOT = path7.resolve(HERE, "..");
4345
+ var HERE = path8.dirname(fileURLToPath(import.meta.url));
4346
+ var APP_ROOT = path8.resolve(HERE, "..");
4253
4347
  var arg = (name, fallback) => {
4254
4348
  const i = process.argv.indexOf(`--${name}`);
4255
4349
  return i >= 0 ? process.argv[i + 1] : process.env[`LOOPS_UI_${name.toUpperCase().replace(/-/g, "_")}`] ?? fallback;
@@ -4257,16 +4351,16 @@ var arg = (name, fallback) => {
4257
4351
  var PORT = Number(arg("port", "8001"));
4258
4352
  var HOST = arg("host", "127.0.0.1");
4259
4353
  var LOOPBACK = ["127.0.0.1", "localhost", "::1"].includes(HOST);
4260
- var CWD = path7.resolve(arg("cwd", process.cwd()));
4354
+ var CWD = path8.resolve(arg("cwd", process.cwd()));
4261
4355
  var DEV = process.argv.includes("--dev") || process.env.NODE_ENV === "development";
4262
4356
  var VITE = arg("vite", "http://localhost:5173");
4263
4357
  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")));
4358
+ var DATA = path8.resolve(arg("data", process.env.LOOPS_UI_DATA ?? path8.join(process.env.HOME ?? process.cwd(), ".pi", "loops-ui")));
4265
4359
  var MODEL = arg("model", void 0) || null;
4266
- var EXTENSIONS = (arg("extensions", path7.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
4360
+ var EXTENSIONS = (arg("extensions", path8.resolve(APP_ROOT, "../pi-loops/extensions/loop.ts")) ?? "").split(",").filter(Boolean);
4267
4361
  function prerequisites() {
4268
4362
  const out = [];
4269
- const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs7.existsSync(PI) : (() => {
4363
+ const onPath = PI.includes("/") || /^[A-Za-z]:\\/.test(PI) ? fs8.existsSync(PI) : (() => {
4270
4364
  try {
4271
4365
  execFileSync2(process.platform === "win32" ? "where" : "which", [PI], { stdio: ["ignore", "pipe", "ignore"] });
4272
4366
  return true;
@@ -4276,18 +4370,18 @@ function prerequisites() {
4276
4370
  })();
4277
4371
  if (!onPath) out.push({ name: "pi", detail: `\`${PI}\` is not on PATH`, fix: "install Pi, or pass --pi <path to the pi binary>" });
4278
4372
  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>" });
4373
+ 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
4374
  }
4281
4375
  return out;
4282
4376
  }
4283
4377
  var PROBLEMS = prerequisites();
4284
- fs7.mkdirSync(DATA, { recursive: true });
4378
+ fs8.mkdirSync(DATA, { recursive: true });
4285
4379
  var TOKEN = (() => {
4286
4380
  const explicit = arg("token", void 0);
4287
- const file = path7.join(DATA, "token");
4381
+ const file = path8.join(DATA, "token");
4288
4382
  const stored = (() => {
4289
4383
  try {
4290
- return fs7.readFileSync(file, "utf8").trim() || null;
4384
+ return fs8.readFileSync(file, "utf8").trim() || null;
4291
4385
  } catch {
4292
4386
  return null;
4293
4387
  }
@@ -4295,7 +4389,7 @@ var TOKEN = (() => {
4295
4389
  if (explicit) {
4296
4390
  if (!LOOPBACK) {
4297
4391
  try {
4298
- fs7.writeFileSync(file, explicit + "\n", { mode: 384 });
4392
+ fs8.writeFileSync(file, explicit + "\n", { mode: 384 });
4299
4393
  } catch {
4300
4394
  }
4301
4395
  }
@@ -4304,7 +4398,7 @@ var TOKEN = (() => {
4304
4398
  if (stored) return stored;
4305
4399
  if (LOOPBACK) return null;
4306
4400
  const made = randomBytes(24).toString("base64url");
4307
- fs7.writeFileSync(file, made + "\n", { mode: 384 });
4401
+ fs8.writeFileSync(file, made + "\n", { mode: 384 });
4308
4402
  return made;
4309
4403
  })();
4310
4404
  if (!LOOPBACK && !TOKEN) {
@@ -4324,7 +4418,7 @@ var manager = new ChatManager({
4324
4418
  });
4325
4419
  var wsOf = (chat) => chat ? workspaces.ofChat(chat) : workspaces.get();
4326
4420
  var wsOfRun = (run3) => {
4327
- const byIndex = workspaces.list().find((w) => fs7.existsSync(path7.join(w.path, STATE_DIR, `${run3}.json`)));
4421
+ const byIndex = workspaces.list().find((w) => fs8.existsSync(path8.join(w.path, STATE_DIR, `${run3}.json`)));
4328
4422
  if (byIndex) return byIndex;
4329
4423
  const chat = manager.findRun(run3);
4330
4424
  return chat ? workspaces.ofChat(chat.id) : workspaces.get();
@@ -4352,7 +4446,7 @@ var watchOf = (wsId) => {
4352
4446
  if (!w) {
4353
4447
  const read = () => {
4354
4448
  try {
4355
- return JSON.parse(fs7.readFileSync(path7.join(ws.path, ".pi", "loops.json"), "utf8"));
4449
+ return JSON.parse(fs8.readFileSync(path8.join(ws.path, ".pi", "loops.json"), "utf8"));
4356
4450
  } catch {
4357
4451
  return {};
4358
4452
  }
@@ -4360,7 +4454,7 @@ var watchOf = (wsId) => {
4360
4454
  w = { cwd: ws.path, accepted: read() };
4361
4455
  watches.set(ws.id, w);
4362
4456
  try {
4363
- fs7.watch(path7.join(ws.path, ".pi"), (_e, name) => {
4457
+ fs8.watch(path8.join(ws.path, ".pi"), (_e, name) => {
4364
4458
  if (name && name !== "loops.json") return;
4365
4459
  clearTimeout(watchTimers.get(ws.id));
4366
4460
  watchTimers.set(ws.id, setTimeout(() => checkConfig(ws.id), 120));
@@ -4382,10 +4476,10 @@ function notifyProposals() {
4382
4476
  }
4383
4477
  function checkConfig(wsId) {
4384
4478
  const w = watchOf(wsId);
4385
- const CONFIG = path7.join(w.cwd, ".pi", "loops.json");
4479
+ const CONFIG = path8.join(w.cwd, ".pi", "loops.json");
4386
4480
  const raw2 = (() => {
4387
4481
  try {
4388
- return fs7.readFileSync(CONFIG, "utf8");
4482
+ return fs8.readFileSync(CONFIG, "utf8");
4389
4483
  } catch {
4390
4484
  return "";
4391
4485
  }
@@ -4406,16 +4500,29 @@ function checkConfig(wsId) {
4406
4500
  notifyProposals();
4407
4501
  }
4408
4502
  var inside = (root, p) => {
4409
- const abs = path7.resolve(root, p);
4410
- return abs === root || abs.startsWith(root + path7.sep) ? abs : null;
4503
+ const abs = path8.resolve(root, p);
4504
+ return abs === root || abs.startsWith(root + path8.sep) ? abs : null;
4411
4505
  };
4412
4506
  var stateOf = (run3) => manager.findRun(run3)?.stateFor(run3) ?? readState(wsOfRun(run3).path, run3);
4413
4507
  var app = new Hono3();
4508
+ var OWN_HOSTS = /* @__PURE__ */ new Set([`127.0.0.1:${PORT}`, `localhost:${PORT}`, `[::1]:${PORT}`]);
4509
+ var OWN_ORIGINS = new Set([...OWN_HOSTS].map((h) => `http://${h}`));
4414
4510
  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("*");
4511
+ var originAllowed = (origin) => {
4512
+ if (ALLOWED_ORIGINS.includes("*")) return true;
4513
+ if (ALLOWED_ORIGINS.length) return ALLOWED_ORIGINS.includes(origin);
4514
+ return !LOOPBACK || OWN_ORIGINS.has(origin);
4515
+ };
4416
4516
  app.use("*", async (c, next) => {
4517
+ const host = c.req.header("host") ?? "";
4518
+ if (LOOPBACK && !OWN_HOSTS.has(host)) {
4519
+ return c.text(`this server answers as ${[...OWN_HOSTS].join(", ")}, not "${host}"`, 403);
4520
+ }
4417
4521
  const origin = c.req.header("origin");
4418
- if (origin && originAllowed(origin)) {
4522
+ if (origin && !originAllowed(origin)) {
4523
+ return c.json({ error: "origin not allowed", origin }, 403);
4524
+ }
4525
+ if (origin) {
4419
4526
  c.header("access-control-allow-origin", origin);
4420
4527
  c.header("access-control-allow-headers", "authorization, content-type, last-event-id");
4421
4528
  c.header("access-control-allow-methods", "GET, POST, OPTIONS");
@@ -4438,6 +4545,31 @@ app.use("/api/v1/*", async (c, next) => {
4438
4545
  });
4439
4546
  var api = new Hono3();
4440
4547
  api.get("/health", (c) => c.json({ ok: PROBLEMS.length === 0, problems: PROBLEMS, cwd: CWD, data: DATA, pi: PI, extensions: EXTENSIONS, workspaces: workspaces.list() }));
4548
+ api.get("/credentials", (c) => c.json({ dir: agentDir2(), providers: credentialStatus() }));
4549
+ api.post("/credentials", async (c) => {
4550
+ const body = await c.req.json().catch(() => ({}));
4551
+ const provider = String(body?.provider ?? "");
4552
+ const key = String(body?.key ?? "").trim();
4553
+ if (!PROVIDERS.some((p) => p.id === provider)) return c.json({ error: `unknown provider "${provider}" \u2014 one of ${PROVIDERS.map((p) => p.id).join(", ")}` }, 400);
4554
+ if (!key) return c.json({ error: "a key is required" }, 400);
4555
+ try {
4556
+ const file = await writeApiKey(provider, key);
4557
+ let model = typeof body?.model === "string" && body.model.trim() ? body.model.trim() : "";
4558
+ if (!model) {
4559
+ for (const chat of manager.openChats()) {
4560
+ const found = (await chat.availableModels()).find((m) => m.provider === provider);
4561
+ if (found) {
4562
+ model = found.id;
4563
+ break;
4564
+ }
4565
+ }
4566
+ }
4567
+ const verified = model ? { model, ...await verifyModel({ pi: PI, model, cwd: CWD }) } : null;
4568
+ return c.json({ ok: true, provider, file, configured: credentialStatus().find((s) => s.id === provider)?.configured ?? true, verified });
4569
+ } catch (e) {
4570
+ return c.json({ error: String(e.message ?? e).slice(0, 200) }, 400);
4571
+ }
4572
+ });
4441
4573
  api.get("/chats", (c) => c.json(chatsOf(c.req.query("ws"))));
4442
4574
  api.get("/chats/:id/events", async (c) => {
4443
4575
  const id = c.req.param("id");
@@ -4480,9 +4612,9 @@ api.post("/chat/delete", async (c) => {
4480
4612
  const file = chat?.sessionFile ?? null;
4481
4613
  await manager.close(String(id));
4482
4614
  workspaces.unbindChat(String(id));
4483
- if (file && fs7.existsSync(file)) {
4615
+ if (file && fs8.existsSync(file)) {
4484
4616
  try {
4485
- fs7.rmSync(file);
4617
+ fs8.rmSync(file);
4486
4618
  } catch {
4487
4619
  }
4488
4620
  }
@@ -4492,9 +4624,9 @@ api.get("/workspaces", (c) => c.json(workspaces.list()));
4492
4624
  api.post("/workspaces", async (c) => {
4493
4625
  const body = await c.req.json().catch(() => ({}));
4494
4626
  try {
4495
- const dir = path7.resolve(String(body.path ?? "").replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
4627
+ const dir = path8.resolve(String(body.path ?? "").replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
4496
4628
  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`);
4629
+ if (!fs8.existsSync(dir) || !fs8.statSync(dir).isDirectory()) throw new Error(`${dir} is not a directory`);
4498
4630
  const ws = workspaces.add(dir);
4499
4631
  watchOf(ws.id);
4500
4632
  return c.json({ ok: true, workspace: ws, loopable: Workspaces.loopable(ws.path) });
@@ -4578,7 +4710,7 @@ api.get("/session", async (c) => {
4578
4710
  if (!run3 || !name) return c.json([]);
4579
4711
  const state = stateOf(run3);
4580
4712
  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([]);
4713
+ if (!file || !fs8.existsSync(file)) return c.json([]);
4582
4714
  const chat = await manager.open(c.req.query("chat") ?? manager.list()[0]?.id ?? "");
4583
4715
  return c.json(chat.sessionItems(file));
4584
4716
  });
@@ -4597,21 +4729,21 @@ api.get("/shot", (c) => {
4597
4729
  const repo = wsOfRun(run3).path;
4598
4730
  if (file.startsWith(STATE_DIR)) {
4599
4731
  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" } });
4732
+ if (!abs2 || !fs8.existsSync(abs2)) return c.notFound();
4733
+ return new Response(fs8.readFileSync(abs2), { headers: { "content-type": abs2.endsWith(".jpg") || abs2.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4602
4734
  }
4603
4735
  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
4736
  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" } });
4737
+ const abs = inside(repo, path8.join(dir, file));
4738
+ if (!abs || !fs8.existsSync(abs)) return c.notFound();
4739
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": abs.endsWith(".jpg") || abs.endsWith(".jpeg") ? "image/jpeg" : "image/png", "cache-control": "no-cache" } });
4608
4740
  });
4609
4741
  api.get("/sheet", (c) => {
4610
4742
  const file = c.req.query("f") ?? "";
4611
- const ws = workspaces.list().find((w) => fs7.existsSync(path7.join(w.path, file))) ?? workspaces.get();
4743
+ const ws = workspaces.list().find((w) => fs8.existsSync(path8.join(w.path, file))) ?? workspaces.get();
4612
4744
  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" } });
4745
+ if (!abs || !abs.includes(STATE_DIR) || !fs8.existsSync(abs)) return c.notFound();
4746
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": "image/png", "cache-control": "no-cache" } });
4615
4747
  });
4616
4748
  api.get("/diff", async (c) => {
4617
4749
  const run3 = c.req.query("run") ?? "";
@@ -4675,7 +4807,7 @@ api.post("/config", async (c) => {
4675
4807
  writeSetting(ws.path, String(key), value);
4676
4808
  const w = watchOf(ws.id);
4677
4809
  try {
4678
- w.accepted = JSON.parse(fs7.readFileSync(path7.join(ws.path, ".pi", "loops.json"), "utf8"));
4810
+ w.accepted = JSON.parse(fs8.readFileSync(path8.join(ws.path, ".pi", "loops.json"), "utf8"));
4679
4811
  } catch {
4680
4812
  }
4681
4813
  return c.json({ ok: true });
@@ -4722,7 +4854,7 @@ api.get("/meta", (c) => c.json({
4722
4854
  }));
4723
4855
  app.route("/api", api);
4724
4856
  app.route("/api/v1", api);
4725
- var DIST = path7.join(APP_ROOT, "dist");
4857
+ var DIST = path8.join(APP_ROOT, "dist");
4726
4858
  app.get("*", async (c) => {
4727
4859
  const url = c.req.path;
4728
4860
  if (DEV) {
@@ -4731,14 +4863,14 @@ app.get("*", async (c) => {
4731
4863
  return new Response(res.body, { status: res.status, headers: { "content-type": res.headers.get("content-type") ?? "text/html" } });
4732
4864
  }
4733
4865
  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);
4866
+ if (abs && fs8.existsSync(abs) && fs8.statSync(abs).isFile()) {
4867
+ const ext = path8.extname(abs);
4736
4868
  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 } });
4869
+ return new Response(fs8.readFileSync(abs), { headers: { "content-type": type } });
4738
4870
  }
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" } });
4871
+ const index = path8.join(DIST, "index.html");
4872
+ if (!fs8.existsSync(index)) return c.text("run `npm run build` first", 500);
4873
+ return new Response(fs8.readFileSync(index), { headers: { "content-type": "text/html" } });
4742
4874
  });
4743
4875
  var server = http.createServer(getRequestListener(app.fetch));
4744
4876
  setInterval(() => {
@@ -4748,7 +4880,7 @@ function openBrowser(url) {
4748
4880
  if (process.argv.includes("--no-open") || process.env.LOOPS_UI_OPEN === "0") return;
4749
4881
  const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
4750
4882
  try {
4751
- const p = spawn5(cmd, args, { stdio: "ignore", detached: true });
4883
+ const p = spawn6(cmd, args, { stdio: "ignore", detached: true });
4752
4884
  p.on("error", () => {
4753
4885
  });
4754
4886
  p.unref();