lopebase 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -5
  3. package/dist/lopebase.js +1447 -291
  4. package/package.json +4 -2
package/dist/lopebase.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  var __create = Object.create;
3
4
  var { getPrototypeOf: __getProtoOf, defineProperty: __defProp, getOwnPropertyNames: __getOwnPropNames } = Object;
4
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
@@ -13,7 +14,7 @@ var __toESMCache_node, __toESMCache_esm, __toESM = (mod, isNodeMode, target) =>
13
14
  return cached;
14
15
  }
15
16
  target = mod != null ? __create(__getProtoOf(mod)) : {};
16
- let to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target;
17
+ let to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target;
17
18
  if (mod && typeof mod === "object" || typeof mod === "function") {
18
19
  for (let key of __getOwnPropNames(mod))
19
20
  if (!__hasOwnProp.call(to, key))
@@ -27,6 +28,7 @@ var __toESMCache_node, __toESMCache_esm, __toESM = (mod, isNodeMode, target) =>
27
28
  return to;
28
29
  };
29
30
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
31
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
32
 
31
33
  // ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
32
34
  var require_src = __commonJS(function(exports, module) {
@@ -143,8 +145,364 @@ var require_picocolors = __commonJS(function(exports, module) {
143
145
  module.exports.createColors = createColors;
144
146
  });
145
147
 
148
+ // ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/windows.js
149
+ var require_windows = __commonJS(function(exports, module) {
150
+ module.exports = isexe;
151
+ isexe.sync = sync;
152
+ var fs = __require("fs");
153
+ function checkPathExt(path, options) {
154
+ var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
155
+ if (!pathext)
156
+ return !0;
157
+ if (pathext = pathext.split(";"), pathext.indexOf("") !== -1)
158
+ return !0;
159
+ for (var i = 0;i < pathext.length; i++) {
160
+ var p2 = pathext[i].toLowerCase();
161
+ if (p2 && path.substr(-p2.length).toLowerCase() === p2)
162
+ return !0;
163
+ }
164
+ return !1;
165
+ }
166
+ function checkStat(stat, path, options) {
167
+ if (!stat.isSymbolicLink() && !stat.isFile())
168
+ return !1;
169
+ return checkPathExt(path, options);
170
+ }
171
+ function isexe(path, options, cb) {
172
+ fs.stat(path, function(er, stat) {
173
+ cb(er, er ? !1 : checkStat(stat, path, options));
174
+ });
175
+ }
176
+ function sync(path, options) {
177
+ return checkStat(fs.statSync(path), path, options);
178
+ }
179
+ });
180
+
181
+ // ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/mode.js
182
+ var require_mode = __commonJS(function(exports, module) {
183
+ module.exports = isexe;
184
+ isexe.sync = sync;
185
+ var fs = __require("fs");
186
+ function isexe(path, options, cb) {
187
+ fs.stat(path, function(er, stat) {
188
+ cb(er, er ? !1 : checkStat(stat, options));
189
+ });
190
+ }
191
+ function sync(path, options) {
192
+ return checkStat(fs.statSync(path), options);
193
+ }
194
+ function checkStat(stat, options) {
195
+ return stat.isFile() && checkMode(stat, options);
196
+ }
197
+ function checkMode(stat, options) {
198
+ var { mode: mod, uid, gid } = stat, myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid(), myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid(), u2 = parseInt("100", 8), g2 = parseInt("010", 8), o2 = parseInt("001", 8), ug = u2 | g2, ret = mod & o2 || mod & g2 && gid === myGid || mod & u2 && uid === myUid || mod & ug && myUid === 0;
199
+ return ret;
200
+ }
201
+ });
202
+
203
+ // ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/index.js
204
+ var require_isexe = __commonJS(function(exports, module) {
205
+ var fs = __require("fs"), core;
206
+ if (process.platform === "win32" || global.TESTING_WINDOWS)
207
+ core = require_windows();
208
+ else
209
+ core = require_mode();
210
+ module.exports = isexe;
211
+ isexe.sync = sync;
212
+ function isexe(path, options, cb) {
213
+ if (typeof options === "function")
214
+ cb = options, options = {};
215
+ if (!cb) {
216
+ if (typeof Promise !== "function")
217
+ throw TypeError("callback not provided");
218
+ return new Promise(function(resolve, reject) {
219
+ isexe(path, options || {}, function(er, is) {
220
+ if (er)
221
+ reject(er);
222
+ else
223
+ resolve(is);
224
+ });
225
+ });
226
+ }
227
+ core(path, options || {}, function(er, is) {
228
+ if (er) {
229
+ if (er.code === "EACCES" || options && options.ignoreErrors)
230
+ er = null, is = !1;
231
+ }
232
+ cb(er, is);
233
+ });
234
+ }
235
+ function sync(path, options) {
236
+ try {
237
+ return core.sync(path, options || {});
238
+ } catch (er) {
239
+ if (options && options.ignoreErrors || er.code === "EACCES")
240
+ return !1;
241
+ else
242
+ throw er;
243
+ }
244
+ }
245
+ });
246
+
247
+ // ../../node_modules/.bun/which@2.0.2/node_modules/which/which.js
248
+ var require_which = __commonJS(function(exports, module) {
249
+ var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys", path = __require("path"), COLON = isWindows ? ";" : ":", isexe = require_isexe(), getNotFoundError = (cmd) => Object.assign(Error(`not found: ${cmd}`), { code: "ENOENT" }), getPathInfo = (cmd, opt) => {
250
+ let colon = opt.colon || COLON, pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
251
+ ...isWindows ? [process.cwd()] : [],
252
+ ...(opt.path || process.env.PATH || "").split(colon)
253
+ ], pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "", pathExt = isWindows ? pathExtExe.split(colon) : [""];
254
+ if (isWindows) {
255
+ if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
256
+ pathExt.unshift("");
257
+ }
258
+ return {
259
+ pathEnv,
260
+ pathExt,
261
+ pathExtExe
262
+ };
263
+ }, which = (cmd, opt, cb) => {
264
+ if (typeof opt === "function")
265
+ cb = opt, opt = {};
266
+ if (!opt)
267
+ opt = {};
268
+ let { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt), found = [], step = (i) => new Promise((resolve, reject) => {
269
+ if (i === pathEnv.length)
270
+ return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
271
+ let ppRaw = pathEnv[i], pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw, pCmd = path.join(pathPart, cmd), p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
272
+ resolve(subStep(p2, i, 0));
273
+ }), subStep = (p2, i, ii) => new Promise((resolve, reject) => {
274
+ if (ii === pathExt.length)
275
+ return resolve(step(i + 1));
276
+ let ext = pathExt[ii];
277
+ isexe(p2 + ext, { pathExt: pathExtExe }, (er, is) => {
278
+ if (!er && is)
279
+ if (opt.all)
280
+ found.push(p2 + ext);
281
+ else
282
+ return resolve(p2 + ext);
283
+ return resolve(subStep(p2, i, ii + 1));
284
+ });
285
+ });
286
+ return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
287
+ }, whichSync = (cmd, opt) => {
288
+ opt = opt || {};
289
+ let { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt), found = [];
290
+ for (let i = 0;i < pathEnv.length; i++) {
291
+ let ppRaw = pathEnv[i], pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw, pCmd = path.join(pathPart, cmd), p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
292
+ for (let j2 = 0;j2 < pathExt.length; j2++) {
293
+ let cur = p2 + pathExt[j2];
294
+ try {
295
+ if (isexe.sync(cur, { pathExt: pathExtExe }))
296
+ if (opt.all)
297
+ found.push(cur);
298
+ else
299
+ return cur;
300
+ } catch (ex) {}
301
+ }
302
+ }
303
+ if (opt.all && found.length)
304
+ return found;
305
+ if (opt.nothrow)
306
+ return null;
307
+ throw getNotFoundError(cmd);
308
+ };
309
+ module.exports = which;
310
+ which.sync = whichSync;
311
+ });
312
+
313
+ // ../../node_modules/.bun/path-key@3.1.1/node_modules/path-key/index.js
314
+ var require_path_key = __commonJS(function(exports, module) {
315
+ var pathKey = (options = {}) => {
316
+ let environment = options.env || process.env;
317
+ if ((options.platform || process.platform) !== "win32")
318
+ return "PATH";
319
+ return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
320
+ };
321
+ module.exports = pathKey;
322
+ module.exports.default = pathKey;
323
+ });
324
+
325
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
326
+ var require_resolveCommand = __commonJS(function(exports, module) {
327
+ var path = __require("path"), which = require_which(), getPathKey = require_path_key();
328
+ function resolveCommandAttempt(parsed, withoutPathExt) {
329
+ let env = parsed.options.env || process.env, cwd = process.cwd(), hasCustomCwd = parsed.options.cwd != null, shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
330
+ if (shouldSwitchCwd)
331
+ try {
332
+ process.chdir(parsed.options.cwd);
333
+ } catch (err) {}
334
+ let resolved;
335
+ try {
336
+ resolved = which.sync(parsed.command, {
337
+ path: env[getPathKey({ env })],
338
+ pathExt: withoutPathExt ? path.delimiter : void 0
339
+ });
340
+ } catch (e2) {} finally {
341
+ if (shouldSwitchCwd)
342
+ process.chdir(cwd);
343
+ }
344
+ if (resolved)
345
+ resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
346
+ return resolved;
347
+ }
348
+ function resolveCommand(parsed) {
349
+ return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, !0);
350
+ }
351
+ module.exports = resolveCommand;
352
+ });
353
+
354
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
355
+ var require_escape = __commonJS(function(exports, module) {
356
+ var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
357
+ function escapeCommand(arg) {
358
+ return arg = arg.replace(metaCharsRegExp, "^$1"), arg;
359
+ }
360
+ function escapeArgument(arg, doubleEscapeMetaChars) {
361
+ if (arg = `${arg}`, arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\""), arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1"), arg = `"${arg}"`, arg = arg.replace(metaCharsRegExp, "^$1"), doubleEscapeMetaChars)
362
+ arg = arg.replace(metaCharsRegExp, "^$1");
363
+ return arg;
364
+ }
365
+ exports.command = escapeCommand;
366
+ exports.argument = escapeArgument;
367
+ });
368
+
369
+ // ../../node_modules/.bun/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
370
+ var require_shebang_regex = __commonJS(function(exports, module) {
371
+ module.exports = /^#!(.*)/;
372
+ });
373
+
374
+ // ../../node_modules/.bun/shebang-command@2.0.0/node_modules/shebang-command/index.js
375
+ var require_shebang_command = __commonJS(function(exports, module) {
376
+ var shebangRegex = require_shebang_regex();
377
+ module.exports = (string = "") => {
378
+ let match = string.match(shebangRegex);
379
+ if (!match)
380
+ return null;
381
+ let [path, argument] = match[0].replace(/#! ?/, "").split(" "), binary = path.split("/").pop();
382
+ if (binary === "env")
383
+ return argument;
384
+ return argument ? `${binary} ${argument}` : binary;
385
+ };
386
+ });
387
+
388
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
389
+ var require_readShebang = __commonJS(function(exports, module) {
390
+ var fs = __require("fs"), shebangCommand = require_shebang_command();
391
+ function readShebang(command) {
392
+ let buffer = Buffer.alloc(150), fd;
393
+ try {
394
+ fd = fs.openSync(command, "r"), fs.readSync(fd, buffer, 0, 150, 0), fs.closeSync(fd);
395
+ } catch (e2) {}
396
+ return shebangCommand(buffer.toString());
397
+ }
398
+ module.exports = readShebang;
399
+ });
400
+
401
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
402
+ var require_parse = __commonJS(function(exports, module) {
403
+ var path = __require("path"), resolveCommand = require_resolveCommand(), escape = require_escape(), readShebang = require_readShebang(), isWin = process.platform === "win32", isExecutableRegExp = /\.(?:com|exe)$/i, isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
404
+ function detectShebang(parsed) {
405
+ parsed.file = resolveCommand(parsed);
406
+ let shebang = parsed.file && readShebang(parsed.file);
407
+ if (shebang)
408
+ return parsed.args.unshift(parsed.file), parsed.command = shebang, resolveCommand(parsed);
409
+ return parsed.file;
410
+ }
411
+ function parseNonShell(parsed) {
412
+ if (!isWin)
413
+ return parsed;
414
+ let commandFile = detectShebang(parsed), needsShell = !isExecutableRegExp.test(commandFile);
415
+ if (parsed.options.forceShell || needsShell) {
416
+ let needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
417
+ parsed.command = path.normalize(parsed.command), parsed.command = escape.command(parsed.command), parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
418
+ let shellCommand = [parsed.command].concat(parsed.args).join(" ");
419
+ parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`], parsed.command = process.env.comspec || "cmd.exe", parsed.options.windowsVerbatimArguments = !0;
420
+ }
421
+ return parsed;
422
+ }
423
+ function parse(command, args, options) {
424
+ if (args && !Array.isArray(args))
425
+ options = args, args = null;
426
+ args = args ? args.slice(0) : [], options = Object.assign({}, options);
427
+ let parsed = {
428
+ command,
429
+ args,
430
+ options,
431
+ file: void 0,
432
+ original: {
433
+ command,
434
+ args
435
+ }
436
+ };
437
+ return options.shell ? parsed : parseNonShell(parsed);
438
+ }
439
+ module.exports = parse;
440
+ });
441
+
442
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
443
+ var require_enoent = __commonJS(function(exports, module) {
444
+ var isWin = process.platform === "win32";
445
+ function notFoundError(original, syscall) {
446
+ return Object.assign(Error(`${syscall} ${original.command} ENOENT`), {
447
+ code: "ENOENT",
448
+ errno: "ENOENT",
449
+ syscall: `${syscall} ${original.command}`,
450
+ path: original.command,
451
+ spawnargs: original.args
452
+ });
453
+ }
454
+ function hookChildProcess(cp, parsed) {
455
+ if (!isWin)
456
+ return;
457
+ let originalEmit = cp.emit;
458
+ cp.emit = function(name, arg1) {
459
+ if (name === "exit") {
460
+ let err = verifyENOENT(arg1, parsed);
461
+ if (err)
462
+ return originalEmit.call(cp, "error", err);
463
+ }
464
+ return originalEmit.apply(cp, arguments);
465
+ };
466
+ }
467
+ function verifyENOENT(status, parsed) {
468
+ if (isWin && status === 1 && !parsed.file)
469
+ return notFoundError(parsed.original, "spawn");
470
+ return null;
471
+ }
472
+ function verifyENOENTSync(status, parsed) {
473
+ if (isWin && status === 1 && !parsed.file)
474
+ return notFoundError(parsed.original, "spawnSync");
475
+ return null;
476
+ }
477
+ module.exports = {
478
+ hookChildProcess,
479
+ verifyENOENT,
480
+ verifyENOENTSync,
481
+ notFoundError
482
+ };
483
+ });
484
+
485
+ // ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
486
+ var require_cross_spawn = __commonJS(function(exports, module) {
487
+ var cp = __require("child_process"), parse = require_parse(), enoent = require_enoent();
488
+ function spawn(command, args, options) {
489
+ let parsed = parse(command, args, options), spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
490
+ return enoent.hookChildProcess(spawned, parsed), spawned;
491
+ }
492
+ function spawnSync(command, args, options) {
493
+ let parsed = parse(command, args, options), result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
494
+ return result.error = result.error || enoent.verifyENOENTSync(result.status, parsed), result;
495
+ }
496
+ module.exports = spawn;
497
+ module.exports.spawn = spawn;
498
+ module.exports.sync = spawnSync;
499
+ module.exports._parse = parse;
500
+ module.exports._enoent = enoent;
501
+ });
502
+
146
503
  // bin/lopebase.ts
147
- import { spawn, spawnSync as spawnSync2 } from "node:child_process";
504
+ import { spawn as spawn2 } from "node:child_process";
505
+ import { readFileSync as readFileSync3 } from "node:fs";
148
506
 
149
507
  // ../../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
150
508
  import { stripVTControlCharacters as S2 } from "node:util";
@@ -592,17 +950,17 @@ var V2 = ce(), u = (t, n) => V2 ? t : n, le = u("◆", "*"), L2 = u("■", "x"),
592
950
  return import_picocolors2.default.green(C);
593
951
  }
594
952
  }, G2 = (t) => {
595
- let { cursor: n, options: r, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5)), l = 0;
596
- n >= l + a - 3 ? l = Math.max(Math.min(n - a + 3, r.length - a), 0) : n < l + 2 && (l = Math.max(n - 2, 0));
597
- let $ = a < r.length && l > 0, g = a < r.length && l + a < r.length;
598
- return r.slice(l, l + a).map((p, v, f) => {
599
- let j = v === 0 && $, E = v === f.length - 1 && g;
600
- return j || E ? import_picocolors2.default.dim("...") : i(p, v + l === n);
953
+ let { cursor: n, options: r2, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5)), l2 = 0;
954
+ n >= l2 + a - 3 ? l2 = Math.max(Math.min(n - a + 3, r2.length - a), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
955
+ let $2 = a < r2.length && l2 > 0, g2 = a < r2.length && l2 + a < r2.length;
956
+ return r2.slice(l2, l2 + a).map((p2, v2, f) => {
957
+ let j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g2;
958
+ return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v2 + l2 === n);
601
959
  });
602
960
  }, he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
603
961
  let n = `${import_picocolors2.default.gray(o)}
604
962
  ${b2(this.state)} ${t.message}
605
- `, r = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), i = this.value ? this.valueWithCursor : r;
963
+ `, r2 = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), i = this.value ? this.valueWithCursor : r2;
606
964
  switch (this.state) {
607
965
  case "error":
608
966
  return `${n.trim()}
@@ -622,7 +980,7 @@ ${import_picocolors2.default.cyan(d2)}
622
980
  } }).prompt(), ge = (t) => new MD({ validate: t.validate, mask: t.mask ?? $e, render() {
623
981
  let n = `${import_picocolors2.default.gray(o)}
624
982
  ${b2(this.state)} ${t.message}
625
- `, r = this.valueWithCursor, i = this.masked;
983
+ `, r2 = this.valueWithCursor, i = this.masked;
626
984
  switch (this.state) {
627
985
  case "error":
628
986
  return `${n.trim()}
@@ -635,19 +993,19 @@ ${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(th
635
993
  return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i ?? ""))}${i ? `
636
994
  ${import_picocolors2.default.gray(o)}` : ""}`;
637
995
  default:
638
- return `${n}${import_picocolors2.default.cyan(o)} ${r}
996
+ return `${n}${import_picocolors2.default.cyan(o)} ${r2}
639
997
  ${import_picocolors2.default.cyan(d2)}
640
998
  `;
641
999
  }
642
1000
  } }).prompt();
643
1001
  var ve = (t) => {
644
- let n = (r, i) => {
645
- let s = r.label ?? String(r.value);
1002
+ let n = (r2, i) => {
1003
+ let s = r2.label ?? String(r2.value);
646
1004
  switch (i) {
647
1005
  case "selected":
648
1006
  return `${import_picocolors2.default.dim(s)}`;
649
1007
  case "active":
650
- return `${import_picocolors2.default.green(k2)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}`;
1008
+ return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
651
1009
  case "cancelled":
652
1010
  return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
653
1011
  default:
@@ -655,17 +1013,17 @@ var ve = (t) => {
655
1013
  }
656
1014
  };
657
1015
  return new LD({ options: t.options, initialValue: t.initialValue, render() {
658
- let r = `${import_picocolors2.default.gray(o)}
1016
+ let r2 = `${import_picocolors2.default.gray(o)}
659
1017
  ${b2(this.state)} ${t.message}
660
1018
  `;
661
1019
  switch (this.state) {
662
1020
  case "submit":
663
- return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
1021
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
664
1022
  case "cancel":
665
- return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
1023
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
666
1024
  ${import_picocolors2.default.gray(o)}`;
667
1025
  default:
668
- return `${r}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
1026
+ return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
669
1027
  ${import_picocolors2.default.cyan(o)} `)}
670
1028
  ${import_picocolors2.default.cyan(d2)}
671
1029
  `;
@@ -673,16 +1031,16 @@ ${import_picocolors2.default.cyan(d2)}
673
1031
  } }).prompt();
674
1032
  };
675
1033
  var fe = (t) => {
676
- let n = (r, i) => {
677
- let s = r.label ?? String(r.value);
678
- return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
1034
+ let n = (r2, i) => {
1035
+ let s = r2.label ?? String(r2.value);
1036
+ return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
679
1037
  };
680
- return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? !0, cursorAt: t.cursorAt, validate(r) {
681
- if (this.required && r.length === 0)
1038
+ return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? !0, cursorAt: t.cursorAt, validate(r2) {
1039
+ if (this.required && r2.length === 0)
682
1040
  return `Please select at least one option.
683
1041
  ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
684
1042
  }, render() {
685
- let r = `${import_picocolors2.default.gray(o)}
1043
+ let r2 = `${import_picocolors2.default.gray(o)}
686
1044
  ${b2(this.state)} ${t.message}
687
1045
  `, i = (s, c) => {
688
1046
  let a = this.value.includes(s.value);
@@ -690,23 +1048,23 @@ ${b2(this.state)} ${t.message}
690
1048
  };
691
1049
  switch (this.state) {
692
1050
  case "submit":
693
- return `${r}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
1051
+ return `${r2}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
694
1052
  case "cancel": {
695
1053
  let s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => n(c, "cancelled")).join(import_picocolors2.default.dim(", "));
696
- return `${r}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
1054
+ return `${r2}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
697
1055
  ${import_picocolors2.default.gray(o)}` : ""}`;
698
1056
  }
699
1057
  case "error": {
700
1058
  let s = this.error.split(`
701
1059
  `).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
702
1060
  `);
703
- return `${r + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
1061
+ return `${r2 + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
704
1062
  ${import_picocolors2.default.yellow(o)} `)}
705
1063
  ${s}
706
1064
  `;
707
1065
  }
708
1066
  default:
709
- return `${r}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
1067
+ return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
710
1068
  ${import_picocolors2.default.cyan(o)} `)}
711
1069
  ${import_picocolors2.default.cyan(d2)}
712
1070
  `;
@@ -714,13 +1072,13 @@ ${import_picocolors2.default.cyan(d2)}
714
1072
  } }).prompt();
715
1073
  };
716
1074
  var Me = (t = "", n = "") => {
717
- let r = `
1075
+ let r2 = `
718
1076
  ${t}
719
1077
  `.split(`
720
- `), i = S2(n).length, s = Math.max(r.reduce((a, l) => {
721
- let $ = S2(l);
722
- return $.length > a ? $.length : a;
723
- }, 0), i) + 2, c = r.map((a) => `${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(a)}${" ".repeat(s - S2(a).length)}${import_picocolors2.default.gray(o)}`).join(`
1078
+ `), i = S2(n).length, s = Math.max(r2.reduce((a, l2) => {
1079
+ let $2 = S2(l2);
1080
+ return $2.length > a ? $2.length : a;
1081
+ }, 0), i) + 2, c = r2.map((a) => `${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(a)}${" ".repeat(s - S2(a).length)}${import_picocolors2.default.gray(o)}`).join(`
724
1082
  `);
725
1083
  process.stdout.write(`${import_picocolors2.default.gray(o)}
726
1084
  ${import_picocolors2.default.green(C)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(_2.repeat(Math.max(s - i - 1, 1)) + me)}
@@ -740,13 +1098,13 @@ ${import_picocolors2.default.gray(d2)} ${t}
740
1098
 
741
1099
  `);
742
1100
  }, M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
743
- let r = [`${import_picocolors2.default.gray(o)}`];
1101
+ let r2 = [`${import_picocolors2.default.gray(o)}`];
744
1102
  if (t) {
745
1103
  let [i, ...s] = t.split(`
746
1104
  `);
747
- r.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
1105
+ r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
748
1106
  }
749
- process.stdout.write(`${r.join(`
1107
+ process.stdout.write(`${r2.join(`
750
1108
  `)}
751
1109
  `);
752
1110
  }, info: (t) => {
@@ -763,52 +1121,52 @@ ${import_picocolors2.default.gray(d2)} ${t}
763
1121
  M2.message(t, { symbol: import_picocolors2.default.red(K2) });
764
1122
  } }, J2 = `${import_picocolors2.default.gray(o)} `;
765
1123
  var Y2 = ({ indicator: t = "dots" } = {}) => {
766
- let n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r = V2 ? 80 : 120, i = process.env.CI === "true", s, c, a = !1, l = "", $, g = performance.now(), p = (m) => {
767
- let h = m > 1 ? "Something went wrong" : "Canceled";
768
- a && N(h, m);
769
- }, v = () => p(2), f = () => p(1), j = () => {
770
- process.on("uncaughtExceptionMonitor", v), process.on("unhandledRejection", v), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p);
1124
+ let n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r2 = V2 ? 80 : 120, i = process.env.CI === "true", s, c, a = !1, l2 = "", $2, g2 = performance.now(), p2 = (m2) => {
1125
+ let h2 = m2 > 1 ? "Something went wrong" : "Canceled";
1126
+ a && N2(h2, m2);
1127
+ }, v2 = () => p2(2), f = () => p2(1), j2 = () => {
1128
+ process.on("uncaughtExceptionMonitor", v2), process.on("unhandledRejection", v2), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p2);
771
1129
  }, E = () => {
772
- process.removeListener("uncaughtExceptionMonitor", v), process.removeListener("unhandledRejection", v), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p);
773
- }, B = () => {
774
- if ($ === void 0)
1130
+ process.removeListener("uncaughtExceptionMonitor", v2), process.removeListener("unhandledRejection", v2), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p2);
1131
+ }, B2 = () => {
1132
+ if ($2 === void 0)
775
1133
  return;
776
1134
  i && process.stdout.write(`
777
1135
  `);
778
- let m = $.split(`
1136
+ let m2 = $2.split(`
779
1137
  `);
780
- process.stdout.write(import_sisteransi2.cursor.move(-999, m.length - 1)), process.stdout.write(import_sisteransi2.erase.down(m.length));
781
- }, R = (m) => m.replace(/\.+$/, ""), O = (m) => {
782
- let h = (performance.now() - m) / 1000, w = Math.floor(h / 60), I = Math.floor(h % 60);
783
- return w > 0 ? `[${w}m ${I}s]` : `[${I}s]`;
784
- }, H = (m = "") => {
785
- a = !0, s = fD(), l = R(m), g = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
1138
+ process.stdout.write(import_sisteransi2.cursor.move(-999, m2.length - 1)), process.stdout.write(import_sisteransi2.erase.down(m2.length));
1139
+ }, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
1140
+ let h2 = (performance.now() - m2) / 1000, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
1141
+ return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
1142
+ }, H2 = (m2 = "") => {
1143
+ a = !0, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
786
1144
  `);
787
- let h = 0, w = 0;
788
- j(), c = setInterval(() => {
789
- if (i && l === $)
1145
+ let h2 = 0, w2 = 0;
1146
+ j2(), c = setInterval(() => {
1147
+ if (i && l2 === $2)
790
1148
  return;
791
- B(), $ = l;
792
- let I = import_picocolors2.default.magenta(n[h]);
1149
+ B2(), $2 = l2;
1150
+ let I2 = import_picocolors2.default.magenta(n[h2]);
793
1151
  if (i)
794
- process.stdout.write(`${I} ${l}...`);
1152
+ process.stdout.write(`${I2} ${l2}...`);
795
1153
  else if (t === "timer")
796
- process.stdout.write(`${I} ${l} ${O(g)}`);
1154
+ process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
797
1155
  else {
798
- let z = ".".repeat(Math.floor(w)).slice(0, 3);
799
- process.stdout.write(`${I} ${l}${z}`);
1156
+ let z2 = ".".repeat(Math.floor(w2)).slice(0, 3);
1157
+ process.stdout.write(`${I2} ${l2}${z2}`);
800
1158
  }
801
- h = h + 1 < n.length ? h + 1 : 0, w = w < n.length ? w + 0.125 : 0;
802
- }, r);
803
- }, N = (m = "", h = 0) => {
804
- a = !1, clearInterval(c), B();
805
- let w = h === 0 ? import_picocolors2.default.green(C) : h === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
806
- l = R(m ?? l), t === "timer" ? process.stdout.write(`${w} ${l} ${O(g)}
807
- `) : process.stdout.write(`${w} ${l}
1159
+ h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
1160
+ }, r2);
1161
+ }, N2 = (m2 = "", h2 = 0) => {
1162
+ a = !1, clearInterval(c), B2();
1163
+ let w2 = h2 === 0 ? import_picocolors2.default.green(C) : h2 === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
1164
+ l2 = R2(m2 ?? l2), t === "timer" ? process.stdout.write(`${w2} ${l2} ${O2(g2)}
1165
+ `) : process.stdout.write(`${w2} ${l2}
808
1166
  `), E(), s();
809
1167
  };
810
- return { start: H, stop: N, message: (m = "") => {
811
- l = R(m ?? l);
1168
+ return { start: H2, stop: N2, message: (m2 = "") => {
1169
+ l2 = R2(m2 ?? l2);
812
1170
  } };
813
1171
  };
814
1172
 
@@ -816,7 +1174,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
816
1174
  var SIGNATURE_HEADER = "lopebase-signature";
817
1175
  var enc = /* @__PURE__ */ new TextEncoder;
818
1176
  function hex(bytes) {
819
- return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
1177
+ return [...new Uint8Array(bytes)].map((b3) => b3.toString(16).padStart(2, "0")).join("");
820
1178
  }
821
1179
  async function sha256Hex(data) {
822
1180
  return hex(await crypto.subtle.digest("SHA-256", data));
@@ -827,8 +1185,8 @@ async function hmacHex(secret, message) {
827
1185
  }
828
1186
  function newSigningSecret() {
829
1187
  let bytes = crypto.getRandomValues(new Uint8Array(32)), bin = "";
830
- for (let b of bytes)
831
- bin += String.fromCharCode(b);
1188
+ for (let b3 of bytes)
1189
+ bin += String.fromCharCode(b3);
832
1190
  return "lbs_" + btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
833
1191
  }
834
1192
  function payload(t, method, pathAndQuery, bodyHash) {
@@ -865,10 +1223,11 @@ var SENSITIVE_PARTS = /* @__PURE__ */ new Set([
865
1223
  ]), SENSITIVE_JOINED = /apikey|privatekey|secretkey|accesskey|accountnumber|routingnumber|stripecustomer|paymentmethod|recoverycode/;
866
1224
  function isSensitiveFieldName(name) {
867
1225
  let parts = name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
868
- if (parts.some((p) => SENSITIVE_PARTS.has(p)))
1226
+ if (parts.some((p2) => SENSITIVE_PARTS.has(p2)))
869
1227
  return !0;
870
1228
  return SENSITIVE_JOINED.test(parts.join(""));
871
1229
  }
1230
+ var PERSON_WORDS = "user|customer|client|member|contact|person|people|owner|author|sender|recipient|requester|assignee|reporter|buyer|guest|subscriber|lead|employee|student|patient|attendee|signer|holder", PERSON_NAME = new RegExp(`(${PERSON_WORDS})s?name$`), PEOPLE_RESOURCE = new RegExp(`^(${PERSON_WORDS})s?$`);
872
1231
 
873
1232
  // ../admin-adapter/src/adapter.ts
874
1233
  var DEFAULT_BASE_PATH = "/api/lopebase";
@@ -883,12 +1242,12 @@ function conformanceChecks(opts) {
883
1242
  let remote = !opts.adapter, base = opts.baseUrl ? opts.baseUrl.replace(/\/$/, "") : `${opts.origin ?? "https://property.test"}${(opts.basePath ?? opts.adapter?.config.basePath ?? DEFAULT_BASE_PATH).replace(/\/$/, "")}`, send = (req) => opts.adapter ? opts.adapter.handle(req) : fetch(req);
884
1243
  async function signedHeaders(url, method, bodyText, timestamp) {
885
1244
  if (opts.secret) {
886
- let u = new URL(url);
1245
+ let u2 = new URL(url);
887
1246
  return {
888
1247
  [SIGNATURE_HEADER]: await signRequest({
889
1248
  secret: opts.secret,
890
1249
  method,
891
- pathAndQuery: u.pathname + u.search,
1250
+ pathAndQuery: u2.pathname + u2.search,
892
1251
  body: bodyText,
893
1252
  timestamp
894
1253
  })
@@ -906,15 +1265,14 @@ function conformanceChecks(opts) {
906
1265
  ...init?.headers
907
1266
  }
908
1267
  }));
909
- }, resources = () => authed("/resources").then((r) => body(r));
910
- return [
1268
+ }, resources = () => authed("/resources").then((r2) => body(r2)), checks = [
911
1269
  {
912
1270
  name: "GET /health returns { ok } without auth",
913
1271
  async run() {
914
1272
  let res = await send(new Request(`${base}/health`));
915
1273
  assert.equal(res.status, 200);
916
- let b = await body(res);
917
- assert.equal(typeof b.ok, "boolean");
1274
+ let b3 = await body(res);
1275
+ assert.equal(typeof b3.ok, "boolean");
918
1276
  }
919
1277
  },
920
1278
  {
@@ -929,10 +1287,10 @@ function conformanceChecks(opts) {
929
1287
  async run() {
930
1288
  let res = await authed("/resources");
931
1289
  assert.equal(res.status, 200);
932
- let b = await body(res);
933
- assert.ok(Array.isArray(b.resources));
934
- for (let r of b.resources)
935
- assert.ok(r.name && r.label && r.readCapability), assert.ok(Array.isArray(r.fields));
1290
+ let b3 = await body(res);
1291
+ assert.ok(Array.isArray(b3.resources));
1292
+ for (let r2 of b3.resources)
1293
+ assert.ok(r2.name && r2.label && r2.readCapability), assert.ok(Array.isArray(r2.fields));
936
1294
  }
937
1295
  },
938
1296
  {
@@ -940,9 +1298,9 @@ function conformanceChecks(opts) {
940
1298
  async run() {
941
1299
  let res = await authed("/actions");
942
1300
  assert.equal(res.status, 200);
943
- let b = await body(res);
944
- assert.ok(Array.isArray(b.actions));
945
- for (let a of b.actions)
1301
+ let b3 = await body(res);
1302
+ assert.ok(Array.isArray(b3.actions));
1303
+ for (let a of b3.actions)
946
1304
  assert.ok(a.name && a.label && a.capability), assert.ok(Array.isArray(a.params));
947
1305
  }
948
1306
  },
@@ -961,7 +1319,7 @@ function conformanceChecks(opts) {
961
1319
  {
962
1320
  name: "GET /resources/:name/:id returns a row or 404-null",
963
1321
  async run() {
964
- let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((r) => r.name === opts.sampleResource) : cat.resources[0];
1322
+ let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((r2) => r2.name === opts.sampleResource) : cat.resources[0];
965
1323
  if (!meta)
966
1324
  return;
967
1325
  let first = (await body(await authed(`/resources/${meta.name}?limit=1`))).rows[0];
@@ -976,8 +1334,8 @@ function conformanceChecks(opts) {
976
1334
  async run() {
977
1335
  let res = await authed("/audit");
978
1336
  assert.equal(res.status, 200);
979
- let b = await body(res);
980
- assert.ok(Array.isArray(b.rows));
1337
+ let b3 = await body(res);
1338
+ assert.ok(Array.isArray(b3.rows));
981
1339
  }
982
1340
  },
983
1341
  {
@@ -991,24 +1349,24 @@ function conformanceChecks(opts) {
991
1349
  name: "no field in the catalog looks like a secret unless marked safe",
992
1350
  async run() {
993
1351
  let cat = await resources();
994
- for (let r of cat.resources)
995
- for (let f of r.fields)
996
- assert.ok(!isSensitiveFieldName(f.name) || f.sensitive === "allow", `${r.name}.${f.name} looks sensitive`);
1352
+ for (let r2 of cat.resources)
1353
+ for (let f of r2.fields)
1354
+ assert.ok(!isSensitiveFieldName(f.name) || f.sensitive === "allow", `${r2.name}.${f.name} looks sensitive`);
997
1355
  }
998
1356
  },
999
1357
  {
1000
1358
  name: "rows contain only declared fields, and a page is at most 100",
1001
1359
  async run() {
1002
1360
  let cat = await resources();
1003
- for (let r of cat.resources) {
1004
- let res = await authed(`/resources/${r.name}?limit=1000`);
1361
+ for (let r2 of cat.resources) {
1362
+ let res = await authed(`/resources/${r2.name}?limit=1000`);
1005
1363
  assert.equal(res.status, 200);
1006
1364
  let page = await body(res);
1007
- assert.ok(page.rows.length <= 100, `${r.name} returned ${page.rows.length} rows`);
1008
- let declared = /* @__PURE__ */ new Set(["id", ...r.fields.map((f) => f.name)]);
1365
+ assert.ok(page.rows.length <= 100, `${r2.name} returned ${page.rows.length} rows`);
1366
+ let declared = /* @__PURE__ */ new Set(["id", ...r2.fields.map((f) => f.name)]);
1009
1367
  for (let row of page.rows)
1010
- for (let k of Object.keys(row))
1011
- assert.ok(declared.has(k), `${r.name} returned undeclared field ${k}`);
1368
+ for (let k3 of Object.keys(row))
1369
+ assert.ok(declared.has(k3), `${r2.name} returned undeclared field ${k3}`);
1012
1370
  }
1013
1371
  }
1014
1372
  },
@@ -1026,7 +1384,7 @@ function conformanceChecks(opts) {
1026
1384
  {
1027
1385
  name: "a mutation writes an audit row (PATCH round-trips into /audit)",
1028
1386
  async run() {
1029
- let writable = (await resources()).resources.find((r) => r.writeCapability && r.fields.some((f) => f.editable));
1387
+ let writable = (await resources()).resources.find((r2) => r2.writeCapability && r2.fields.some((f) => f.editable));
1030
1388
  if (!writable || remote)
1031
1389
  return;
1032
1390
  let row = (await body(await authed(`/resources/${writable.name}?limit=1`))).rows[0];
@@ -1045,9 +1403,10 @@ function conformanceChecks(opts) {
1045
1403
  }
1046
1404
  }
1047
1405
  ];
1406
+ return remote ? checks.filter((c) => !c.name.startsWith("a mutation writes")) : checks;
1048
1407
  }
1049
1408
  // bin/lopebase.ts
1050
- var import_picocolors3 = __toESM(require_picocolors(), 1);
1409
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
1051
1410
 
1052
1411
  // src/adapter-check.ts
1053
1412
  async function checkAdapter(input) {
@@ -1061,8 +1420,8 @@ async function checkAdapter(input) {
1061
1420
  })
1062
1421
  } : { authorization: `Bearer ${input.token ?? ""}` };
1063
1422
  status = (await fetch(url, { headers })).status;
1064
- } catch (e) {
1065
- let error = `Could not reach ${base} (${e instanceof Error ? e.message : String(e)}).`;
1423
+ } catch (e2) {
1424
+ let error = `Could not reach ${base} (${e2 instanceof Error ? e2.message : String(e2)}).`;
1066
1425
  return log(`FAIL ${error}`), { passed, failed: [{ name: "reachable", error }] };
1067
1426
  }
1068
1427
  if (status === 401) {
@@ -1080,22 +1439,23 @@ async function checkAdapter(input) {
1080
1439
  }))
1081
1440
  try {
1082
1441
  await c.run(), passed++, log(`pass ${c.name}`);
1083
- } catch (e) {
1084
- let error = e instanceof Error ? e.message.split(`
1085
- `)[0] : String(e);
1442
+ } catch (e2) {
1443
+ let error = e2 instanceof Error ? e2.message.split(`
1444
+ `)[0] : String(e2);
1086
1445
  failed.push({ name: c.name, error }), log(`FAIL ${c.name}: ${error}`);
1087
1446
  }
1088
1447
  return { passed, failed };
1089
1448
  }
1090
1449
 
1091
1450
  // src/agents.ts
1451
+ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
1092
1452
  import { spawnSync } from "node:child_process";
1093
1453
  var AGENTS = [
1094
1454
  {
1095
1455
  id: "claude",
1096
1456
  label: "Claude Code",
1097
1457
  bin: "claude",
1098
- args: (p) => [p],
1458
+ args: (p2) => [p2],
1099
1459
  mcpAdd: (argv) => [
1100
1460
  "mcp",
1101
1461
  "add",
@@ -1110,32 +1470,48 @@ var AGENTS = [
1110
1470
  id: "codex",
1111
1471
  label: "Codex",
1112
1472
  bin: "codex",
1113
- args: (p) => [p],
1473
+ args: (p2) => [p2],
1114
1474
  mcpAdd: (argv) => ["mcp", "add", "lopebase", "--", ...argv]
1115
1475
  },
1116
1476
  {
1117
1477
  id: "gemini",
1118
1478
  label: "Gemini CLI",
1119
1479
  bin: "gemini",
1120
- args: (p) => ["-i", p],
1480
+ args: (p2) => ["-i", p2],
1121
1481
  mcpAdd: (argv) => ["mcp", "add", "lopebase", ...argv]
1122
1482
  },
1123
1483
  {
1124
1484
  id: "opencode",
1125
1485
  label: "opencode",
1126
1486
  bin: "opencode",
1127
- args: (p) => ["--prompt", p]
1487
+ args: (p2) => ["--prompt", p2]
1128
1488
  },
1129
1489
  {
1130
1490
  id: "cursor-agent",
1131
1491
  label: "Cursor Agent",
1132
1492
  bin: "cursor-agent",
1133
- args: (p) => [p]
1493
+ args: (p2) => [p2]
1134
1494
  }
1135
1495
  ];
1136
1496
  function isInstalled(bin) {
1137
- let probe = process.platform === "win32" ? "where" : "which";
1138
- return spawnSync(probe, [bin], { stdio: "ignore" }).status === 0;
1497
+ return whereIs(bin) !== null;
1498
+ }
1499
+ function whereIs(bin) {
1500
+ let probe = process.platform === "win32" ? "where" : "which", res = spawnSync(probe, [bin], { encoding: "utf8" });
1501
+ if (res.status !== 0)
1502
+ return null;
1503
+ return res.stdout.split(/\r?\n/)[0]?.trim() || null;
1504
+ }
1505
+ function portableArgs(bin, args, platform = process.platform, resolved = platform === "win32" ? whereIs(bin) : null) {
1506
+ if (platform !== "win32" || !resolved || !/\.(cmd|bat)$/i.test(resolved))
1507
+ return args;
1508
+ return args.map((a) => a.replace(/[ \t]*\r?\n\s*/g, " "));
1509
+ }
1510
+ function spawnAgent(bin, args, options) {
1511
+ return import_cross_spawn.default(bin, portableArgs(bin, args), options);
1512
+ }
1513
+ function spawnAgentSync(bin, args, options) {
1514
+ return import_cross_spawn.default.sync(bin, portableArgs(bin, args), options);
1139
1515
  }
1140
1516
  function installedAgents() {
1141
1517
  return AGENTS.filter((a) => isInstalled(a.bin));
@@ -1152,6 +1528,22 @@ import {
1152
1528
  import os from "node:os";
1153
1529
  import path from "node:path";
1154
1530
  var DEFAULT_URL = "https://app.lopebase.com";
1531
+ function workspaceUrl(input) {
1532
+ let v2 = input.trim().replace(/\/+$/, "");
1533
+ if (!v2)
1534
+ return null;
1535
+ if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(v2))
1536
+ return `https://${v2.toLowerCase()}.lopebase.com`;
1537
+ let withScheme = /^https?:\/\//i.test(v2) ? v2 : `https://${v2}`;
1538
+ try {
1539
+ let u2 = new URL(withScheme);
1540
+ if (!u2.hostname.includes(".") && u2.hostname !== "localhost")
1541
+ return null;
1542
+ return `${u2.origin}${u2.pathname.replace(/\/+$/, "")}`;
1543
+ } catch {
1544
+ return null;
1545
+ }
1546
+ }
1155
1547
  function configDir() {
1156
1548
  let base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
1157
1549
  return path.join(base, "lopebase");
@@ -1181,21 +1573,21 @@ function saveAuth(auth) {
1181
1573
  var mcpEndpoint = (url) => `${url.replace(/\/+$/, "")}/api/mcp`;
1182
1574
 
1183
1575
  // src/detect.ts
1184
- var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_)|_DSN$/;
1576
+ var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_|REACT_APP_|GATSBY_)|_DSN$/, file = (name) => new RegExp(`(^|/)${name.replace(/[.]/g, "\\.")}$`);
1185
1577
  function evidenceFor(facts, rule) {
1186
1578
  let out = [], weak = [];
1187
1579
  for (let re of rule.deps ?? []) {
1188
- let hit = facts.dependencies.find((d) => re.test(d));
1580
+ let hit = facts.dependencies.find((d3) => re.test(d3));
1189
1581
  if (hit)
1190
- out.push(`uses ${hit}`);
1582
+ out.push(`uses ${hit.replace(/^(py|gem|composer|go|cargo):/, "")}`);
1191
1583
  }
1192
1584
  for (let re of rule.files ?? []) {
1193
1585
  let hit = facts.files.find((f) => re.test(f));
1194
1586
  if (hit)
1195
- out.push(`has ${hit}`);
1587
+ out.push(`has ${hit.replace(/#.*$/, "")}`);
1196
1588
  }
1197
1589
  for (let re of rule.env ?? []) {
1198
- let hit = facts.envKeys.find((k) => re.test(k));
1590
+ let hit = facts.envKeys.find((k3) => re.test(k3));
1199
1591
  if (hit)
1200
1592
  (APP_SIDE_ENV.test(hit) ? out : weak).push(`env ${hit}`);
1201
1593
  }
@@ -1204,15 +1596,31 @@ function evidenceFor(facts, rule) {
1204
1596
  var SOURCE_RULES = {
1205
1597
  posthog: {
1206
1598
  label: "PostHog",
1207
- rule: { deps: [/^posthog-(js|node)$/], env: [/POSTHOG/] },
1599
+ rule: {
1600
+ deps: [
1601
+ /^posthog-(js|node|react-native)$/,
1602
+ /^py:posthog$/,
1603
+ /^gem:posthog-ruby$/,
1604
+ /^composer:posthog\//,
1605
+ /^go:github\.com\/posthog\//
1606
+ ],
1607
+ env: [/POSTHOG/]
1608
+ },
1208
1609
  why: "Signups, active users, and funnels on your dashboard."
1209
1610
  },
1210
1611
  ga4: {
1211
1612
  label: "Google Analytics",
1212
1613
  rule: {
1213
- deps: [/^@next\/third-parties$/, /^react-ga4$/, /^vue-gtag/],
1614
+ deps: [
1615
+ /^@next\/third-parties$/,
1616
+ /^react-ga4$/,
1617
+ /^vue-gtag/,
1618
+ /^nuxt-gtag$/,
1619
+ /^gatsby-plugin-google-(gtag|analytics)$/,
1620
+ /^@analytics\/google-analytics$/
1621
+ ],
1214
1622
  env: [
1215
- /^(NEXT_PUBLIC_|PUBLIC_|VITE_)?GA(4)?_(MEASUREMENT_)?ID$/,
1623
+ /^(NEXT_PUBLIC_|PUBLIC_|VITE_|GATSBY_|REACT_APP_)?GA(4)?_(MEASUREMENT_|TRACKING_)?ID$/,
1216
1624
  /GTM_ID/
1217
1625
  ]
1218
1626
  },
@@ -1221,7 +1629,14 @@ var SOURCE_RULES = {
1221
1629
  sentry: {
1222
1630
  label: "Sentry",
1223
1631
  rule: {
1224
- deps: [/^@sentry\//],
1632
+ deps: [
1633
+ /^@sentry\//,
1634
+ /^py:sentry-sdk$/,
1635
+ /^gem:sentry-(ruby|rails)$/,
1636
+ /^composer:sentry\//,
1637
+ /^go:github\.com\/getsentry\//,
1638
+ /^cargo:sentry$/
1639
+ ],
1225
1640
  files: [/sentry\.(client|server|edge)\.config/],
1226
1641
  env: [/SENTRY_DSN/]
1227
1642
  },
@@ -1229,80 +1644,392 @@ var SOURCE_RULES = {
1229
1644
  },
1230
1645
  stripe: {
1231
1646
  label: "Stripe",
1232
- rule: { deps: [/^stripe$/, /^@stripe\//], env: [/^STRIPE_/] },
1647
+ rule: {
1648
+ deps: [
1649
+ /^stripe$/,
1650
+ /^@stripe\//,
1651
+ /^py:stripe$/,
1652
+ /^gem:stripe$/,
1653
+ /^composer:stripe\//,
1654
+ /^go:github\.com\/stripe\//
1655
+ ],
1656
+ env: [/^STRIPE_/, /^(NEXT_PUBLIC_|PUBLIC_|VITE_)STRIPE_/]
1657
+ },
1233
1658
  why: "Revenue, new customers, and churn per product."
1659
+ },
1660
+ polar: {
1661
+ label: "Polar",
1662
+ rule: { deps: [/^@polar-sh\//, /^py:polar-sdk$/], env: [/^POLAR_/] },
1663
+ why: "Revenue and subscriptions from Polar per product."
1664
+ },
1665
+ supabase: {
1666
+ label: "Supabase",
1667
+ rule: {
1668
+ deps: [/^@supabase\//, /^py:supabase$/, /^composer:supabase\//],
1669
+ files: [file("supabase/config.toml")],
1670
+ env: [/SUPABASE_URL$/]
1671
+ },
1672
+ why: "Database health, security warnings, disk, and backups next to everything else."
1673
+ },
1674
+ resend: {
1675
+ label: "Resend",
1676
+ rule: {
1677
+ deps: [/^resend$/, /^py:resend$/, /^gem:resend$/, /^composer:resend\//],
1678
+ env: [/^RESEND_/]
1679
+ },
1680
+ why: "Bounce and complaint rates, so you know your emails land."
1681
+ },
1682
+ sequenzy: {
1683
+ label: "Sequenzy",
1684
+ rule: { deps: [/^@?sequenzy/], env: [/SEQUENZY/] },
1685
+ why: "Your audience and running email sequences at a glance."
1234
1686
  }
1235
1687
  }, STACK_RULES = [
1236
- { kind: "framework", name: "Next.js", rule: { deps: [/^next$/] } },
1237
- { kind: "framework", name: "Astro", rule: { deps: [/^astro$/] } },
1688
+ {
1689
+ kind: "framework",
1690
+ name: "Next.js",
1691
+ rule: { deps: [/^next$/], files: [/(^|\/)next\.config\./] }
1692
+ },
1693
+ {
1694
+ kind: "framework",
1695
+ name: "Astro",
1696
+ rule: { deps: [/^astro$/], files: [/(^|\/)astro\.config\./] }
1697
+ },
1238
1698
  {
1239
1699
  kind: "framework",
1240
1700
  name: "SvelteKit",
1241
1701
  rule: { deps: [/^@sveltejs\/kit$/] }
1242
1702
  },
1703
+ { kind: "framework", name: "Remix", rule: { deps: [/^@remix-run\//] } },
1704
+ {
1705
+ kind: "framework",
1706
+ name: "React Router",
1707
+ rule: { deps: [/^@react-router\/dev$/] }
1708
+ },
1709
+ {
1710
+ kind: "framework",
1711
+ name: "Nuxt",
1712
+ rule: { deps: [/^nuxt$/], files: [/(^|\/)nuxt\.config\./] }
1713
+ },
1714
+ { kind: "framework", name: "Gatsby", rule: { deps: [/^gatsby$/] } },
1715
+ {
1716
+ kind: "framework",
1717
+ name: "Docusaurus",
1718
+ rule: { deps: [/^@docusaurus\/core$/] }
1719
+ },
1720
+ {
1721
+ kind: "framework",
1722
+ name: "Eleventy",
1723
+ rule: {
1724
+ deps: [/^@11ty\/eleventy$/],
1725
+ files: [/(^|\/)(\.eleventy\.js|eleventy\.config\.)/]
1726
+ }
1727
+ },
1728
+ {
1729
+ kind: "framework",
1730
+ name: "Hugo",
1731
+ rule: { files: [/(^|\/)hugo\.(toml|ya?ml|json)$/] }
1732
+ },
1733
+ { kind: "framework", name: "Jekyll", rule: { deps: [/^gem:jekyll$/] } },
1734
+ { kind: "framework", name: "Angular", rule: { deps: [/^@angular\/core$/] } },
1243
1735
  {
1244
1736
  kind: "framework",
1245
- name: "Remix",
1246
- rule: { deps: [/^@remix-run\//, /^react-router$/] }
1737
+ name: "SolidStart",
1738
+ rule: { deps: [/^@solidjs\/start$/] }
1247
1739
  },
1248
- { kind: "framework", name: "Nuxt", rule: { deps: [/^nuxt$/] } },
1740
+ {
1741
+ kind: "framework",
1742
+ name: "Qwik",
1743
+ rule: { deps: [/^@builder\.io\/qwik-city$/] }
1744
+ },
1745
+ {
1746
+ kind: "framework",
1747
+ name: "TanStack Start",
1748
+ rule: { deps: [/^@tanstack\/(react-)?start$/] }
1749
+ },
1750
+ { kind: "framework", name: "Expo", rule: { deps: [/^expo$/] } },
1751
+ { kind: "framework", name: "Electron", rule: { deps: [/^electron$/] } },
1249
1752
  { kind: "framework", name: "Hono", rule: { deps: [/^hono$/] } },
1753
+ { kind: "framework", name: "Express", rule: { deps: [/^express$/] } },
1754
+ { kind: "framework", name: "Fastify", rule: { deps: [/^fastify$/] } },
1755
+ { kind: "framework", name: "NestJS", rule: { deps: [/^@nestjs\/core$/] } },
1756
+ {
1757
+ kind: "framework",
1758
+ name: "Django",
1759
+ rule: { deps: [/^py:django$/], files: [/(^|\/)manage\.py$/] }
1760
+ },
1761
+ { kind: "framework", name: "Flask", rule: { deps: [/^py:flask$/] } },
1762
+ { kind: "framework", name: "FastAPI", rule: { deps: [/^py:fastapi$/] } },
1763
+ { kind: "framework", name: "Rails", rule: { deps: [/^gem:rails$/] } },
1764
+ {
1765
+ kind: "framework",
1766
+ name: "Laravel",
1767
+ rule: {
1768
+ deps: [/^composer:laravel\/framework$/],
1769
+ files: [/(^|\/)artisan$/]
1770
+ }
1771
+ },
1772
+ {
1773
+ kind: "framework",
1774
+ name: "WordPress",
1775
+ rule: { files: [/(^|\/)wp-config(-sample)?\.php$/, /(^|\/)wp-content$/] }
1776
+ },
1777
+ {
1778
+ kind: "framework",
1779
+ name: "Shopify app",
1780
+ rule: { files: [/(^|\/)shopify\.app\.toml$/] }
1781
+ },
1782
+ { kind: "framework", name: "Vue", rule: { deps: [/^vue$/] } },
1783
+ { kind: "framework", name: "React", rule: { deps: [/^react$/] } },
1784
+ { kind: "framework", name: "Vite", rule: { deps: [/^vite$/] } },
1785
+ { kind: "language", name: "TypeScript", rule: { deps: [/^typescript$/] } },
1786
+ {
1787
+ kind: "language",
1788
+ name: "Python",
1789
+ rule: { files: [/(^|\/)(requirements\.txt|pyproject\.toml|Pipfile)$/] }
1790
+ },
1791
+ { kind: "language", name: "Ruby", rule: { files: [/(^|\/)Gemfile$/] } },
1792
+ { kind: "language", name: "PHP", rule: { files: [/(^|\/)composer\.json$/] } },
1793
+ { kind: "language", name: "Go", rule: { files: [/(^|\/)go\.mod$/] } },
1794
+ { kind: "language", name: "Rust", rule: { files: [/(^|\/)Cargo\.toml$/] } },
1250
1795
  {
1251
1796
  kind: "hosting",
1252
1797
  name: "Cloudflare Workers",
1253
1798
  rule: {
1254
1799
  files: [/(^|\/)wrangler\.(toml|jsonc?)$/],
1255
- deps: [/^@opennextjs\/cloudflare$/]
1800
+ deps: [
1801
+ /^@opennextjs\/cloudflare$/,
1802
+ /^@astrojs\/cloudflare$/,
1803
+ /^@sveltejs\/adapter-cloudflare/
1804
+ ]
1805
+ }
1806
+ },
1807
+ {
1808
+ kind: "hosting",
1809
+ name: "Vercel",
1810
+ rule: {
1811
+ files: [file("vercel.json")],
1812
+ deps: [
1813
+ /^@vercel\/(analytics|speed-insights|og)$/,
1814
+ /^@astrojs\/vercel$/,
1815
+ /^@sveltejs\/adapter-vercel$/
1816
+ ]
1256
1817
  }
1257
1818
  },
1258
- { kind: "hosting", name: "Vercel", rule: { files: [/(^|\/)vercel\.json$/] } },
1259
1819
  {
1260
1820
  kind: "hosting",
1261
1821
  name: "Netlify",
1262
- rule: { files: [/(^|\/)netlify\.toml$/] }
1822
+ rule: {
1823
+ files: [file("netlify.toml")],
1824
+ deps: [/^@netlify\//, /^@astrojs\/netlify$/]
1825
+ }
1263
1826
  },
1264
- { kind: "hosting", name: "Fly.io", rule: { files: [/(^|\/)fly\.toml$/] } },
1827
+ { kind: "hosting", name: "Fly.io", rule: { files: [file("fly.toml")] } },
1265
1828
  {
1266
- kind: "database",
1267
- name: "Supabase",
1268
- rule: { deps: [/^@supabase\//], files: [/(^|\/)supabase\/config\.toml$/] }
1829
+ kind: "hosting",
1830
+ name: "Render",
1831
+ rule: { files: [/(^|\/)render\.ya?ml$/] }
1832
+ },
1833
+ {
1834
+ kind: "hosting",
1835
+ name: "Railway",
1836
+ rule: { files: [/(^|\/)railway\.(json|toml)$/] }
1837
+ },
1838
+ {
1839
+ kind: "hosting",
1840
+ name: "Firebase Hosting",
1841
+ rule: { files: [file("firebase.json")] }
1269
1842
  },
1843
+ {
1844
+ kind: "hosting",
1845
+ name: "AWS Amplify",
1846
+ rule: { files: [/(^|\/)amplify\.ya?ml$/] }
1847
+ },
1848
+ { kind: "hosting", name: "Heroku", rule: { files: [file("Procfile")] } },
1849
+ { kind: "hosting", name: "GitHub Pages", rule: { files: [file("CNAME")] } },
1850
+ {
1851
+ kind: "hosting",
1852
+ name: "Docker",
1853
+ rule: { files: [file("Dockerfile"), /(^|\/)(docker-)?compose\.ya?ml$/] }
1854
+ },
1855
+ { kind: "database", name: "Supabase", rule: SOURCE_RULES.supabase.rule },
1270
1856
  {
1271
1857
  kind: "database",
1272
1858
  name: "Convex",
1273
- rule: { deps: [/^convex$/], files: [/(^|\/)convex\/schema\.ts$/] }
1859
+ rule: { deps: [/^convex$/], files: [file("convex/schema.ts")] }
1274
1860
  },
1861
+ { kind: "database", name: "Neon", rule: { deps: [/^@neondatabase\//] } },
1275
1862
  {
1276
1863
  kind: "database",
1277
- name: "Neon",
1278
- rule: { deps: [/^@neondatabase\//, /^@neon\/(env|config)$/] }
1864
+ name: "Postgres",
1865
+ rule: {
1866
+ deps: [
1867
+ /^postgres$/,
1868
+ /^pg$/,
1869
+ /^py:psycopg2?(-binary)?$/,
1870
+ /^py:asyncpg$/,
1871
+ /^gem:pg$/
1872
+ ]
1873
+ }
1279
1874
  },
1280
1875
  {
1281
1876
  kind: "database",
1282
- name: "Postgres",
1283
- rule: { deps: [/^postgres$/, /^pg$/] }
1877
+ name: "MySQL",
1878
+ rule: { deps: [/^mysql2?$/, /^py:(mysqlclient|pymysql)$/, /^gem:mysql2$/] }
1879
+ },
1880
+ {
1881
+ kind: "database",
1882
+ name: "SQLite",
1883
+ rule: { deps: [/^better-sqlite3$/, /^gem:sqlite3$/] }
1884
+ },
1885
+ {
1886
+ kind: "database",
1887
+ name: "MongoDB",
1888
+ rule: { deps: [/^mongodb$/, /^mongoose$/, /^py:pymongo$/, /^py:motor$/] }
1889
+ },
1890
+ {
1891
+ kind: "database",
1892
+ name: "Firebase",
1893
+ rule: { deps: [/^firebase(-admin)?$/, /^py:firebase-admin$/] }
1894
+ },
1895
+ {
1896
+ kind: "database",
1897
+ name: "PlanetScale",
1898
+ rule: { deps: [/^@planetscale\/database$/] }
1899
+ },
1900
+ { kind: "database", name: "Turso", rule: { deps: [/^@libsql\/client$/] } },
1901
+ {
1902
+ kind: "database",
1903
+ name: "Redis",
1904
+ rule: { deps: [/^@upstash\/redis$/, /^ioredis$/, /^redis$/, /^py:redis$/] }
1905
+ },
1906
+ {
1907
+ kind: "database",
1908
+ name: "Prisma",
1909
+ rule: {
1910
+ deps: [/^@prisma\/client$/],
1911
+ files: [file("prisma/schema.prisma")]
1912
+ }
1284
1913
  },
1285
- { kind: "database", name: "Prisma", rule: { deps: [/^@prisma\/client$/] } },
1286
1914
  { kind: "database", name: "Drizzle", rule: { deps: [/^drizzle-orm$/] } },
1287
1915
  {
1288
1916
  kind: "database",
1289
1917
  name: "Cloudflare D1",
1290
1918
  rule: { files: [/(^|\/)wrangler\.(toml|jsonc?)#d1$/] }
1291
1919
  },
1920
+ { kind: "email", name: "Resend", rule: SOURCE_RULES.resend.rule },
1921
+ { kind: "email", name: "Sequenzy", rule: SOURCE_RULES.sequenzy.rule },
1922
+ {
1923
+ kind: "email",
1924
+ name: "SendGrid",
1925
+ rule: { deps: [/^@sendgrid\/mail$/, /^py:sendgrid$/], env: [/^SENDGRID_/] }
1926
+ },
1927
+ {
1928
+ kind: "email",
1929
+ name: "Postmark",
1930
+ rule: { deps: [/^postmark$/, /^py:postmarker$/], env: [/^POSTMARK_/] }
1931
+ },
1292
1932
  {
1293
1933
  kind: "email",
1294
- name: "Resend",
1295
- rule: { deps: [/^resend$/], env: [/^RESEND_/] }
1934
+ name: "Mailgun",
1935
+ rule: { deps: [/^mailgun\.js$/], env: [/^MAILGUN_/] }
1296
1936
  },
1937
+ {
1938
+ kind: "email",
1939
+ name: "Loops",
1940
+ rule: { deps: [/^loops$/], env: [/^LOOPS_/] }
1941
+ },
1942
+ {
1943
+ kind: "email",
1944
+ name: "Amazon SES",
1945
+ rule: { deps: [/^@aws-sdk\/client-sesv?2?$/] }
1946
+ },
1947
+ { kind: "email", name: "Nodemailer", rule: { deps: [/^nodemailer$/] } },
1948
+ { kind: "billing", name: "Stripe", rule: SOURCE_RULES.stripe.rule },
1949
+ { kind: "billing", name: "Polar", rule: SOURCE_RULES.polar.rule },
1297
1950
  {
1298
1951
  kind: "billing",
1299
- name: "Polar",
1300
- rule: { deps: [/^@polar-sh\//], env: [/^POLAR_/] }
1952
+ name: "Lemon Squeezy",
1953
+ rule: { deps: [/^@lemonsqueezy\//], env: [/^LEMON_?SQUEEZY_/] }
1954
+ },
1955
+ {
1956
+ kind: "billing",
1957
+ name: "Paddle",
1958
+ rule: { deps: [/^@paddle\//], env: [/^PADDLE_/] }
1301
1959
  },
1302
1960
  { kind: "auth", name: "Clerk", rule: { deps: [/^@clerk\//] } },
1303
- { kind: "auth", name: "WorkOS", rule: { deps: [/^@workos-inc\//] } },
1304
- { kind: "auth", name: "Better Auth", rule: { deps: [/^better-auth$/] } }
1305
- ];
1961
+ {
1962
+ kind: "auth",
1963
+ name: "WorkOS",
1964
+ rule: { deps: [/^@workos-inc\//, /^py:workos$/] }
1965
+ },
1966
+ { kind: "auth", name: "Better Auth", rule: { deps: [/^better-auth$/] } },
1967
+ {
1968
+ kind: "auth",
1969
+ name: "Auth.js",
1970
+ rule: { deps: [/^next-auth$/, /^@auth\/core$/] }
1971
+ },
1972
+ { kind: "auth", name: "Auth0", rule: { deps: [/^@auth0\//] } },
1973
+ { kind: "auth", name: "Kinde", rule: { deps: [/^@kinde-oss\//] } },
1974
+ { kind: "auth", name: "Lucia", rule: { deps: [/^lucia$/] } },
1975
+ { kind: "auth", name: "Devise", rule: { deps: [/^gem:devise$/] } },
1976
+ {
1977
+ kind: "analytics",
1978
+ name: "Vercel Analytics",
1979
+ rule: { deps: [/^@vercel\/analytics$/] }
1980
+ },
1981
+ {
1982
+ kind: "analytics",
1983
+ name: "Plausible",
1984
+ rule: { deps: [/plausible/], env: [/PLAUSIBLE/] }
1985
+ },
1986
+ { kind: "analytics", name: "Umami", rule: { env: [/UMAMI/] } },
1987
+ { kind: "analytics", name: "Fathom", rule: { deps: [/^fathom-client$/] } },
1988
+ {
1989
+ kind: "analytics",
1990
+ name: "Mixpanel",
1991
+ rule: { deps: [/^mixpanel(-browser)?$/] }
1992
+ },
1993
+ { kind: "analytics", name: "Amplitude", rule: { deps: [/^@amplitude\//] } },
1994
+ {
1995
+ kind: "cms",
1996
+ name: "Sanity",
1997
+ rule: { deps: [/^(next-)?sanity$/, /^@sanity\/client$/] }
1998
+ },
1999
+ { kind: "cms", name: "Contentful", rule: { deps: [/^contentful$/] } },
2000
+ { kind: "cms", name: "Payload", rule: { deps: [/^payload$/] } },
2001
+ { kind: "cms", name: "Strapi", rule: { deps: [/^@strapi\/strapi$/] } },
2002
+ { kind: "cms", name: "Keystatic", rule: { deps: [/^@keystatic\/core$/] } },
2003
+ {
2004
+ kind: "cms",
2005
+ name: "MDX",
2006
+ rule: { deps: [/^@next\/mdx$/, /^@astrojs\/mdx$/, /^@mdx-js\//] }
2007
+ }
2008
+ ], WEB_FRAMEWORKS = /* @__PURE__ */ new Set([
2009
+ "Next.js",
2010
+ "Astro",
2011
+ "SvelteKit",
2012
+ "Remix",
2013
+ "React Router",
2014
+ "Nuxt",
2015
+ "Gatsby",
2016
+ "Docusaurus",
2017
+ "Eleventy",
2018
+ "Hugo",
2019
+ "Jekyll",
2020
+ "Angular",
2021
+ "SolidStart",
2022
+ "Qwik",
2023
+ "TanStack Start",
2024
+ "Django",
2025
+ "Flask",
2026
+ "Rails",
2027
+ "Laravel",
2028
+ "WordPress",
2029
+ "Vue",
2030
+ "React",
2031
+ "Vite"
2032
+ ]);
1306
2033
  function detectStack(facts) {
1307
2034
  let stack = [];
1308
2035
  for (let s of STACK_RULES) {
@@ -1310,7 +2037,13 @@ function detectStack(facts) {
1310
2037
  if (ev.length)
1311
2038
  stack.push({ kind: s.kind, name: s.name, evidence: ev[0] });
1312
2039
  }
1313
- let sources = [];
2040
+ if (!stack.some((s) => s.kind === "framework") && facts.files.some((f) => /(^|\/)index\.html$/.test(f)))
2041
+ stack.unshift({
2042
+ kind: "framework",
2043
+ name: "Static site",
2044
+ evidence: "has index.html"
2045
+ });
2046
+ let tidy = stack.filter((s) => s.kind === "framework" && !["React", "Vite", "Vue"].includes(s.name)).length ? stack.filter((s) => !["React", "Vite", "Vue"].includes(s.name)) : stack, sources = [];
1314
2047
  for (let [id, s] of Object.entries(SOURCE_RULES)) {
1315
2048
  let { strong, weak } = evidenceFor(facts, s.rule);
1316
2049
  if (strong.length || weak.length)
@@ -1322,7 +2055,15 @@ function detectStack(facts) {
1322
2055
  why: s.why
1323
2056
  });
1324
2057
  }
1325
- let has = (id) => sources.some((s) => s.source === id && s.status === "found"), listed = (id) => sources.some((s) => s.source === id), isWebsite = stack.some((s) => s.kind === "framework") || !!facts.siteUrl, gaps = [];
2058
+ if (facts.repo)
2059
+ sources.push({
2060
+ source: "github",
2061
+ label: "GitHub",
2062
+ status: "found",
2063
+ evidence: [`repo ${facts.repo}`],
2064
+ why: "Open pull requests, issues, and failing Actions runs."
2065
+ });
2066
+ let has = (id) => sources.some((s) => s.source === id && s.status === "found"), listed = (id) => sources.some((s) => s.source === id), isWebsite = tidy.some((s) => s.kind === "framework" && (WEB_FRAMEWORKS.has(s.name) || s.name === "Static site")) || !!facts.siteUrl, gaps = [];
1326
2067
  if (isWebsite)
1327
2068
  sources.push({
1328
2069
  source: "gsc",
@@ -1336,8 +2077,14 @@ function detectStack(facts) {
1336
2077
  status: "suggested",
1337
2078
  evidence: [],
1338
2079
  why: "Bing and ChatGPT search traffic. Imports from Search Console in one click."
2080
+ }, {
2081
+ source: "uptime",
2082
+ label: "Uptime check",
2083
+ status: "suggested",
2084
+ evidence: [],
2085
+ why: "Know the moment the site goes down. Nothing to install."
1339
2086
  });
1340
- if (!has("posthog") && !has("ga4")) {
2087
+ if (isWebsite && !has("posthog") && !has("ga4")) {
1341
2088
  if (gaps.push("No product analytics found. Add PostHog (free tier, one snippet) so LopeBase can show signups and active users."), !listed("posthog"))
1342
2089
  sources.push({
1343
2090
  source: "posthog",
@@ -1357,7 +2104,7 @@ function detectStack(facts) {
1357
2104
  why: SOURCE_RULES.sentry.why
1358
2105
  });
1359
2106
  }
1360
- return { stack, sources, gaps };
2107
+ return { stack: tidy, sources, gaps };
1361
2108
  }
1362
2109
 
1363
2110
  // src/device-login.ts
@@ -1374,27 +2121,27 @@ async function startDeviceLogin(url, opts = {}) {
1374
2121
  });
1375
2122
  if (!res.ok)
1376
2123
  return null;
1377
- let b = await res.json();
1378
- if (typeof b.device_code !== "string" || typeof b.user_code !== "string")
2124
+ let b3 = await res.json();
2125
+ if (typeof b3.device_code !== "string" || typeof b3.user_code !== "string")
1379
2126
  return null;
1380
2127
  return {
1381
- deviceCode: b.device_code,
1382
- userCode: b.user_code,
1383
- verifyUrl: String(b.verification_uri_complete ?? b.verification_uri),
1384
- interval: Number(b.interval) || 5,
1385
- expiresIn: Number(b.expires_in) || 600
2128
+ deviceCode: b3.device_code,
2129
+ userCode: b3.user_code,
2130
+ verifyUrl: String(b3.verification_uri_complete ?? b3.verification_uri),
2131
+ interval: Number(b3.interval) || 5,
2132
+ expiresIn: Number(b3.expires_in) || 600
1386
2133
  };
1387
2134
  } catch {
1388
2135
  return null;
1389
2136
  }
1390
2137
  }
1391
2138
  async function pollDeviceLogin(url, start, opts = {}) {
1392
- let f = opts.fetchImpl ?? fetch, sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))), interval = start.interval, deadline = Date.now() + start.expiresIn * 1000;
2139
+ let f = opts.fetchImpl ?? fetch, sleep = opts.sleep ?? ((ms) => new Promise((r2) => setTimeout(r2, ms))), interval = start.interval, deadline = Date.now() + start.expiresIn * 1000;
1393
2140
  while (Date.now() < deadline) {
1394
2141
  await sleep(interval * 1000);
1395
- let body;
2142
+ let body2;
1396
2143
  try {
1397
- body = await (await f(`${url}/api/oauth/token`, {
2144
+ body2 = await (await f(`${url}/api/oauth/token`, {
1398
2145
  method: "POST",
1399
2146
  headers: { "content-type": "application/x-www-form-urlencoded" },
1400
2147
  body: new URLSearchParams({
@@ -1405,14 +2152,14 @@ async function pollDeviceLogin(url, start, opts = {}) {
1405
2152
  } catch {
1406
2153
  continue;
1407
2154
  }
1408
- if (typeof body.access_token === "string")
2155
+ if (typeof body2.access_token === "string")
1409
2156
  return {
1410
2157
  ok: !0,
1411
- token: body.access_token,
1412
- url: typeof body.workspace_url === "string" ? body.workspace_url : url,
1413
- scope: String(body.scope ?? "")
2158
+ token: body2.access_token,
2159
+ url: typeof body2.workspace_url === "string" ? body2.workspace_url : url,
2160
+ scope: String(body2.scope ?? "")
1414
2161
  };
1415
- switch (body.error) {
2162
+ switch (body2.error) {
1416
2163
  case "authorization_pending":
1417
2164
  continue;
1418
2165
  case "slow_down":
@@ -1434,7 +2181,7 @@ async function pollDeviceLogin(url, start, opts = {}) {
1434
2181
  return {
1435
2182
  ok: !1,
1436
2183
  reason: "error",
1437
- message: String(body.error_description ?? body.error ?? "Sign-in failed.")
2184
+ message: String(body2.error_description ?? body2.error ?? "Sign-in failed.")
1438
2185
  };
1439
2186
  }
1440
2187
  }
@@ -1503,7 +2250,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
1503
2250
  }
1504
2251
  process.stdout.write(`${text}
1505
2252
  `);
1506
- } catch (e) {
2253
+ } catch (e2) {
1507
2254
  if (id === null)
1508
2255
  return;
1509
2256
  write({
@@ -1511,7 +2258,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
1511
2258
  id,
1512
2259
  error: {
1513
2260
  code: -32000,
1514
- message: `Could not reach LopeBase at ${endpoint}: ${String(e)}`
2261
+ message: `Could not reach LopeBase at ${endpoint}: ${String(e2)}`
1515
2262
  }
1516
2263
  });
1517
2264
  }
@@ -1543,116 +2290,473 @@ function onboardingPrompt(input) {
1543
2290
  }
1544
2291
 
1545
2292
  // src/scan.ts
1546
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
2293
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1547
2294
  import path2 from "node:path";
1548
- var MONOREPO_DIRS = ["apps", "packages", "services", "web", "site"], MARKER_FILES = [
1549
- "wrangler.toml",
1550
- "wrangler.json",
1551
- "wrangler.jsonc",
1552
- "vercel.json",
1553
- "netlify.toml",
1554
- "fly.toml",
2295
+ var MONOREPO_DIRS = ["apps", "packages", "services", "sites", "web", "site"], SKIP_DIRS = /* @__PURE__ */ new Set([
2296
+ "node_modules",
2297
+ ".git",
2298
+ ".next",
2299
+ ".nuxt",
2300
+ ".svelte-kit",
2301
+ ".turbo",
2302
+ ".vercel",
2303
+ ".wrangler",
2304
+ "dist",
2305
+ "build",
2306
+ "out",
2307
+ "vendor",
2308
+ "venv",
2309
+ ".venv",
2310
+ "target"
2311
+ ]), MAX_PACKAGES = 200, MARKERS = [
2312
+ /^wrangler\.(toml|jsonc?)$/,
2313
+ /^vercel\.json$/,
2314
+ /^netlify\.toml$/,
2315
+ /^fly\.toml$/,
2316
+ /^render\.ya?ml$/,
2317
+ /^railway\.(json|toml)$/,
2318
+ /^firebase\.json$/,
2319
+ /^amplify\.ya?ml$/,
2320
+ /^app\.ya?ml$/,
2321
+ /^Dockerfile$/,
2322
+ /^docker-compose\.ya?ml$/,
2323
+ /^compose\.ya?ml$/,
2324
+ /^Procfile$/,
2325
+ /^CNAME$/,
2326
+ /^index\.html$/,
2327
+ /^(next|astro|nuxt|svelte|vite|remix|gatsby|docusaurus|tailwind|drizzle|sentry\.(client|server|edge))\.config\.(js|cjs|mjs|ts|mts)$/,
2328
+ /^gatsby-config\.(js|ts)$/,
2329
+ /^(\.eleventy\.js|eleventy\.config\.(js|cjs|mjs))$/,
2330
+ /^hugo\.(toml|ya?ml|json)$/,
2331
+ /^_config\.ya?ml$/,
2332
+ /^instrumentation(-client)?\.(ts|js)$/,
2333
+ /^manage\.py$/,
2334
+ /^artisan$/,
2335
+ /^wp-config(-sample)?\.php$/,
2336
+ /^shopify\.app\.toml$/,
2337
+ /^app\.json$/,
2338
+ /^(requirements\.txt|pyproject\.toml|Pipfile|Gemfile|composer\.json|go\.mod|Cargo\.toml)$/,
2339
+ /^(bun\.lockb?|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$/
2340
+ ], NESTED_MARKERS = [
1555
2341
  "supabase/config.toml",
1556
2342
  "convex/schema.ts",
1557
- "sentry.client.config.ts",
1558
- "sentry.server.config.ts",
1559
- "sentry.edge.config.ts",
1560
- "instrumentation.ts"
1561
- ], EXAMPLE_ENV = [
1562
- ".env.example",
1563
- ".env.sample",
1564
- ".env.template",
1565
- ".dev.vars.example"
1566
- ];
1567
- function readJson(file) {
2343
+ "prisma/schema.prisma",
2344
+ "src/instrumentation.ts",
2345
+ "wp-content",
2346
+ ".github/workflows",
2347
+ "content/config.ts"
2348
+ ], EXAMPLE_ENV = /^(\.env\.(example|sample|template|dist|local\.example|development\.example|production\.example)|example\.env|\.dev\.vars\.example)$/;
2349
+ function readText(file2) {
1568
2350
  try {
1569
- return JSON.parse(readFileSync2(file, "utf8"));
2351
+ return readFileSync2(file2, "utf8");
1570
2352
  } catch {
1571
2353
  return null;
1572
2354
  }
1573
2355
  }
2356
+ function readJson(file2) {
2357
+ let text = readText(file2);
2358
+ if (text === null)
2359
+ return null;
2360
+ try {
2361
+ return JSON.parse(text);
2362
+ } catch {
2363
+ return null;
2364
+ }
2365
+ }
2366
+ function isDir(p2) {
2367
+ try {
2368
+ return statSync(p2).isDirectory();
2369
+ } catch {
2370
+ return !1;
2371
+ }
2372
+ }
2373
+ function listDir(dir) {
2374
+ try {
2375
+ return readdirSync(dir);
2376
+ } catch {
2377
+ return [];
2378
+ }
2379
+ }
2380
+ function isPackage(dir) {
2381
+ return [
2382
+ "package.json",
2383
+ "pyproject.toml",
2384
+ "requirements.txt",
2385
+ "Gemfile",
2386
+ "composer.json",
2387
+ "go.mod",
2388
+ "Cargo.toml"
2389
+ ].some((f) => existsSync2(path2.join(dir, f)));
2390
+ }
2391
+ function workspaceGlobs(root) {
2392
+ let globs = [], ws = readJson(path2.join(root, "package.json"))?.workspaces, list = Array.isArray(ws) ? ws : ws && typeof ws === "object" && Array.isArray(ws.packages) ? ws.packages : [];
2393
+ for (let g2 of list)
2394
+ if (typeof g2 === "string")
2395
+ globs.push(g2);
2396
+ let pnpm = readText(path2.join(root, "pnpm-workspace.yaml"));
2397
+ if (pnpm)
2398
+ for (let m2 of pnpm.matchAll(/^\s*-\s*["']?([^"'\s#]+)["']?/gm))
2399
+ globs.push(m2[1]);
2400
+ return globs.filter((g2) => !g2.startsWith("!"));
2401
+ }
2402
+ function expandGlob(root, glob) {
2403
+ let clean = glob.replace(/\/\*\*$/, "/*").replace(/\/$/, "");
2404
+ if (!clean.includes("*")) {
2405
+ let dir = path2.join(root, clean);
2406
+ return isDir(dir) ? [dir] : [];
2407
+ }
2408
+ let [base] = clean.split("*"), parent = path2.join(root, base);
2409
+ return listDir(parent).filter((e2) => !SKIP_DIRS.has(e2) && !e2.startsWith(".")).map((e2) => path2.join(parent, e2)).filter(isDir);
2410
+ }
1574
2411
  function packageDirs(root) {
1575
- let dirs = [root];
2412
+ let dirs = /* @__PURE__ */ new Set([root]);
2413
+ for (let g2 of workspaceGlobs(root))
2414
+ for (let d3 of expandGlob(root, g2))
2415
+ if (isPackage(d3))
2416
+ dirs.add(d3);
1576
2417
  for (let group of MONOREPO_DIRS) {
1577
- let g = path2.join(root, group);
1578
- if (!existsSync2(g))
2418
+ let g2 = path2.join(root, group);
2419
+ if (!isDir(g2))
1579
2420
  continue;
1580
- if (existsSync2(path2.join(g, "package.json")))
1581
- dirs.push(g);
1582
- try {
1583
- for (let entry of readdirSync(g, { withFileTypes: !0 }))
1584
- if (entry.isDirectory() && existsSync2(path2.join(g, entry.name, "package.json")))
1585
- dirs.push(path2.join(g, entry.name));
1586
- } catch {}
2421
+ if (isPackage(g2))
2422
+ dirs.add(g2);
2423
+ for (let entry of listDir(g2)) {
2424
+ let d3 = path2.join(g2, entry);
2425
+ if (!SKIP_DIRS.has(entry) && isDir(d3) && isPackage(d3))
2426
+ dirs.add(d3);
2427
+ }
2428
+ }
2429
+ return [...dirs].slice(0, MAX_PACKAGES);
2430
+ }
2431
+ function pythonRequirements(text) {
2432
+ let out = [];
2433
+ for (let line of text.split(/\r?\n/)) {
2434
+ let m2 = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line.replace(/#.*/, ""));
2435
+ if (m2 && !line.trim().startsWith("-"))
2436
+ out.push(m2[1].toLowerCase());
1587
2437
  }
1588
- return dirs;
2438
+ return out;
2439
+ }
2440
+ function pythonProjectDeps(text) {
2441
+ let out = /* @__PURE__ */ new Set;
2442
+ for (let block of text.matchAll(/(?:^|\n)\s*dependencies\s*=\s*\[([\s\S]*?)\]/g))
2443
+ for (let m2 of block[1].matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)/g))
2444
+ out.add(m2[1].toLowerCase());
2445
+ for (let table of text.matchAll(/\[(?:tool\.poetry\.(?:group\.[a-z]+\.)?dependencies|(?:dev-)?packages)\]([\s\S]*?)(?=\n\[|$)/g))
2446
+ for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*=/gm))
2447
+ out.add(m2[1].toLowerCase());
2448
+ return out.delete("python"), [...out];
2449
+ }
2450
+ function gemfileDeps(text) {
2451
+ return [...text.matchAll(/^\s*gem\s+["']([^"']+)["']/gm)].map((m2) => m2[1]);
2452
+ }
2453
+ function goModDeps(text) {
2454
+ let out = [];
2455
+ for (let m2 of text.matchAll(/^\s*(?:require\s+)?([a-z0-9.-]+\.[a-z]+\/[^\s]+)\s+v[0-9]/gm))
2456
+ out.push(m2[1]);
2457
+ return out;
2458
+ }
2459
+ function cargoDeps(text) {
2460
+ let out = [];
2461
+ for (let table of text.matchAll(/\[(?:dev-|build-)?dependencies\]([\s\S]*?)(?=\n\[|$)/g))
2462
+ for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9_-]+)\s*=/gm))
2463
+ out.push(m2[1]);
2464
+ return out;
2465
+ }
2466
+ function manifestDeps(dir) {
2467
+ let deps = [], add = (prefix, names) => {
2468
+ for (let n of names)
2469
+ deps.push(`${prefix}:${n}`);
2470
+ }, req = readText(path2.join(dir, "requirements.txt"));
2471
+ if (req)
2472
+ add("py", pythonRequirements(req));
2473
+ for (let f of ["pyproject.toml", "Pipfile"]) {
2474
+ let t = readText(path2.join(dir, f));
2475
+ if (t)
2476
+ add("py", pythonProjectDeps(t));
2477
+ }
2478
+ let gem = readText(path2.join(dir, "Gemfile"));
2479
+ if (gem)
2480
+ add("gem", gemfileDeps(gem));
2481
+ let composer = readJson(path2.join(dir, "composer.json"));
2482
+ for (let k3 of ["require", "require-dev"]) {
2483
+ let d3 = composer?.[k3];
2484
+ if (d3 && typeof d3 === "object")
2485
+ add("composer", Object.keys(d3));
2486
+ }
2487
+ let gomod = readText(path2.join(dir, "go.mod"));
2488
+ if (gomod)
2489
+ add("go", goModDeps(gomod));
2490
+ let cargo = readText(path2.join(dir, "Cargo.toml"));
2491
+ if (cargo)
2492
+ add("cargo", cargoDeps(cargo));
2493
+ return deps;
1589
2494
  }
1590
2495
  function siteFromWrangler(text) {
1591
2496
  let first = [
1592
2497
  ...text.matchAll(/(?:pattern|route)["']?\s*[:=]\s*["']([^"'*/\s]+)/g),
1593
2498
  ...text.matchAll(/custom_domain["']?\s*[:=]\s*["']([^"'*/\s]+)/g)
1594
- ].map((m) => m[1].toLowerCase()).filter((h) => h.includes(".") && !h.endsWith("workers.dev"))[0];
2499
+ ].map((m2) => m2[1].toLowerCase()).filter((h2) => h2.includes(".") && !h2.endsWith("workers.dev"))[0];
1595
2500
  if (!first)
1596
2501
  return;
1597
2502
  return `https://${first.replace(/^(app|www|api|console)\./, "")}`;
1598
2503
  }
2504
+ var NOT_A_SITE = /(^|\.)(localhost|example\.(com|org)|your-?(site|domain)\.[a-z]+|vercel\.app|netlify\.app|pages\.dev|workers\.dev|github\.com|gitlab\.com|npmjs\.com)$/;
2505
+ function publicSite(url) {
2506
+ try {
2507
+ let u2 = new URL(url.startsWith("http") ? url : `https://${url}`);
2508
+ if (!u2.hostname.includes(".") || NOT_A_SITE.test(u2.hostname))
2509
+ return;
2510
+ return `https://${u2.hostname.replace(/^www\./, "")}`;
2511
+ } catch {
2512
+ return;
2513
+ }
2514
+ }
2515
+ function siteFromConfig(text) {
2516
+ let m2 = /\b(?:site|siteUrl|baseURL|baseUrl|url)\s*[:=]\s*["'`](https?:\/\/[^"'`\s]+)["'`]/.exec(text);
2517
+ return m2 ? publicSite(m2[1]) : void 0;
2518
+ }
2519
+ function githubRepo(gitConfig) {
2520
+ let remotes = [
2521
+ ...gitConfig.matchAll(/\[remote "([^"]+)"\][^[]*?url\s*=\s*(\S+)/g)
2522
+ ].sort((a, b3) => Number(b3[1] === "origin") - Number(a[1] === "origin"));
2523
+ for (let [, , url] of remotes) {
2524
+ let m2 = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
2525
+ if (m2)
2526
+ return `${m2[1]}/${m2[2]}`;
2527
+ }
2528
+ return;
2529
+ }
2530
+ function readGitConfig(root) {
2531
+ let dotGit = path2.join(root, ".git");
2532
+ if (isDir(dotGit))
2533
+ return readText(path2.join(dotGit, "config"));
2534
+ let pointer = /gitdir:\s*(.+)/.exec(readText(dotGit) ?? "")?.[1]?.trim();
2535
+ if (!pointer)
2536
+ return null;
2537
+ let gitDir = path2.resolve(root, pointer);
2538
+ return readText(path2.join(gitDir, "config")) ?? readText(path2.join(gitDir, "..", "..", "config"));
2539
+ }
2540
+ function siteFromHtml(html) {
2541
+ let m2 = /<link[^>]+rel=["']canonical["'][^>]+href=["'](https?:\/\/[^"']+)/i.exec(html) ?? /<link[^>]+href=["'](https?:\/\/[^"']+)["'][^>]+rel=["']canonical["']/i.exec(html) ?? /<meta[^>]+property=["']og:url["'][^>]+content=["'](https?:\/\/[^"']+)/i.exec(html);
2542
+ return m2 ? publicSite(m2[1]) : void 0;
2543
+ }
1599
2544
  function envKeyNames(text) {
1600
2545
  let out = [];
1601
2546
  for (let line of text.split(/\r?\n/)) {
1602
- let m = /^\s*(?:#\s*)?(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/.exec(line);
1603
- if (m)
1604
- out.push(m[1]);
2547
+ let m2 = /^\s*(?:#\s*)?(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/.exec(line);
2548
+ if (m2)
2549
+ out.push(m2[1]);
1605
2550
  }
1606
2551
  return out;
1607
2552
  }
1608
- function scanProject(root) {
1609
- let deps = /* @__PURE__ */ new Set, files = /* @__PURE__ */ new Set, envKeys = /* @__PURE__ */ new Set, name, siteUrl;
1610
- for (let dir of packageDirs(root)) {
1611
- let rel = (f) => path2.relative(root, path2.join(dir, f)) || f, pkg = readJson(path2.join(dir, "package.json"));
2553
+ var SITE_CONFIGS = /^(astro\.config\.\w+|next-sitemap\.config\.\w+|gatsby-config\.\w+|docusaurus\.config\.\w+|hugo\.toml|config\.toml|_config\.ya?ml|nuxt\.config\.\w+)$/;
2554
+ function scanProject(root, onProgress) {
2555
+ let deps = /* @__PURE__ */ new Set, files = /* @__PURE__ */ new Set, envKeys = /* @__PURE__ */ new Set, name, siteUrl, dirs = packageDirs(root), relDir = (d3) => path2.relative(root, d3).split(path2.sep).join("/") || ".";
2556
+ onProgress?.({
2557
+ label: `Found ${dirs.length} ${dirs.length === 1 ? "package" : "packages"}`,
2558
+ detail: dirs.length > 1 ? dirs.map(relDir).join(", ") : void 0
2559
+ });
2560
+ let envFiles = 0;
2561
+ for (let dir of dirs) {
2562
+ let rel = (f) => (path2.relative(root, path2.join(dir, f)) || f).split(path2.sep).join("/"), pkg = readJson(path2.join(dir, "package.json"));
1612
2563
  if (pkg) {
1613
2564
  if (dir === root && typeof pkg.name === "string")
1614
2565
  name = pkg.name;
1615
2566
  if (!siteUrl && typeof pkg.homepage === "string")
1616
- siteUrl = pkg.homepage;
1617
- for (let k of ["dependencies", "devDependencies"]) {
1618
- let d = pkg[k];
1619
- if (d && typeof d === "object")
1620
- for (let n of Object.keys(d))
2567
+ siteUrl = publicSite(pkg.homepage);
2568
+ for (let k3 of ["dependencies", "devDependencies"]) {
2569
+ let d3 = pkg[k3];
2570
+ if (d3 && typeof d3 === "object")
2571
+ for (let n of Object.keys(d3))
1621
2572
  deps.add(n);
1622
2573
  }
1623
2574
  }
1624
- for (let f of MARKER_FILES) {
1625
- if (!existsSync2(path2.join(dir, f)))
2575
+ for (let d3 of manifestDeps(dir))
2576
+ deps.add(d3);
2577
+ for (let entry of listDir(dir)) {
2578
+ if (EXAMPLE_ENV.test(entry)) {
2579
+ let text = readText(path2.join(dir, entry));
2580
+ if (text) {
2581
+ envFiles++;
2582
+ for (let k3 of envKeyNames(text))
2583
+ envKeys.add(k3);
2584
+ }
2585
+ }
2586
+ if (!MARKERS.some((re) => re.test(entry)))
1626
2587
  continue;
1627
- if (files.add(rel(f)), f.startsWith("wrangler."))
1628
- try {
1629
- let text = readFileSync2(path2.join(dir, f), "utf8");
1630
- if (/d1_databases/.test(text))
1631
- files.add(`${rel(f)}#d1`);
1632
- if (!siteUrl)
1633
- siteUrl = siteFromWrangler(text);
1634
- } catch {}
2588
+ if (files.add(rel(entry)), entry.startsWith("wrangler.")) {
2589
+ let text = readText(path2.join(dir, entry)) ?? "";
2590
+ if (/d1_databases/.test(text))
2591
+ files.add(`${rel(entry)}#d1`);
2592
+ if (!siteUrl)
2593
+ siteUrl = siteFromWrangler(text);
2594
+ } else if (entry === "CNAME" && !siteUrl)
2595
+ siteUrl = publicSite((readText(path2.join(dir, entry)) ?? "").trim());
2596
+ }
2597
+ for (let f of NESTED_MARKERS)
2598
+ if (existsSync2(path2.join(dir, f)))
2599
+ files.add(rel(f));
2600
+ if (!siteUrl)
2601
+ for (let entry of listDir(dir)) {
2602
+ if (!SITE_CONFIGS.test(entry))
2603
+ continue;
2604
+ if (siteUrl = siteFromConfig(readText(path2.join(dir, entry)) ?? ""), siteUrl)
2605
+ break;
2606
+ }
2607
+ if (!siteUrl) {
2608
+ for (let f of ["index.html", "public/index.html", "src/index.html"])
2609
+ if (siteUrl = siteFromHtml(readText(path2.join(dir, f)) ?? ""), siteUrl)
2610
+ break;
1635
2611
  }
1636
- for (let f of EXAMPLE_ENV)
1637
- try {
1638
- for (let k of envKeyNames(readFileSync2(path2.join(dir, f), "utf8")))
1639
- envKeys.add(k);
1640
- } catch {}
1641
2612
  }
2613
+ onProgress?.({
2614
+ label: `Read ${deps.size} ${deps.size === 1 ? "dependency" : "dependencies"}`
2615
+ }), onProgress?.({
2616
+ label: `Found ${files.size} config ${files.size === 1 ? "file" : "files"}`,
2617
+ detail: [...files].filter((f) => !f.includes("#")).slice(0, 8).join(", ") || void 0
2618
+ }), onProgress?.({
2619
+ label: envFiles ? `Read ${envKeys.size} variable names from ${envFiles} example env ${envFiles === 1 ? "file" : "files"}` : "No example env files to read",
2620
+ detail: "Names only. Your real .env files are never opened."
2621
+ });
2622
+ let gitConfig = readGitConfig(root), repo = gitConfig ? githubRepo(gitConfig) : void 0;
2623
+ if (repo)
2624
+ onProgress?.({ label: `GitHub repo ${repo}` });
1642
2625
  return {
1643
2626
  name,
1644
2627
  siteUrl,
2628
+ repo,
1645
2629
  dependencies: [...deps].sort(),
1646
2630
  files: [...files].sort(),
1647
2631
  envKeys: [...envKeys].sort()
1648
2632
  };
1649
2633
  }
1650
2634
 
2635
+ // src/ui.ts
2636
+ var import_picocolors3 = __toESM(require_picocolors(), 1), MARK = [
2637
+ " ▗▖ ▗▖ ",
2638
+ " ▐▌ ▐▌ ",
2639
+ " ▐█▖▗█▌ ",
2640
+ " ▜██▛ ",
2641
+ " ▟██▙ ",
2642
+ " ▟████▙ ",
2643
+ " ▟██████▙ ",
2644
+ " ",
2645
+ " ▀▀▀▀▀▀▀▀▀▀ ",
2646
+ " ▀▀▀▀▀▀ "
2647
+ ], HORIZON = /* @__PURE__ */ new Set([8, 9]);
2648
+ function motionAllowed(argv = process.argv, env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
2649
+ return isTTY && !env.CI && !env.LOPEBASE_NO_MOTION && env.TERM !== "dumb" && !argv.includes("--no-motion");
2650
+ }
2651
+ var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
2652
+ function logoLines(opts) {
2653
+ let right = {
2654
+ 4: `${import_picocolors3.default.bold("Lope")}${import_picocolors3.default.dim("Base")}${opts.version ? import_picocolors3.default.dim(` v${opts.version}`) : ""}`,
2655
+ 5: import_picocolors3.default.dim(opts.tagline)
2656
+ };
2657
+ return MARK.map((row, i) => {
2658
+ let mark = HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row);
2659
+ return right[i] ? `${mark} ${right[i]}` : mark;
2660
+ });
2661
+ }
2662
+ async function showLogo(opts) {
2663
+ let write = opts.write ?? ((s) => process.stdout.write(s)), lines = logoLines(opts);
2664
+ if (!opts.motion) {
2665
+ write(`
2666
+ ${lines.join(`
2667
+ `)}
2668
+
2669
+ `);
2670
+ return;
2671
+ }
2672
+ let blank = MARK.map((r2) => " ".repeat(r2.length)), frame = (shown) => MARK.map((row, i) => shown.has(i) ? HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row) : blank[i]), up = `\x1B[${MARK.length}A`, onInterrupt = () => {
2673
+ write(`\x1B[?25h
2674
+ `), process.exit(130);
2675
+ };
2676
+ process.once("SIGINT", onInterrupt), write(`
2677
+ \x1B[?25l`);
2678
+ try {
2679
+ let shown = /* @__PURE__ */ new Set;
2680
+ write(`${frame(shown).join(`
2681
+ `)}
2682
+ `);
2683
+ for (let i = MARK.length - 1;i >= 0; i--)
2684
+ shown.add(i), write(`${up}\r${frame(shown).join(`
2685
+ `)}
2686
+ `), await sleep(i >= 8 ? 70 : 35);
2687
+ write(`${up}\r${lines.join(`
2688
+ `)}
2689
+
2690
+ `);
2691
+ } finally {
2692
+ process.off("SIGINT", onInterrupt), write("\x1B[?25h");
2693
+ }
2694
+ }
2695
+ async function pace(motion, ms = 90) {
2696
+ if (motion)
2697
+ await sleep(ms);
2698
+ }
2699
+ var KIND_LABEL = {
2700
+ framework: "Framework",
2701
+ language: "Language",
2702
+ hosting: "Hosting",
2703
+ database: "Data",
2704
+ email: "Email",
2705
+ billing: "Billing",
2706
+ auth: "Sign-in",
2707
+ analytics: "Analytics",
2708
+ cms: "Content"
2709
+ };
2710
+ function stackRows(detection, facts) {
2711
+ let byKind = /* @__PURE__ */ new Map;
2712
+ for (let s of detection.stack)
2713
+ byKind.set(s.kind, [...byKind.get(s.kind) ?? [], s.name]);
2714
+ let rows = [];
2715
+ for (let kind of Object.keys(KIND_LABEL)) {
2716
+ let names = byKind.get(kind);
2717
+ if (names)
2718
+ rows.push([KIND_LABEL[kind], names.join(", ")]);
2719
+ }
2720
+ if (facts.siteUrl)
2721
+ rows.push(["Site", facts.siteUrl]);
2722
+ if (facts.repo)
2723
+ rows.push(["Repo", facts.repo]);
2724
+ if (!rows.length)
2725
+ return [import_picocolors3.default.dim("Nothing recognized yet. Your agent will look closer.")];
2726
+ let width = Math.max(...rows.map(([k3]) => k3.length));
2727
+ return rows.map(([k3, v2]) => `${import_picocolors3.default.dim(k3.padEnd(width))} ${v2}`);
2728
+ }
2729
+ var PLAN = [
2730
+ {
2731
+ title: "Scan this folder",
2732
+ detail: "Reads dependency and file names only. Never your .env or code."
2733
+ },
2734
+ {
2735
+ title: "Connect this computer",
2736
+ detail: "Sign in in the browser and confirm a short code."
2737
+ },
2738
+ {
2739
+ title: "Choose what to connect",
2740
+ detail: "What your code already uses is ticked. New things are optional."
2741
+ },
2742
+ {
2743
+ title: "Hand over to your coding agent",
2744
+ detail: "It does the setup and asks you when it needs a key or a yes."
2745
+ }
2746
+ ];
2747
+ function planLines() {
2748
+ return PLAN.map((s, i) => `${import_picocolors3.default.yellow(String(i + 1))} ${import_picocolors3.default.bold(s.title)}
2749
+ ${import_picocolors3.default.dim(s.detail)}`);
2750
+ }
2751
+ function stepTitle(n) {
2752
+ return `${import_picocolors3.default.dim(`Step ${n} of ${PLAN.length}`)} ${import_picocolors3.default.bold(PLAN[n - 1].title)}`;
2753
+ }
2754
+
1651
2755
  // bin/lopebase.ts
1652
2756
  var [command = "init", ...rest] = process.argv.slice(2), HELP = `
1653
- ${import_picocolors3.default.bgYellow(import_picocolors3.default.black(" LopeBase "))} Set up LopeBase from this project's folder.
2757
+ ${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} Set up LopeBase from this project's folder.
1654
2758
 
1655
- ${import_picocolors3.default.bold("Usage")}
2759
+ ${import_picocolors4.default.bold("Usage")}
1656
2760
  npx lopebase scan, connect, and hand over to your agent
1657
2761
  npx lopebase login connect this computer (--paste for a token)
1658
2762
  npx lopebase scan [--json] show what the scan found
@@ -1661,13 +2765,24 @@ ${import_picocolors3.default.bold("Usage")}
1661
2765
  npx lopebase adapter secret print a new @lopebase/adapter signing secret
1662
2766
  npx lopebase adapter check <url> test a running adapter (reads LOPEBASE_SIGNING_SECRET)
1663
2767
 
1664
- ${import_picocolors3.default.bold("Learn more")}
1665
- ${import_picocolors3.default.cyan("https://lopebase.com")}
2768
+ ${import_picocolors4.default.bold("Options")}
2769
+ --print print the agent prompt instead of starting an agent
2770
+ --no-motion no animation (also LOPEBASE_NO_MOTION=1)
2771
+
2772
+ ${import_picocolors4.default.bold("Learn more")}
2773
+ ${import_picocolors4.default.cyan("https://lopebase.com")}
1666
2774
  Help: contact@jackalope.digital
1667
2775
  `, flag = (name) => rest.includes(`--${name}`), option = (name) => {
1668
2776
  let i = rest.indexOf(`--${name}`);
1669
2777
  return i >= 0 ? rest[i + 1] : rest.find((a) => a.startsWith(`--${name}=`))?.split("=")[1];
1670
- }, cwd = process.cwd();
2778
+ }, cwd = process.cwd(), motion = motionAllowed(), TAGLINE = "Every product, one place.";
2779
+ function version() {
2780
+ try {
2781
+ return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
2782
+ } catch {
2783
+ return;
2784
+ }
2785
+ }
1671
2786
  function bail(message) {
1672
2787
  xe(message), process.exit(1);
1673
2788
  }
@@ -1677,8 +2792,11 @@ function guard(value) {
1677
2792
  return value;
1678
2793
  }
1679
2794
  function openBrowser(url) {
1680
- let cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1681
- spawn(cmd, [url], { stdio: "ignore", detached: !0 }).unref();
2795
+ let [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
2796
+ try {
2797
+ let child = spawn2(cmd, args, { stdio: "ignore", detached: !0 });
2798
+ child.on("error", () => {}), child.unref();
2799
+ } catch {}
1682
2800
  }
1683
2801
  async function checkToken(auth) {
1684
2802
  try {
@@ -1697,40 +2815,40 @@ async function checkToken(auth) {
1697
2815
  });
1698
2816
  if (res.status === 401)
1699
2817
  return { ok: !1, message: "That token is not valid or has expired." };
1700
- let body = await res.json();
1701
- return { ok: !0, products: JSON.parse(body.result?.content?.[0]?.text ?? "[]").length };
1702
- } catch (e) {
2818
+ let body2 = await res.json();
2819
+ return { ok: !0, products: JSON.parse(body2.result?.content?.[0]?.text ?? "[]").length };
2820
+ } catch (e2) {
1703
2821
  return {
1704
2822
  ok: !1,
1705
- message: `Could not reach ${auth.url} (${String(e)}).`
2823
+ message: `Could not reach ${auth.url} (${String(e2)}).`
1706
2824
  };
1707
2825
  }
1708
2826
  }
1709
2827
  async function login() {
1710
- let url = guard(await he({
1711
- message: "Your LopeBase workspace address",
1712
- placeholder: "https://yourteam.lopebase.com",
1713
- initialValue: option("url") ?? loadAuth()?.url ?? DEFAULT_URL,
1714
- validate: (v) => /^https?:\/\/\S+$/.test(v ?? "") ? void 0 : "Starts with https://"
1715
- })).replace(/\/+$/, "");
2828
+ let known = option("url") ?? loadAuth()?.url, url = workspaceUrl(guard(await he({
2829
+ message: "Your LopeBase workspace (name or address)",
2830
+ placeholder: "yourteam or https://yourteam.lopebase.com",
2831
+ initialValue: known ?? "",
2832
+ validate: (v2) => workspaceUrl(v2 ?? "") ? void 0 : "Type your workspace name, like yourteam, or its full address"
2833
+ })));
1716
2834
  if (!flag("paste")) {
1717
2835
  let device = await startDeviceLogin(url);
1718
2836
  if (device) {
1719
2837
  Me([
1720
- `Your code: ${import_picocolors3.default.bold(import_picocolors3.default.yellow(device.userCode))}`,
2838
+ `Your code: ${import_picocolors4.default.bold(import_picocolors4.default.yellow(device.userCode))}`,
1721
2839
  "",
1722
2840
  "We opened your workspace in the browser. Sign in if asked,",
1723
2841
  "check the code matches, and press Connect."
1724
2842
  ].join(`
1725
- `), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors3.default.dim(device.verifyUrl));
2843
+ `), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors4.default.dim(device.verifyUrl));
1726
2844
  let s = Y2();
1727
2845
  s.start("Waiting for you to approve in the browser");
1728
2846
  let res = await pollDeviceLogin(url, device);
1729
2847
  if (res.ok) {
1730
- let file = saveAuth({ url: res.url, token: res.token });
1731
- return s.stop(`Connected (${res.scope}). Saved to ${import_picocolors3.default.dim(file)}`), { url: res.url, token: res.token };
2848
+ let file2 = saveAuth({ url: res.url, token: res.token });
2849
+ return s.stop(`Connected (${res.scope}). Saved to ${import_picocolors4.default.dim(file2)}`), { url: res.url, token: res.token };
1732
2850
  }
1733
- if (s.stop(import_picocolors3.default.red(res.message)), res.reason === "denied")
2851
+ if (s.stop(import_picocolors4.default.red(res.message)), res.reason === "denied")
1734
2852
  bail("Nothing was connected.");
1735
2853
  M2.warn("Falling back to pasting a token.");
1736
2854
  }
@@ -1741,47 +2859,59 @@ async function pasteLogin(url) {
1741
2859
  let settings = `${url}/settings#agent-access`;
1742
2860
  Me([
1743
2861
  "1. Sign in (or sign up) in the browser tab we open.",
1744
- `2. Under Settings, Agent access, press ${import_picocolors3.default.bold("Create agent access")}.`,
2862
+ `2. Under Settings, Agent access, press ${import_picocolors4.default.bold("Create agent access")}.`,
1745
2863
  "3. Copy the token and paste it here. It is saved on this computer only."
1746
2864
  ].join(`
1747
- `), "Get an agent token"), openBrowser(settings), M2.info(import_picocolors3.default.dim(settings));
2865
+ `), "Get an agent token"), openBrowser(settings), M2.info(import_picocolors4.default.dim(settings));
1748
2866
  for (;; ) {
1749
2867
  let token = guard(await ge({
1750
2868
  message: "Paste the agent token",
1751
- validate: (v) => v?.startsWith("lb_agent_") ? void 0 : "Tokens start with lb_agent_"
2869
+ validate: (v2) => v2?.startsWith("lb_agent_") ? void 0 : "Tokens start with lb_agent_"
1752
2870
  })).trim(), s = Y2();
1753
2871
  s.start("Checking the token");
1754
2872
  let check = await checkToken({ url, token });
1755
2873
  if (check.ok) {
1756
- let file = saveAuth({ url, token });
1757
- return s.stop(`Signed in. ${check.products} product(s) in this workspace. Saved to ${import_picocolors3.default.dim(file)}`), { url, token };
2874
+ let file2 = saveAuth({ url, token });
2875
+ return s.stop(`Signed in. ${check.products} product(s) in this workspace. Saved to ${import_picocolors4.default.dim(file2)}`), { url, token };
1758
2876
  }
1759
- s.stop(import_picocolors3.default.red(check.message ?? "That did not work."));
2877
+ s.stop(import_picocolors4.default.red(check.message ?? "That did not work."));
1760
2878
  }
1761
2879
  }
1762
- function printDetection(d) {
1763
- let stack = d.stack.map((s) => `${import_picocolors3.default.bold(s.name)} ${import_picocolors3.default.dim(s.kind)}`).join(`
1764
- `) || import_picocolors3.default.dim("Nothing recognised");
1765
- Me(stack, "Your stack");
1766
- let found = d.sources.filter((s) => s.status === "found");
2880
+ async function printDetection(d3, facts) {
2881
+ Me(stackRows(d3, facts).join(`
2882
+ `), "Your stack"), await pace(motion, 150);
2883
+ let found = d3.sources.filter((s) => s.status === "found");
1767
2884
  if (found.length)
1768
- Me(found.map((s) => `${import_picocolors3.default.green("●")} ${import_picocolors3.default.bold(s.label)} ${import_picocolors3.default.dim(s.evidence.join(", "))}`).join(`
1769
- `), "Already in your code, ready to connect");
1770
- let possible = d.sources.filter((s) => s.status === "possible");
2885
+ Me(found.map((s) => `${import_picocolors4.default.green("●")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(s.evidence.join(", "))}`).join(`
2886
+ `), "Already in your code, ready to connect"), await pace(motion, 150);
2887
+ let possible = d3.sources.filter((s) => s.status === "possible");
1771
2888
  if (possible.length)
1772
- Me(possible.map((s) => `${import_picocolors3.default.yellow("?")} ${import_picocolors3.default.bold(s.label)} ${import_picocolors3.default.dim(`only ${s.evidence.join(", ")}`)}`).join(`
1773
- `), "Maybe in use, your agent will check");
1774
- for (let gap of d.gaps)
2889
+ Me(possible.map((s) => `${import_picocolors4.default.yellow("?")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(`only ${s.evidence.join(", ")}`)}`).join(`
2890
+ `), "Maybe in use, your agent will check"), await pace(motion, 150);
2891
+ for (let gap of d3.gaps)
1775
2892
  M2.warn(gap);
1776
2893
  }
2894
+ async function scanWithProgress() {
2895
+ let s = Y2();
2896
+ s.start("Reading this folder");
2897
+ let steps = [], facts = scanProject(cwd, (step) => steps.push(step));
2898
+ await pace(motion, 400), s.stop("Read this folder");
2899
+ for (let step of steps)
2900
+ M2.success(step.detail ? `${step.label}
2901
+ ${import_picocolors4.default.dim(step.detail)}` : step.label), await pace(motion, 120);
2902
+ let detection = detectStack(facts);
2903
+ return { facts, detection };
2904
+ }
1777
2905
  function selfCommand() {
1778
2906
  let self = process.argv[1] ?? "";
1779
- return self.includes("node_modules") || !self ? ["npx", "-y", "lopebase", "mcp"] : [process.execPath, self, "mcp"];
2907
+ if (self && !self.includes("node_modules"))
2908
+ return [process.execPath, self, "mcp"];
2909
+ return process.platform === "win32" ? ["cmd", "/c", "npx", "-y", "lopebase", "mcp"] : ["npx", "-y", "lopebase", "mcp"];
1780
2910
  }
1781
2911
  function registerMcp(agent) {
1782
2912
  if (!agent.mcpAdd)
1783
2913
  return !1;
1784
- let argv = agent.mcpAdd(selfCommand()), res = spawnSync2(agent.bin, argv, {
2914
+ let argv = agent.mcpAdd(selfCommand()), res = spawnAgentSync(agent.bin, argv, {
1785
2915
  stdio: "pipe",
1786
2916
  cwd,
1787
2917
  encoding: "utf8"
@@ -1789,29 +2919,40 @@ function registerMcp(agent) {
1789
2919
  return res.status === 0 || /already exists/i.test(`${res.stderr}${res.stdout}`);
1790
2920
  }
1791
2921
  async function init() {
1792
- console.clear(), Ie(`${import_picocolors3.default.bgYellow(import_picocolors3.default.black(" LopeBase "))} set up this project`);
1793
- let s = Y2();
1794
- s.start("Scanning this folder");
1795
- let facts = scanProject(cwd), detection = detectStack(facts);
1796
- s.stop(`Scanned ${facts.dependencies.length} dependencies and ${facts.files.length} config files`), printDetection(detection);
1797
- let auth = loadAuth() ?? await login();
1798
- if (!(await checkToken(auth)).ok)
1799
- M2.warn("Your saved token no longer works. Let's get a new one."), await login();
2922
+ console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} set up this project`), Me(planLines().join(`
2923
+ `), "Here is what happens"), await pace(motion, 300), M2.step(stepTitle(1));
2924
+ let { facts, detection } = await scanWithProgress();
2925
+ await printDetection(detection, facts), M2.step(stepTitle(2));
2926
+ let auth = loadAuth();
2927
+ if (auth) {
2928
+ let s2 = Y2();
2929
+ s2.start(`Checking your connection to ${auth.url}`);
2930
+ let check = await checkToken(auth);
2931
+ if (check.ok)
2932
+ s2.stop(`Connected to ${import_picocolors4.default.bold(auth.url)}. ${check.products} ${check.products === 1 ? "product" : "products"} so far.`);
2933
+ else
2934
+ s2.stop(import_picocolors4.default.yellow("Your saved connection no longer works.")), auth = null;
2935
+ }
2936
+ auth ??= await login(), M2.step(stepTitle(3));
1800
2937
  let productName = guard(await he({
1801
2938
  message: "What is this product called?",
1802
2939
  initialValue: facts.name?.replace(/^@[^/]+\//, "") ?? ""
1803
2940
  })), chosen = guard(await fe({
1804
- message: "What should we connect? (space to toggle)",
2941
+ message: "What should we connect? (space to toggle, enter to go on)",
1805
2942
  options: detection.sources.map((src) => ({
1806
2943
  value: src.label,
1807
- label: src.status === "found" ? src.label : src.status === "possible" ? `${src.label} ${import_picocolors3.default.dim("(maybe in use, we'll check)")}` : `${src.label} ${import_picocolors3.default.dim("(not set up yet, we'll help)")}`,
2944
+ label: src.status === "found" ? src.label : src.status === "possible" ? `${src.label} ${import_picocolors4.default.dim("(maybe in use, we'll check)")}` : `${src.label} ${import_picocolors4.default.dim("(not set up yet, we'll help)")}`,
1808
2945
  hint: src.why
1809
2946
  })),
1810
- initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc").map((src) => src.label),
2947
+ initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc" || src.source === "uptime").map((src) => src.label),
1811
2948
  required: !1
1812
- })), prompt = onboardingPrompt({ facts, detection, chosen, productName }), agents = installedAgents();
2949
+ }));
2950
+ M2.step(stepTitle(4));
2951
+ let prompt = onboardingPrompt({ facts, detection, chosen, productName }), agents = installedAgents();
1813
2952
  if (!agents.length || flag("print")) {
1814
- Me(prompt, "Paste this into your coding agent"), Se(`Add the MCP server first: ${import_picocolors3.default.cyan("npx lopebase mcp")} (stdio)`);
2953
+ if (!agents.length)
2954
+ M2.info("No coding agent found on this computer (Claude Code, Codex, and others). Paste the prompt below into the one you use.");
2955
+ Me(prompt, "Paste this into your coding agent"), Se(`Add the MCP server first: ${import_picocolors4.default.cyan("npx lopebase mcp")} (stdio)`);
1815
2956
  return;
1816
2957
  }
1817
2958
  let agentId = guard(await ve({
@@ -1825,24 +2966,39 @@ async function init() {
1825
2966
  Me(prompt, "Paste this into your coding agent"), Se("Done.");
1826
2967
  return;
1827
2968
  }
1828
- let agent = agents.find((a) => a.id === agentId);
1829
- if (registerMcp(agent))
1830
- M2.success(`Added LopeBase tools to ${agent.label} for this folder.`);
2969
+ let agent = agents.find((a) => a.id === agentId), s = Y2();
2970
+ s.start(`Adding the LopeBase tools to ${agent.label}`);
2971
+ let registered = registerMcp(agent);
2972
+ if (await pace(motion, 300), registered)
2973
+ s.stop(`Added the LopeBase tools to ${agent.label} for this folder.`);
1831
2974
  else
1832
- M2.warn(`Add an MCP server to ${agent.label} that runs: npx -y lopebase mcp`);
1833
- Se(`Handing over to ${agent.label}. You stay in the loop for anything that needs you.`), spawn(agent.bin, agent.args(prompt), { stdio: "inherit", cwd }).on("exit", (code) => process.exit(code ?? 0));
2975
+ s.stop(import_picocolors4.default.yellow(`Add an MCP server to ${agent.label} that runs: npx -y lopebase mcp`));
2976
+ Me([
2977
+ `${import_picocolors4.default.bold(agent.label)} will now:`,
2978
+ ` ${import_picocolors4.default.dim("1.")} add ${import_picocolors4.default.bold(productName || "this product")} to your workspace`,
2979
+ ` ${import_picocolors4.default.dim("2.")} connect ${chosen.length ? chosen.join(", ") : "what it recommends"}`,
2980
+ ` ${import_picocolors4.default.dim("3.")} stop and ask you for any sign-in, key, or yes`,
2981
+ "",
2982
+ import_picocolors4.default.dim("You can follow along in your workspace as each one turns green.")
2983
+ ].join(`
2984
+ `), "Handing over"), Se(`Starting ${agent.label}. Press Ctrl+C any time to stop.`);
2985
+ let child = spawnAgent(agent.bin, agent.args(prompt), {
2986
+ stdio: "inherit",
2987
+ cwd
2988
+ });
2989
+ child.on("error", (e2) => bail(`Could not start ${agent.label}: ${e2.message}`)), child.on("exit", (code) => process.exit(code ?? 0));
1834
2990
  }
1835
2991
  async function main() {
1836
2992
  switch (command) {
1837
2993
  case "init":
1838
2994
  return init();
1839
2995
  case "login":
1840
- return Ie(`${import_picocolors3.default.bgYellow(import_picocolors3.default.black(" LopeBase "))} sign in`), await login(), Se("Ready. Run lopebase in any project.");
2996
+ return await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} sign in`), await login(), Se("Ready. Run lopebase in any project.");
1841
2997
  case "scan": {
1842
2998
  let facts = scanProject(cwd), detection = detectStack(facts);
1843
2999
  if (flag("json"))
1844
3000
  return console.log(JSON.stringify({ facts, detection }, null, 2));
1845
- return printDetection(detection);
3001
+ return Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} scan`), await printDetection(detection, facts), Se(`Run ${import_picocolors4.default.cyan("npx lopebase")} to connect what it found.`);
1846
3002
  }
1847
3003
  case "prompt": {
1848
3004
  let facts = scanProject(cwd), detection = detectStack(facts);
@@ -1862,9 +3018,9 @@ async function main() {
1862
3018
  baseUrl: rest[1],
1863
3019
  secret,
1864
3020
  token: secret ? void 0 : token,
1865
- log: (l) => console.log(l.startsWith("FAIL") ? import_picocolors3.default.red(l) : import_picocolors3.default.green(l))
3021
+ log: (l2) => console.log(l2.startsWith("FAIL") ? import_picocolors4.default.red(l2) : import_picocolors4.default.green(l2))
1866
3022
  });
1867
- console.log(res.failed.length ? import_picocolors3.default.red(`${res.failed.length} failed, ${res.passed} passed`) : import_picocolors3.default.green(`All ${res.passed} checks passed. Ready to connect.`)), process.exit(res.failed.length ? 1 : 0);
3023
+ console.log(res.failed.length ? import_picocolors4.default.red(`${res.failed.length} failed, ${res.passed} passed`) : import_picocolors4.default.green(`All ${res.passed} checks passed. Ready to connect.`)), process.exit(res.failed.length ? 1 : 0);
1868
3024
  }
1869
3025
  return console.log(`Usage:
1870
3026
  lopebase adapter secret print a new signing secret