lopebase 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -7
  3. package/dist/lopebase.js +1490 -296
  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";
@@ -474,6 +832,24 @@ class x {
474
832
  }
475
833
  }
476
834
  }
835
+
836
+ class dD extends x {
837
+ get cursor() {
838
+ return this.value ? 0 : 1;
839
+ }
840
+ get _value() {
841
+ return this.cursor === 0;
842
+ }
843
+ constructor(u) {
844
+ super(u, !1), this.value = !!u.initialValue, this.on("value", () => {
845
+ this.value = this._value;
846
+ }), this.on("confirm", (t) => {
847
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = t, this.state = "submit", this.close();
848
+ }), this.on("cursor", () => {
849
+ this.value = !this.value;
850
+ });
851
+ }
852
+ }
477
853
  var A;
478
854
  A = /* @__PURE__ */ new WeakMap;
479
855
  var kD = Object.defineProperty, $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[u] = t, H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t), SD = class extends x {
@@ -592,17 +968,17 @@ var V2 = ce(), u = (t, n) => V2 ? t : n, le = u("◆", "*"), L2 = u("■", "x"),
592
968
  return import_picocolors2.default.green(C);
593
969
  }
594
970
  }, 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);
971
+ 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;
972
+ 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));
973
+ let $2 = a < r2.length && l2 > 0, g2 = a < r2.length && l2 + a < r2.length;
974
+ return r2.slice(l2, l2 + a).map((p2, v2, f) => {
975
+ let j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g2;
976
+ return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v2 + l2 === n);
601
977
  });
602
978
  }, he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
603
979
  let n = `${import_picocolors2.default.gray(o)}
604
980
  ${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;
981
+ `, 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
982
  switch (this.state) {
607
983
  case "error":
608
984
  return `${n.trim()}
@@ -622,7 +998,7 @@ ${import_picocolors2.default.cyan(d2)}
622
998
  } }).prompt(), ge = (t) => new MD({ validate: t.validate, mask: t.mask ?? $e, render() {
623
999
  let n = `${import_picocolors2.default.gray(o)}
624
1000
  ${b2(this.state)} ${t.message}
625
- `, r = this.valueWithCursor, i = this.masked;
1001
+ `, r2 = this.valueWithCursor, i = this.masked;
626
1002
  switch (this.state) {
627
1003
  case "error":
628
1004
  return `${n.trim()}
@@ -635,19 +1011,36 @@ ${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(th
635
1011
  return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i ?? ""))}${i ? `
636
1012
  ${import_picocolors2.default.gray(o)}` : ""}`;
637
1013
  default:
638
- return `${n}${import_picocolors2.default.cyan(o)} ${r}
1014
+ return `${n}${import_picocolors2.default.cyan(o)} ${r2}
639
1015
  ${import_picocolors2.default.cyan(d2)}
640
1016
  `;
641
1017
  }
642
- } }).prompt();
643
- var ve = (t) => {
644
- let n = (r, i) => {
645
- let s = r.label ?? String(r.value);
1018
+ } }).prompt(), ye = (t) => {
1019
+ let n = t.active ?? "Yes", r2 = t.inactive ?? "No";
1020
+ return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? !0, render() {
1021
+ let i = `${import_picocolors2.default.gray(o)}
1022
+ ${b2(this.state)} ${t.message}
1023
+ `, s = this.value ? n : r2;
1024
+ switch (this.state) {
1025
+ case "submit":
1026
+ return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(s)}`;
1027
+ case "cancel":
1028
+ return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}
1029
+ ${import_picocolors2.default.gray(o)}`;
1030
+ default:
1031
+ return `${i}${import_picocolors2.default.cyan(o)} ${this.value ? `${import_picocolors2.default.green(k2)} ${n}` : `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.green(k2)} ${r2}`}
1032
+ ${import_picocolors2.default.cyan(d2)}
1033
+ `;
1034
+ }
1035
+ } }).prompt();
1036
+ }, ve = (t) => {
1037
+ let n = (r2, i) => {
1038
+ let s = r2.label ?? String(r2.value);
646
1039
  switch (i) {
647
1040
  case "selected":
648
1041
  return `${import_picocolors2.default.dim(s)}`;
649
1042
  case "active":
650
- return `${import_picocolors2.default.green(k2)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}`;
1043
+ return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
651
1044
  case "cancelled":
652
1045
  return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
653
1046
  default:
@@ -655,17 +1048,17 @@ var ve = (t) => {
655
1048
  }
656
1049
  };
657
1050
  return new LD({ options: t.options, initialValue: t.initialValue, render() {
658
- let r = `${import_picocolors2.default.gray(o)}
1051
+ let r2 = `${import_picocolors2.default.gray(o)}
659
1052
  ${b2(this.state)} ${t.message}
660
1053
  `;
661
1054
  switch (this.state) {
662
1055
  case "submit":
663
- return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
1056
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
664
1057
  case "cancel":
665
- return `${r}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
1058
+ return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
666
1059
  ${import_picocolors2.default.gray(o)}`;
667
1060
  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(`
1061
+ 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
1062
  ${import_picocolors2.default.cyan(o)} `)}
670
1063
  ${import_picocolors2.default.cyan(d2)}
671
1064
  `;
@@ -673,16 +1066,16 @@ ${import_picocolors2.default.cyan(d2)}
673
1066
  } }).prompt();
674
1067
  };
675
1068
  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)}`;
1069
+ let n = (r2, i) => {
1070
+ let s = r2.label ?? String(r2.value);
1071
+ 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
1072
  };
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)
1073
+ return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? !0, cursorAt: t.cursorAt, validate(r2) {
1074
+ if (this.required && r2.length === 0)
682
1075
  return `Please select at least one option.
683
1076
  ${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
1077
  }, render() {
685
- let r = `${import_picocolors2.default.gray(o)}
1078
+ let r2 = `${import_picocolors2.default.gray(o)}
686
1079
  ${b2(this.state)} ${t.message}
687
1080
  `, i = (s, c) => {
688
1081
  let a = this.value.includes(s.value);
@@ -690,23 +1083,23 @@ ${b2(this.state)} ${t.message}
690
1083
  };
691
1084
  switch (this.state) {
692
1085
  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")}`;
1086
+ 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
1087
  case "cancel": {
695
1088
  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}
1089
+ return `${r2}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
697
1090
  ${import_picocolors2.default.gray(o)}` : ""}`;
698
1091
  }
699
1092
  case "error": {
700
1093
  let s = this.error.split(`
701
1094
  `).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
702
1095
  `);
703
- return `${r + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
1096
+ return `${r2 + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
704
1097
  ${import_picocolors2.default.yellow(o)} `)}
705
1098
  ${s}
706
1099
  `;
707
1100
  }
708
1101
  default:
709
- return `${r}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
1102
+ return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
710
1103
  ${import_picocolors2.default.cyan(o)} `)}
711
1104
  ${import_picocolors2.default.cyan(d2)}
712
1105
  `;
@@ -714,13 +1107,13 @@ ${import_picocolors2.default.cyan(d2)}
714
1107
  } }).prompt();
715
1108
  };
716
1109
  var Me = (t = "", n = "") => {
717
- let r = `
1110
+ let r2 = `
718
1111
  ${t}
719
1112
  `.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(`
1113
+ `), i = S2(n).length, s = Math.max(r2.reduce((a, l2) => {
1114
+ let $2 = S2(l2);
1115
+ return $2.length > a ? $2.length : a;
1116
+ }, 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
1117
  `);
725
1118
  process.stdout.write(`${import_picocolors2.default.gray(o)}
726
1119
  ${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 +1133,13 @@ ${import_picocolors2.default.gray(d2)} ${t}
740
1133
 
741
1134
  `);
742
1135
  }, M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
743
- let r = [`${import_picocolors2.default.gray(o)}`];
1136
+ let r2 = [`${import_picocolors2.default.gray(o)}`];
744
1137
  if (t) {
745
1138
  let [i, ...s] = t.split(`
746
1139
  `);
747
- r.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
1140
+ r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
748
1141
  }
749
- process.stdout.write(`${r.join(`
1142
+ process.stdout.write(`${r2.join(`
750
1143
  `)}
751
1144
  `);
752
1145
  }, info: (t) => {
@@ -763,52 +1156,52 @@ ${import_picocolors2.default.gray(d2)} ${t}
763
1156
  M2.message(t, { symbol: import_picocolors2.default.red(K2) });
764
1157
  } }, J2 = `${import_picocolors2.default.gray(o)} `;
765
1158
  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);
1159
+ 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) => {
1160
+ let h2 = m2 > 1 ? "Something went wrong" : "Canceled";
1161
+ a && N2(h2, m2);
1162
+ }, v2 = () => p2(2), f = () => p2(1), j2 = () => {
1163
+ process.on("uncaughtExceptionMonitor", v2), process.on("unhandledRejection", v2), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p2);
771
1164
  }, 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)
1165
+ process.removeListener("uncaughtExceptionMonitor", v2), process.removeListener("unhandledRejection", v2), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p2);
1166
+ }, B2 = () => {
1167
+ if ($2 === void 0)
775
1168
  return;
776
1169
  i && process.stdout.write(`
777
1170
  `);
778
- let m = $.split(`
1171
+ let m2 = $2.split(`
779
1172
  `);
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)}
1173
+ process.stdout.write(import_sisteransi2.cursor.move(-999, m2.length - 1)), process.stdout.write(import_sisteransi2.erase.down(m2.length));
1174
+ }, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
1175
+ let h2 = (performance.now() - m2) / 1000, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
1176
+ return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
1177
+ }, H2 = (m2 = "") => {
1178
+ a = !0, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
786
1179
  `);
787
- let h = 0, w = 0;
788
- j(), c = setInterval(() => {
789
- if (i && l === $)
1180
+ let h2 = 0, w2 = 0;
1181
+ j2(), c = setInterval(() => {
1182
+ if (i && l2 === $2)
790
1183
  return;
791
- B(), $ = l;
792
- let I = import_picocolors2.default.magenta(n[h]);
1184
+ B2(), $2 = l2;
1185
+ let I2 = import_picocolors2.default.magenta(n[h2]);
793
1186
  if (i)
794
- process.stdout.write(`${I} ${l}...`);
1187
+ process.stdout.write(`${I2} ${l2}...`);
795
1188
  else if (t === "timer")
796
- process.stdout.write(`${I} ${l} ${O(g)}`);
1189
+ process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
797
1190
  else {
798
- let z = ".".repeat(Math.floor(w)).slice(0, 3);
799
- process.stdout.write(`${I} ${l}${z}`);
1191
+ let z2 = ".".repeat(Math.floor(w2)).slice(0, 3);
1192
+ process.stdout.write(`${I2} ${l2}${z2}`);
800
1193
  }
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}
1194
+ h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
1195
+ }, r2);
1196
+ }, N2 = (m2 = "", h2 = 0) => {
1197
+ a = !1, clearInterval(c), B2();
1198
+ let w2 = h2 === 0 ? import_picocolors2.default.green(C) : h2 === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
1199
+ l2 = R2(m2 ?? l2), t === "timer" ? process.stdout.write(`${w2} ${l2} ${O2(g2)}
1200
+ `) : process.stdout.write(`${w2} ${l2}
808
1201
  `), E(), s();
809
1202
  };
810
- return { start: H, stop: N, message: (m = "") => {
811
- l = R(m ?? l);
1203
+ return { start: H2, stop: N2, message: (m2 = "") => {
1204
+ l2 = R2(m2 ?? l2);
812
1205
  } };
813
1206
  };
814
1207
 
@@ -816,7 +1209,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
816
1209
  var SIGNATURE_HEADER = "lopebase-signature";
817
1210
  var enc = /* @__PURE__ */ new TextEncoder;
818
1211
  function hex(bytes) {
819
- return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
1212
+ return [...new Uint8Array(bytes)].map((b3) => b3.toString(16).padStart(2, "0")).join("");
820
1213
  }
821
1214
  async function sha256Hex(data) {
822
1215
  return hex(await crypto.subtle.digest("SHA-256", data));
@@ -827,8 +1220,8 @@ async function hmacHex(secret, message) {
827
1220
  }
828
1221
  function newSigningSecret() {
829
1222
  let bytes = crypto.getRandomValues(new Uint8Array(32)), bin = "";
830
- for (let b of bytes)
831
- bin += String.fromCharCode(b);
1223
+ for (let b3 of bytes)
1224
+ bin += String.fromCharCode(b3);
832
1225
  return "lbs_" + btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
833
1226
  }
834
1227
  function payload(t, method, pathAndQuery, bodyHash) {
@@ -865,10 +1258,11 @@ var SENSITIVE_PARTS = /* @__PURE__ */ new Set([
865
1258
  ]), SENSITIVE_JOINED = /apikey|privatekey|secretkey|accesskey|accountnumber|routingnumber|stripecustomer|paymentmethod|recoverycode/;
866
1259
  function isSensitiveFieldName(name) {
867
1260
  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)))
1261
+ if (parts.some((p2) => SENSITIVE_PARTS.has(p2)))
869
1262
  return !0;
870
1263
  return SENSITIVE_JOINED.test(parts.join(""));
871
1264
  }
1265
+ 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
1266
 
873
1267
  // ../admin-adapter/src/adapter.ts
874
1268
  var DEFAULT_BASE_PATH = "/api/lopebase";
@@ -883,12 +1277,12 @@ function conformanceChecks(opts) {
883
1277
  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
1278
  async function signedHeaders(url, method, bodyText, timestamp) {
885
1279
  if (opts.secret) {
886
- let u = new URL(url);
1280
+ let u2 = new URL(url);
887
1281
  return {
888
1282
  [SIGNATURE_HEADER]: await signRequest({
889
1283
  secret: opts.secret,
890
1284
  method,
891
- pathAndQuery: u.pathname + u.search,
1285
+ pathAndQuery: u2.pathname + u2.search,
892
1286
  body: bodyText,
893
1287
  timestamp
894
1288
  })
@@ -906,15 +1300,14 @@ function conformanceChecks(opts) {
906
1300
  ...init?.headers
907
1301
  }
908
1302
  }));
909
- }, resources = () => authed("/resources").then((r) => body(r));
910
- return [
1303
+ }, resources = () => authed("/resources").then((r2) => body(r2)), checks = [
911
1304
  {
912
1305
  name: "GET /health returns { ok } without auth",
913
1306
  async run() {
914
1307
  let res = await send(new Request(`${base}/health`));
915
1308
  assert.equal(res.status, 200);
916
- let b = await body(res);
917
- assert.equal(typeof b.ok, "boolean");
1309
+ let b3 = await body(res);
1310
+ assert.equal(typeof b3.ok, "boolean");
918
1311
  }
919
1312
  },
920
1313
  {
@@ -929,10 +1322,10 @@ function conformanceChecks(opts) {
929
1322
  async run() {
930
1323
  let res = await authed("/resources");
931
1324
  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));
1325
+ let b3 = await body(res);
1326
+ assert.ok(Array.isArray(b3.resources));
1327
+ for (let r2 of b3.resources)
1328
+ assert.ok(r2.name && r2.label && r2.readCapability), assert.ok(Array.isArray(r2.fields));
936
1329
  }
937
1330
  },
938
1331
  {
@@ -940,9 +1333,9 @@ function conformanceChecks(opts) {
940
1333
  async run() {
941
1334
  let res = await authed("/actions");
942
1335
  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)
1336
+ let b3 = await body(res);
1337
+ assert.ok(Array.isArray(b3.actions));
1338
+ for (let a of b3.actions)
946
1339
  assert.ok(a.name && a.label && a.capability), assert.ok(Array.isArray(a.params));
947
1340
  }
948
1341
  },
@@ -961,7 +1354,7 @@ function conformanceChecks(opts) {
961
1354
  {
962
1355
  name: "GET /resources/:name/:id returns a row or 404-null",
963
1356
  async run() {
964
- let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((r) => r.name === opts.sampleResource) : cat.resources[0];
1357
+ let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((r2) => r2.name === opts.sampleResource) : cat.resources[0];
965
1358
  if (!meta)
966
1359
  return;
967
1360
  let first = (await body(await authed(`/resources/${meta.name}?limit=1`))).rows[0];
@@ -976,8 +1369,8 @@ function conformanceChecks(opts) {
976
1369
  async run() {
977
1370
  let res = await authed("/audit");
978
1371
  assert.equal(res.status, 200);
979
- let b = await body(res);
980
- assert.ok(Array.isArray(b.rows));
1372
+ let b3 = await body(res);
1373
+ assert.ok(Array.isArray(b3.rows));
981
1374
  }
982
1375
  },
983
1376
  {
@@ -991,24 +1384,24 @@ function conformanceChecks(opts) {
991
1384
  name: "no field in the catalog looks like a secret unless marked safe",
992
1385
  async run() {
993
1386
  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`);
1387
+ for (let r2 of cat.resources)
1388
+ for (let f of r2.fields)
1389
+ assert.ok(!isSensitiveFieldName(f.name) || f.sensitive === "allow", `${r2.name}.${f.name} looks sensitive`);
997
1390
  }
998
1391
  },
999
1392
  {
1000
1393
  name: "rows contain only declared fields, and a page is at most 100",
1001
1394
  async run() {
1002
1395
  let cat = await resources();
1003
- for (let r of cat.resources) {
1004
- let res = await authed(`/resources/${r.name}?limit=1000`);
1396
+ for (let r2 of cat.resources) {
1397
+ let res = await authed(`/resources/${r2.name}?limit=1000`);
1005
1398
  assert.equal(res.status, 200);
1006
1399
  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)]);
1400
+ assert.ok(page.rows.length <= 100, `${r2.name} returned ${page.rows.length} rows`);
1401
+ let declared = /* @__PURE__ */ new Set(["id", ...r2.fields.map((f) => f.name)]);
1009
1402
  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}`);
1403
+ for (let k3 of Object.keys(row))
1404
+ assert.ok(declared.has(k3), `${r2.name} returned undeclared field ${k3}`);
1012
1405
  }
1013
1406
  }
1014
1407
  },
@@ -1026,7 +1419,7 @@ function conformanceChecks(opts) {
1026
1419
  {
1027
1420
  name: "a mutation writes an audit row (PATCH round-trips into /audit)",
1028
1421
  async run() {
1029
- let writable = (await resources()).resources.find((r) => r.writeCapability && r.fields.some((f) => f.editable));
1422
+ let writable = (await resources()).resources.find((r2) => r2.writeCapability && r2.fields.some((f) => f.editable));
1030
1423
  if (!writable || remote)
1031
1424
  return;
1032
1425
  let row = (await body(await authed(`/resources/${writable.name}?limit=1`))).rows[0];
@@ -1045,9 +1438,10 @@ function conformanceChecks(opts) {
1045
1438
  }
1046
1439
  }
1047
1440
  ];
1441
+ return remote ? checks.filter((c) => !c.name.startsWith("a mutation writes")) : checks;
1048
1442
  }
1049
1443
  // bin/lopebase.ts
1050
- var import_picocolors3 = __toESM(require_picocolors(), 1);
1444
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
1051
1445
 
1052
1446
  // src/adapter-check.ts
1053
1447
  async function checkAdapter(input) {
@@ -1061,8 +1455,8 @@ async function checkAdapter(input) {
1061
1455
  })
1062
1456
  } : { authorization: `Bearer ${input.token ?? ""}` };
1063
1457
  status = (await fetch(url, { headers })).status;
1064
- } catch (e) {
1065
- let error = `Could not reach ${base} (${e instanceof Error ? e.message : String(e)}).`;
1458
+ } catch (e2) {
1459
+ let error = `Could not reach ${base} (${e2 instanceof Error ? e2.message : String(e2)}).`;
1066
1460
  return log(`FAIL ${error}`), { passed, failed: [{ name: "reachable", error }] };
1067
1461
  }
1068
1462
  if (status === 401) {
@@ -1080,22 +1474,23 @@ async function checkAdapter(input) {
1080
1474
  }))
1081
1475
  try {
1082
1476
  await c.run(), passed++, log(`pass ${c.name}`);
1083
- } catch (e) {
1084
- let error = e instanceof Error ? e.message.split(`
1085
- `)[0] : String(e);
1477
+ } catch (e2) {
1478
+ let error = e2 instanceof Error ? e2.message.split(`
1479
+ `)[0] : String(e2);
1086
1480
  failed.push({ name: c.name, error }), log(`FAIL ${c.name}: ${error}`);
1087
1481
  }
1088
1482
  return { passed, failed };
1089
1483
  }
1090
1484
 
1091
1485
  // src/agents.ts
1486
+ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
1092
1487
  import { spawnSync } from "node:child_process";
1093
1488
  var AGENTS = [
1094
1489
  {
1095
1490
  id: "claude",
1096
1491
  label: "Claude Code",
1097
1492
  bin: "claude",
1098
- args: (p) => [p],
1493
+ args: (p2) => [p2],
1099
1494
  mcpAdd: (argv) => [
1100
1495
  "mcp",
1101
1496
  "add",
@@ -1110,32 +1505,48 @@ var AGENTS = [
1110
1505
  id: "codex",
1111
1506
  label: "Codex",
1112
1507
  bin: "codex",
1113
- args: (p) => [p],
1508
+ args: (p2) => [p2],
1114
1509
  mcpAdd: (argv) => ["mcp", "add", "lopebase", "--", ...argv]
1115
1510
  },
1116
1511
  {
1117
1512
  id: "gemini",
1118
1513
  label: "Gemini CLI",
1119
1514
  bin: "gemini",
1120
- args: (p) => ["-i", p],
1515
+ args: (p2) => ["-i", p2],
1121
1516
  mcpAdd: (argv) => ["mcp", "add", "lopebase", ...argv]
1122
1517
  },
1123
1518
  {
1124
1519
  id: "opencode",
1125
1520
  label: "opencode",
1126
1521
  bin: "opencode",
1127
- args: (p) => ["--prompt", p]
1522
+ args: (p2) => ["--prompt", p2]
1128
1523
  },
1129
1524
  {
1130
1525
  id: "cursor-agent",
1131
1526
  label: "Cursor Agent",
1132
1527
  bin: "cursor-agent",
1133
- args: (p) => [p]
1528
+ args: (p2) => [p2]
1134
1529
  }
1135
1530
  ];
1136
1531
  function isInstalled(bin) {
1137
- let probe = process.platform === "win32" ? "where" : "which";
1138
- return spawnSync(probe, [bin], { stdio: "ignore" }).status === 0;
1532
+ return whereIs(bin) !== null;
1533
+ }
1534
+ function whereIs(bin) {
1535
+ let probe = process.platform === "win32" ? "where" : "which", res = spawnSync(probe, [bin], { encoding: "utf8" });
1536
+ if (res.status !== 0)
1537
+ return null;
1538
+ return res.stdout.split(/\r?\n/)[0]?.trim() || null;
1539
+ }
1540
+ function portableArgs(bin, args, platform = process.platform, resolved = platform === "win32" ? whereIs(bin) : null) {
1541
+ if (platform !== "win32" || !resolved || !/\.(cmd|bat)$/i.test(resolved))
1542
+ return args;
1543
+ return args.map((a) => a.replace(/[ \t]*\r?\n\s*/g, " "));
1544
+ }
1545
+ function spawnAgent(bin, args, options) {
1546
+ return import_cross_spawn.default(bin, portableArgs(bin, args), options);
1547
+ }
1548
+ function spawnAgentSync(bin, args, options) {
1549
+ return import_cross_spawn.default.sync(bin, portableArgs(bin, args), options);
1139
1550
  }
1140
1551
  function installedAgents() {
1141
1552
  return AGENTS.filter((a) => isInstalled(a.bin));
@@ -1152,6 +1563,22 @@ import {
1152
1563
  import os from "node:os";
1153
1564
  import path from "node:path";
1154
1565
  var DEFAULT_URL = "https://app.lopebase.com";
1566
+ function workspaceUrl(input) {
1567
+ let v2 = input.trim().replace(/\/+$/, "");
1568
+ if (!v2)
1569
+ return null;
1570
+ if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(v2))
1571
+ return `https://${v2.toLowerCase()}.lopebase.com`;
1572
+ let withScheme = /^https?:\/\//i.test(v2) ? v2 : `https://${v2}`;
1573
+ try {
1574
+ let u2 = new URL(withScheme);
1575
+ if (!u2.hostname.includes(".") && u2.hostname !== "localhost")
1576
+ return null;
1577
+ return `${u2.origin}${u2.pathname.replace(/\/+$/, "")}`;
1578
+ } catch {
1579
+ return null;
1580
+ }
1581
+ }
1155
1582
  function configDir() {
1156
1583
  let base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
1157
1584
  return path.join(base, "lopebase");
@@ -1181,21 +1608,21 @@ function saveAuth(auth) {
1181
1608
  var mcpEndpoint = (url) => `${url.replace(/\/+$/, "")}/api/mcp`;
1182
1609
 
1183
1610
  // src/detect.ts
1184
- var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_)|_DSN$/;
1611
+ var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_|REACT_APP_|GATSBY_)|_DSN$/, file = (name) => new RegExp(`(^|/)${name.replace(/[.]/g, "\\.")}$`);
1185
1612
  function evidenceFor(facts, rule) {
1186
1613
  let out = [], weak = [];
1187
1614
  for (let re of rule.deps ?? []) {
1188
- let hit = facts.dependencies.find((d) => re.test(d));
1615
+ let hit = facts.dependencies.find((d3) => re.test(d3));
1189
1616
  if (hit)
1190
- out.push(`uses ${hit}`);
1617
+ out.push(`uses ${hit.replace(/^(py|gem|composer|go|cargo):/, "")}`);
1191
1618
  }
1192
1619
  for (let re of rule.files ?? []) {
1193
1620
  let hit = facts.files.find((f) => re.test(f));
1194
1621
  if (hit)
1195
- out.push(`has ${hit}`);
1622
+ out.push(`has ${hit.replace(/#.*$/, "")}`);
1196
1623
  }
1197
1624
  for (let re of rule.env ?? []) {
1198
- let hit = facts.envKeys.find((k) => re.test(k));
1625
+ let hit = facts.envKeys.find((k3) => re.test(k3));
1199
1626
  if (hit)
1200
1627
  (APP_SIDE_ENV.test(hit) ? out : weak).push(`env ${hit}`);
1201
1628
  }
@@ -1204,15 +1631,31 @@ function evidenceFor(facts, rule) {
1204
1631
  var SOURCE_RULES = {
1205
1632
  posthog: {
1206
1633
  label: "PostHog",
1207
- rule: { deps: [/^posthog-(js|node)$/], env: [/POSTHOG/] },
1634
+ rule: {
1635
+ deps: [
1636
+ /^posthog-(js|node|react-native)$/,
1637
+ /^py:posthog$/,
1638
+ /^gem:posthog-ruby$/,
1639
+ /^composer:posthog\//,
1640
+ /^go:github\.com\/posthog\//
1641
+ ],
1642
+ env: [/POSTHOG/]
1643
+ },
1208
1644
  why: "Signups, active users, and funnels on your dashboard."
1209
1645
  },
1210
1646
  ga4: {
1211
1647
  label: "Google Analytics",
1212
1648
  rule: {
1213
- deps: [/^@next\/third-parties$/, /^react-ga4$/, /^vue-gtag/],
1649
+ deps: [
1650
+ /^@next\/third-parties$/,
1651
+ /^react-ga4$/,
1652
+ /^vue-gtag/,
1653
+ /^nuxt-gtag$/,
1654
+ /^gatsby-plugin-google-(gtag|analytics)$/,
1655
+ /^@analytics\/google-analytics$/
1656
+ ],
1214
1657
  env: [
1215
- /^(NEXT_PUBLIC_|PUBLIC_|VITE_)?GA(4)?_(MEASUREMENT_)?ID$/,
1658
+ /^(NEXT_PUBLIC_|PUBLIC_|VITE_|GATSBY_|REACT_APP_)?GA(4)?_(MEASUREMENT_|TRACKING_)?ID$/,
1216
1659
  /GTM_ID/
1217
1660
  ]
1218
1661
  },
@@ -1221,7 +1664,14 @@ var SOURCE_RULES = {
1221
1664
  sentry: {
1222
1665
  label: "Sentry",
1223
1666
  rule: {
1224
- deps: [/^@sentry\//],
1667
+ deps: [
1668
+ /^@sentry\//,
1669
+ /^py:sentry-sdk$/,
1670
+ /^gem:sentry-(ruby|rails)$/,
1671
+ /^composer:sentry\//,
1672
+ /^go:github\.com\/getsentry\//,
1673
+ /^cargo:sentry$/
1674
+ ],
1225
1675
  files: [/sentry\.(client|server|edge)\.config/],
1226
1676
  env: [/SENTRY_DSN/]
1227
1677
  },
@@ -1229,80 +1679,392 @@ var SOURCE_RULES = {
1229
1679
  },
1230
1680
  stripe: {
1231
1681
  label: "Stripe",
1232
- rule: { deps: [/^stripe$/, /^@stripe\//], env: [/^STRIPE_/] },
1682
+ rule: {
1683
+ deps: [
1684
+ /^stripe$/,
1685
+ /^@stripe\//,
1686
+ /^py:stripe$/,
1687
+ /^gem:stripe$/,
1688
+ /^composer:stripe\//,
1689
+ /^go:github\.com\/stripe\//
1690
+ ],
1691
+ env: [/^STRIPE_/, /^(NEXT_PUBLIC_|PUBLIC_|VITE_)STRIPE_/]
1692
+ },
1233
1693
  why: "Revenue, new customers, and churn per product."
1694
+ },
1695
+ polar: {
1696
+ label: "Polar",
1697
+ rule: { deps: [/^@polar-sh\//, /^py:polar-sdk$/], env: [/^POLAR_/] },
1698
+ why: "Revenue and subscriptions from Polar per product."
1699
+ },
1700
+ supabase: {
1701
+ label: "Supabase",
1702
+ rule: {
1703
+ deps: [/^@supabase\//, /^py:supabase$/, /^composer:supabase\//],
1704
+ files: [file("supabase/config.toml")],
1705
+ env: [/SUPABASE_URL$/]
1706
+ },
1707
+ why: "Database health, security warnings, disk, and backups next to everything else."
1708
+ },
1709
+ resend: {
1710
+ label: "Resend",
1711
+ rule: {
1712
+ deps: [/^resend$/, /^py:resend$/, /^gem:resend$/, /^composer:resend\//],
1713
+ env: [/^RESEND_/]
1714
+ },
1715
+ why: "Bounce and complaint rates, so you know your emails land."
1716
+ },
1717
+ sequenzy: {
1718
+ label: "Sequenzy",
1719
+ rule: { deps: [/^@?sequenzy/], env: [/SEQUENZY/] },
1720
+ why: "Your audience and running email sequences at a glance."
1234
1721
  }
1235
1722
  }, STACK_RULES = [
1236
- { kind: "framework", name: "Next.js", rule: { deps: [/^next$/] } },
1237
- { kind: "framework", name: "Astro", rule: { deps: [/^astro$/] } },
1723
+ {
1724
+ kind: "framework",
1725
+ name: "Next.js",
1726
+ rule: { deps: [/^next$/], files: [/(^|\/)next\.config\./] }
1727
+ },
1728
+ {
1729
+ kind: "framework",
1730
+ name: "Astro",
1731
+ rule: { deps: [/^astro$/], files: [/(^|\/)astro\.config\./] }
1732
+ },
1238
1733
  {
1239
1734
  kind: "framework",
1240
1735
  name: "SvelteKit",
1241
1736
  rule: { deps: [/^@sveltejs\/kit$/] }
1242
1737
  },
1738
+ { kind: "framework", name: "Remix", rule: { deps: [/^@remix-run\//] } },
1243
1739
  {
1244
1740
  kind: "framework",
1245
- name: "Remix",
1246
- rule: { deps: [/^@remix-run\//, /^react-router$/] }
1741
+ name: "React Router",
1742
+ rule: { deps: [/^@react-router\/dev$/] }
1247
1743
  },
1248
- { kind: "framework", name: "Nuxt", rule: { deps: [/^nuxt$/] } },
1744
+ {
1745
+ kind: "framework",
1746
+ name: "Nuxt",
1747
+ rule: { deps: [/^nuxt$/], files: [/(^|\/)nuxt\.config\./] }
1748
+ },
1749
+ { kind: "framework", name: "Gatsby", rule: { deps: [/^gatsby$/] } },
1750
+ {
1751
+ kind: "framework",
1752
+ name: "Docusaurus",
1753
+ rule: { deps: [/^@docusaurus\/core$/] }
1754
+ },
1755
+ {
1756
+ kind: "framework",
1757
+ name: "Eleventy",
1758
+ rule: {
1759
+ deps: [/^@11ty\/eleventy$/],
1760
+ files: [/(^|\/)(\.eleventy\.js|eleventy\.config\.)/]
1761
+ }
1762
+ },
1763
+ {
1764
+ kind: "framework",
1765
+ name: "Hugo",
1766
+ rule: { files: [/(^|\/)hugo\.(toml|ya?ml|json)$/] }
1767
+ },
1768
+ { kind: "framework", name: "Jekyll", rule: { deps: [/^gem:jekyll$/] } },
1769
+ { kind: "framework", name: "Angular", rule: { deps: [/^@angular\/core$/] } },
1770
+ {
1771
+ kind: "framework",
1772
+ name: "SolidStart",
1773
+ rule: { deps: [/^@solidjs\/start$/] }
1774
+ },
1775
+ {
1776
+ kind: "framework",
1777
+ name: "Qwik",
1778
+ rule: { deps: [/^@builder\.io\/qwik-city$/] }
1779
+ },
1780
+ {
1781
+ kind: "framework",
1782
+ name: "TanStack Start",
1783
+ rule: { deps: [/^@tanstack\/(react-)?start$/] }
1784
+ },
1785
+ { kind: "framework", name: "Expo", rule: { deps: [/^expo$/] } },
1786
+ { kind: "framework", name: "Electron", rule: { deps: [/^electron$/] } },
1249
1787
  { kind: "framework", name: "Hono", rule: { deps: [/^hono$/] } },
1788
+ { kind: "framework", name: "Express", rule: { deps: [/^express$/] } },
1789
+ { kind: "framework", name: "Fastify", rule: { deps: [/^fastify$/] } },
1790
+ { kind: "framework", name: "NestJS", rule: { deps: [/^@nestjs\/core$/] } },
1791
+ {
1792
+ kind: "framework",
1793
+ name: "Django",
1794
+ rule: { deps: [/^py:django$/], files: [/(^|\/)manage\.py$/] }
1795
+ },
1796
+ { kind: "framework", name: "Flask", rule: { deps: [/^py:flask$/] } },
1797
+ { kind: "framework", name: "FastAPI", rule: { deps: [/^py:fastapi$/] } },
1798
+ { kind: "framework", name: "Rails", rule: { deps: [/^gem:rails$/] } },
1799
+ {
1800
+ kind: "framework",
1801
+ name: "Laravel",
1802
+ rule: {
1803
+ deps: [/^composer:laravel\/framework$/],
1804
+ files: [/(^|\/)artisan$/]
1805
+ }
1806
+ },
1807
+ {
1808
+ kind: "framework",
1809
+ name: "WordPress",
1810
+ rule: { files: [/(^|\/)wp-config(-sample)?\.php$/, /(^|\/)wp-content$/] }
1811
+ },
1812
+ {
1813
+ kind: "framework",
1814
+ name: "Shopify app",
1815
+ rule: { files: [/(^|\/)shopify\.app\.toml$/] }
1816
+ },
1817
+ { kind: "framework", name: "Vue", rule: { deps: [/^vue$/] } },
1818
+ { kind: "framework", name: "React", rule: { deps: [/^react$/] } },
1819
+ { kind: "framework", name: "Vite", rule: { deps: [/^vite$/] } },
1820
+ { kind: "language", name: "TypeScript", rule: { deps: [/^typescript$/] } },
1821
+ {
1822
+ kind: "language",
1823
+ name: "Python",
1824
+ rule: { files: [/(^|\/)(requirements\.txt|pyproject\.toml|Pipfile)$/] }
1825
+ },
1826
+ { kind: "language", name: "Ruby", rule: { files: [/(^|\/)Gemfile$/] } },
1827
+ { kind: "language", name: "PHP", rule: { files: [/(^|\/)composer\.json$/] } },
1828
+ { kind: "language", name: "Go", rule: { files: [/(^|\/)go\.mod$/] } },
1829
+ { kind: "language", name: "Rust", rule: { files: [/(^|\/)Cargo\.toml$/] } },
1250
1830
  {
1251
1831
  kind: "hosting",
1252
1832
  name: "Cloudflare Workers",
1253
1833
  rule: {
1254
1834
  files: [/(^|\/)wrangler\.(toml|jsonc?)$/],
1255
- deps: [/^@opennextjs\/cloudflare$/]
1835
+ deps: [
1836
+ /^@opennextjs\/cloudflare$/,
1837
+ /^@astrojs\/cloudflare$/,
1838
+ /^@sveltejs\/adapter-cloudflare/
1839
+ ]
1840
+ }
1841
+ },
1842
+ {
1843
+ kind: "hosting",
1844
+ name: "Vercel",
1845
+ rule: {
1846
+ files: [file("vercel.json")],
1847
+ deps: [
1848
+ /^@vercel\/(analytics|speed-insights|og)$/,
1849
+ /^@astrojs\/vercel$/,
1850
+ /^@sveltejs\/adapter-vercel$/
1851
+ ]
1256
1852
  }
1257
1853
  },
1258
- { kind: "hosting", name: "Vercel", rule: { files: [/(^|\/)vercel\.json$/] } },
1259
1854
  {
1260
1855
  kind: "hosting",
1261
1856
  name: "Netlify",
1262
- rule: { files: [/(^|\/)netlify\.toml$/] }
1857
+ rule: {
1858
+ files: [file("netlify.toml")],
1859
+ deps: [/^@netlify\//, /^@astrojs\/netlify$/]
1860
+ }
1263
1861
  },
1264
- { kind: "hosting", name: "Fly.io", rule: { files: [/(^|\/)fly\.toml$/] } },
1862
+ { kind: "hosting", name: "Fly.io", rule: { files: [file("fly.toml")] } },
1265
1863
  {
1266
- kind: "database",
1267
- name: "Supabase",
1268
- rule: { deps: [/^@supabase\//], files: [/(^|\/)supabase\/config\.toml$/] }
1864
+ kind: "hosting",
1865
+ name: "Render",
1866
+ rule: { files: [/(^|\/)render\.ya?ml$/] }
1867
+ },
1868
+ {
1869
+ kind: "hosting",
1870
+ name: "Railway",
1871
+ rule: { files: [/(^|\/)railway\.(json|toml)$/] }
1872
+ },
1873
+ {
1874
+ kind: "hosting",
1875
+ name: "Firebase Hosting",
1876
+ rule: { files: [file("firebase.json")] }
1877
+ },
1878
+ {
1879
+ kind: "hosting",
1880
+ name: "AWS Amplify",
1881
+ rule: { files: [/(^|\/)amplify\.ya?ml$/] }
1269
1882
  },
1883
+ { kind: "hosting", name: "Heroku", rule: { files: [file("Procfile")] } },
1884
+ { kind: "hosting", name: "GitHub Pages", rule: { files: [file("CNAME")] } },
1885
+ {
1886
+ kind: "hosting",
1887
+ name: "Docker",
1888
+ rule: { files: [file("Dockerfile"), /(^|\/)(docker-)?compose\.ya?ml$/] }
1889
+ },
1890
+ { kind: "database", name: "Supabase", rule: SOURCE_RULES.supabase.rule },
1270
1891
  {
1271
1892
  kind: "database",
1272
1893
  name: "Convex",
1273
- rule: { deps: [/^convex$/], files: [/(^|\/)convex\/schema\.ts$/] }
1894
+ rule: { deps: [/^convex$/], files: [file("convex/schema.ts")] }
1274
1895
  },
1896
+ { kind: "database", name: "Neon", rule: { deps: [/^@neondatabase\//] } },
1275
1897
  {
1276
1898
  kind: "database",
1277
- name: "Neon",
1278
- rule: { deps: [/^@neondatabase\//, /^@neon\/(env|config)$/] }
1899
+ name: "Postgres",
1900
+ rule: {
1901
+ deps: [
1902
+ /^postgres$/,
1903
+ /^pg$/,
1904
+ /^py:psycopg2?(-binary)?$/,
1905
+ /^py:asyncpg$/,
1906
+ /^gem:pg$/
1907
+ ]
1908
+ }
1279
1909
  },
1280
1910
  {
1281
1911
  kind: "database",
1282
- name: "Postgres",
1283
- rule: { deps: [/^postgres$/, /^pg$/] }
1912
+ name: "MySQL",
1913
+ rule: { deps: [/^mysql2?$/, /^py:(mysqlclient|pymysql)$/, /^gem:mysql2$/] }
1914
+ },
1915
+ {
1916
+ kind: "database",
1917
+ name: "SQLite",
1918
+ rule: { deps: [/^better-sqlite3$/, /^gem:sqlite3$/] }
1919
+ },
1920
+ {
1921
+ kind: "database",
1922
+ name: "MongoDB",
1923
+ rule: { deps: [/^mongodb$/, /^mongoose$/, /^py:pymongo$/, /^py:motor$/] }
1924
+ },
1925
+ {
1926
+ kind: "database",
1927
+ name: "Firebase",
1928
+ rule: { deps: [/^firebase(-admin)?$/, /^py:firebase-admin$/] }
1929
+ },
1930
+ {
1931
+ kind: "database",
1932
+ name: "PlanetScale",
1933
+ rule: { deps: [/^@planetscale\/database$/] }
1934
+ },
1935
+ { kind: "database", name: "Turso", rule: { deps: [/^@libsql\/client$/] } },
1936
+ {
1937
+ kind: "database",
1938
+ name: "Redis",
1939
+ rule: { deps: [/^@upstash\/redis$/, /^ioredis$/, /^redis$/, /^py:redis$/] }
1940
+ },
1941
+ {
1942
+ kind: "database",
1943
+ name: "Prisma",
1944
+ rule: {
1945
+ deps: [/^@prisma\/client$/],
1946
+ files: [file("prisma/schema.prisma")]
1947
+ }
1284
1948
  },
1285
- { kind: "database", name: "Prisma", rule: { deps: [/^@prisma\/client$/] } },
1286
1949
  { kind: "database", name: "Drizzle", rule: { deps: [/^drizzle-orm$/] } },
1287
1950
  {
1288
1951
  kind: "database",
1289
1952
  name: "Cloudflare D1",
1290
1953
  rule: { files: [/(^|\/)wrangler\.(toml|jsonc?)#d1$/] }
1291
1954
  },
1955
+ { kind: "email", name: "Resend", rule: SOURCE_RULES.resend.rule },
1956
+ { kind: "email", name: "Sequenzy", rule: SOURCE_RULES.sequenzy.rule },
1957
+ {
1958
+ kind: "email",
1959
+ name: "SendGrid",
1960
+ rule: { deps: [/^@sendgrid\/mail$/, /^py:sendgrid$/], env: [/^SENDGRID_/] }
1961
+ },
1962
+ {
1963
+ kind: "email",
1964
+ name: "Postmark",
1965
+ rule: { deps: [/^postmark$/, /^py:postmarker$/], env: [/^POSTMARK_/] }
1966
+ },
1292
1967
  {
1293
1968
  kind: "email",
1294
- name: "Resend",
1295
- rule: { deps: [/^resend$/], env: [/^RESEND_/] }
1969
+ name: "Mailgun",
1970
+ rule: { deps: [/^mailgun\.js$/], env: [/^MAILGUN_/] }
1296
1971
  },
1972
+ {
1973
+ kind: "email",
1974
+ name: "Loops",
1975
+ rule: { deps: [/^loops$/], env: [/^LOOPS_/] }
1976
+ },
1977
+ {
1978
+ kind: "email",
1979
+ name: "Amazon SES",
1980
+ rule: { deps: [/^@aws-sdk\/client-sesv?2?$/] }
1981
+ },
1982
+ { kind: "email", name: "Nodemailer", rule: { deps: [/^nodemailer$/] } },
1983
+ { kind: "billing", name: "Stripe", rule: SOURCE_RULES.stripe.rule },
1984
+ { kind: "billing", name: "Polar", rule: SOURCE_RULES.polar.rule },
1297
1985
  {
1298
1986
  kind: "billing",
1299
- name: "Polar",
1300
- rule: { deps: [/^@polar-sh\//], env: [/^POLAR_/] }
1987
+ name: "Lemon Squeezy",
1988
+ rule: { deps: [/^@lemonsqueezy\//], env: [/^LEMON_?SQUEEZY_/] }
1989
+ },
1990
+ {
1991
+ kind: "billing",
1992
+ name: "Paddle",
1993
+ rule: { deps: [/^@paddle\//], env: [/^PADDLE_/] }
1301
1994
  },
1302
1995
  { 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
- ];
1996
+ {
1997
+ kind: "auth",
1998
+ name: "WorkOS",
1999
+ rule: { deps: [/^@workos-inc\//, /^py:workos$/] }
2000
+ },
2001
+ { kind: "auth", name: "Better Auth", rule: { deps: [/^better-auth$/] } },
2002
+ {
2003
+ kind: "auth",
2004
+ name: "Auth.js",
2005
+ rule: { deps: [/^next-auth$/, /^@auth\/core$/] }
2006
+ },
2007
+ { kind: "auth", name: "Auth0", rule: { deps: [/^@auth0\//] } },
2008
+ { kind: "auth", name: "Kinde", rule: { deps: [/^@kinde-oss\//] } },
2009
+ { kind: "auth", name: "Lucia", rule: { deps: [/^lucia$/] } },
2010
+ { kind: "auth", name: "Devise", rule: { deps: [/^gem:devise$/] } },
2011
+ {
2012
+ kind: "analytics",
2013
+ name: "Vercel Analytics",
2014
+ rule: { deps: [/^@vercel\/analytics$/] }
2015
+ },
2016
+ {
2017
+ kind: "analytics",
2018
+ name: "Plausible",
2019
+ rule: { deps: [/plausible/], env: [/PLAUSIBLE/] }
2020
+ },
2021
+ { kind: "analytics", name: "Umami", rule: { env: [/UMAMI/] } },
2022
+ { kind: "analytics", name: "Fathom", rule: { deps: [/^fathom-client$/] } },
2023
+ {
2024
+ kind: "analytics",
2025
+ name: "Mixpanel",
2026
+ rule: { deps: [/^mixpanel(-browser)?$/] }
2027
+ },
2028
+ { kind: "analytics", name: "Amplitude", rule: { deps: [/^@amplitude\//] } },
2029
+ {
2030
+ kind: "cms",
2031
+ name: "Sanity",
2032
+ rule: { deps: [/^(next-)?sanity$/, /^@sanity\/client$/] }
2033
+ },
2034
+ { kind: "cms", name: "Contentful", rule: { deps: [/^contentful$/] } },
2035
+ { kind: "cms", name: "Payload", rule: { deps: [/^payload$/] } },
2036
+ { kind: "cms", name: "Strapi", rule: { deps: [/^@strapi\/strapi$/] } },
2037
+ { kind: "cms", name: "Keystatic", rule: { deps: [/^@keystatic\/core$/] } },
2038
+ {
2039
+ kind: "cms",
2040
+ name: "MDX",
2041
+ rule: { deps: [/^@next\/mdx$/, /^@astrojs\/mdx$/, /^@mdx-js\//] }
2042
+ }
2043
+ ], WEB_FRAMEWORKS = /* @__PURE__ */ new Set([
2044
+ "Next.js",
2045
+ "Astro",
2046
+ "SvelteKit",
2047
+ "Remix",
2048
+ "React Router",
2049
+ "Nuxt",
2050
+ "Gatsby",
2051
+ "Docusaurus",
2052
+ "Eleventy",
2053
+ "Hugo",
2054
+ "Jekyll",
2055
+ "Angular",
2056
+ "SolidStart",
2057
+ "Qwik",
2058
+ "TanStack Start",
2059
+ "Django",
2060
+ "Flask",
2061
+ "Rails",
2062
+ "Laravel",
2063
+ "WordPress",
2064
+ "Vue",
2065
+ "React",
2066
+ "Vite"
2067
+ ]);
1306
2068
  function detectStack(facts) {
1307
2069
  let stack = [];
1308
2070
  for (let s of STACK_RULES) {
@@ -1310,7 +2072,13 @@ function detectStack(facts) {
1310
2072
  if (ev.length)
1311
2073
  stack.push({ kind: s.kind, name: s.name, evidence: ev[0] });
1312
2074
  }
1313
- let sources = [];
2075
+ if (!stack.some((s) => s.kind === "framework") && facts.files.some((f) => /(^|\/)index\.html$/.test(f)))
2076
+ stack.unshift({
2077
+ kind: "framework",
2078
+ name: "Static site",
2079
+ evidence: "has index.html"
2080
+ });
2081
+ 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
2082
  for (let [id, s] of Object.entries(SOURCE_RULES)) {
1315
2083
  let { strong, weak } = evidenceFor(facts, s.rule);
1316
2084
  if (strong.length || weak.length)
@@ -1322,7 +2090,15 @@ function detectStack(facts) {
1322
2090
  why: s.why
1323
2091
  });
1324
2092
  }
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 = [];
2093
+ if (facts.repo)
2094
+ sources.push({
2095
+ source: "github",
2096
+ label: "GitHub",
2097
+ status: "found",
2098
+ evidence: [`repo ${facts.repo}`],
2099
+ why: "Open pull requests, issues, and failing Actions runs."
2100
+ });
2101
+ 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
2102
  if (isWebsite)
1327
2103
  sources.push({
1328
2104
  source: "gsc",
@@ -1336,9 +2112,15 @@ function detectStack(facts) {
1336
2112
  status: "suggested",
1337
2113
  evidence: [],
1338
2114
  why: "Bing and ChatGPT search traffic. Imports from Search Console in one click."
2115
+ }, {
2116
+ source: "uptime",
2117
+ label: "Uptime check",
2118
+ status: "suggested",
2119
+ evidence: [],
2120
+ why: "Know the moment the site goes down. Nothing to install."
1339
2121
  });
1340
- if (!has("posthog") && !has("ga4")) {
1341
- if (gaps.push("No product analytics found. Add PostHog (free tier, one snippet) so LopeBase can show signups and active users."), !listed("posthog"))
2122
+ if (isWebsite && !has("posthog") && !has("ga4")) {
2123
+ if (gaps.push("No product analytics found."), !listed("posthog"))
1342
2124
  sources.push({
1343
2125
  source: "posthog",
1344
2126
  label: "PostHog",
@@ -1348,7 +2130,7 @@ function detectStack(facts) {
1348
2130
  });
1349
2131
  }
1350
2132
  if (!has("sentry")) {
1351
- if (gaps.push("No error tracking found. Sentry lets LopeBase flag a spike in errors after a deploy."), !listed("sentry"))
2133
+ if (gaps.push("No error logging found."), !listed("sentry"))
1352
2134
  sources.push({
1353
2135
  source: "sentry",
1354
2136
  label: "Sentry",
@@ -1357,7 +2139,7 @@ function detectStack(facts) {
1357
2139
  why: SOURCE_RULES.sentry.why
1358
2140
  });
1359
2141
  }
1360
- return { stack, sources, gaps };
2142
+ return { stack: tidy, sources, gaps };
1361
2143
  }
1362
2144
 
1363
2145
  // src/device-login.ts
@@ -1374,27 +2156,27 @@ async function startDeviceLogin(url, opts = {}) {
1374
2156
  });
1375
2157
  if (!res.ok)
1376
2158
  return null;
1377
- let b = await res.json();
1378
- if (typeof b.device_code !== "string" || typeof b.user_code !== "string")
2159
+ let b3 = await res.json();
2160
+ if (typeof b3.device_code !== "string" || typeof b3.user_code !== "string")
1379
2161
  return null;
1380
2162
  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
2163
+ deviceCode: b3.device_code,
2164
+ userCode: b3.user_code,
2165
+ verifyUrl: String(b3.verification_uri_complete ?? b3.verification_uri),
2166
+ interval: Number(b3.interval) || 5,
2167
+ expiresIn: Number(b3.expires_in) || 600
1386
2168
  };
1387
2169
  } catch {
1388
2170
  return null;
1389
2171
  }
1390
2172
  }
1391
2173
  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;
2174
+ 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
2175
  while (Date.now() < deadline) {
1394
2176
  await sleep(interval * 1000);
1395
- let body;
2177
+ let body2;
1396
2178
  try {
1397
- body = await (await f(`${url}/api/oauth/token`, {
2179
+ body2 = await (await f(`${url}/api/oauth/token`, {
1398
2180
  method: "POST",
1399
2181
  headers: { "content-type": "application/x-www-form-urlencoded" },
1400
2182
  body: new URLSearchParams({
@@ -1405,14 +2187,14 @@ async function pollDeviceLogin(url, start, opts = {}) {
1405
2187
  } catch {
1406
2188
  continue;
1407
2189
  }
1408
- if (typeof body.access_token === "string")
2190
+ if (typeof body2.access_token === "string")
1409
2191
  return {
1410
2192
  ok: !0,
1411
- token: body.access_token,
1412
- url: typeof body.workspace_url === "string" ? body.workspace_url : url,
1413
- scope: String(body.scope ?? "")
2193
+ token: body2.access_token,
2194
+ url: typeof body2.workspace_url === "string" ? body2.workspace_url : url,
2195
+ scope: String(body2.scope ?? "")
1414
2196
  };
1415
- switch (body.error) {
2197
+ switch (body2.error) {
1416
2198
  case "authorization_pending":
1417
2199
  continue;
1418
2200
  case "slow_down":
@@ -1434,7 +2216,7 @@ async function pollDeviceLogin(url, start, opts = {}) {
1434
2216
  return {
1435
2217
  ok: !1,
1436
2218
  reason: "error",
1437
- message: String(body.error_description ?? body.error ?? "Sign-in failed.")
2219
+ message: String(body2.error_description ?? body2.error ?? "Sign-in failed.")
1438
2220
  };
1439
2221
  }
1440
2222
  }
@@ -1503,7 +2285,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
1503
2285
  }
1504
2286
  process.stdout.write(`${text}
1505
2287
  `);
1506
- } catch (e) {
2288
+ } catch (e2) {
1507
2289
  if (id === null)
1508
2290
  return;
1509
2291
  write({
@@ -1511,7 +2293,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
1511
2293
  id,
1512
2294
  error: {
1513
2295
  code: -32000,
1514
- message: `Could not reach LopeBase at ${endpoint}: ${String(e)}`
2296
+ message: `Could not reach LopeBase at ${endpoint}: ${String(e2)}`
1515
2297
  }
1516
2298
  });
1517
2299
  }
@@ -1543,116 +2325,471 @@ function onboardingPrompt(input) {
1543
2325
  }
1544
2326
 
1545
2327
  // src/scan.ts
1546
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
2328
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
1547
2329
  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",
2330
+ var MONOREPO_DIRS = ["apps", "packages", "services", "sites", "web", "site"], SKIP_DIRS = /* @__PURE__ */ new Set([
2331
+ "node_modules",
2332
+ ".git",
2333
+ ".next",
2334
+ ".nuxt",
2335
+ ".svelte-kit",
2336
+ ".turbo",
2337
+ ".vercel",
2338
+ ".wrangler",
2339
+ "dist",
2340
+ "build",
2341
+ "out",
2342
+ "vendor",
2343
+ "venv",
2344
+ ".venv",
2345
+ "target"
2346
+ ]), MAX_PACKAGES = 200, MARKERS = [
2347
+ /^wrangler\.(toml|jsonc?)$/,
2348
+ /^vercel\.json$/,
2349
+ /^netlify\.toml$/,
2350
+ /^fly\.toml$/,
2351
+ /^render\.ya?ml$/,
2352
+ /^railway\.(json|toml)$/,
2353
+ /^firebase\.json$/,
2354
+ /^amplify\.ya?ml$/,
2355
+ /^app\.ya?ml$/,
2356
+ /^Dockerfile$/,
2357
+ /^docker-compose\.ya?ml$/,
2358
+ /^compose\.ya?ml$/,
2359
+ /^Procfile$/,
2360
+ /^CNAME$/,
2361
+ /^index\.html$/,
2362
+ /^(next|astro|nuxt|svelte|vite|remix|gatsby|docusaurus|tailwind|drizzle|sentry\.(client|server|edge))\.config\.(js|cjs|mjs|ts|mts)$/,
2363
+ /^gatsby-config\.(js|ts)$/,
2364
+ /^(\.eleventy\.js|eleventy\.config\.(js|cjs|mjs))$/,
2365
+ /^hugo\.(toml|ya?ml|json)$/,
2366
+ /^_config\.ya?ml$/,
2367
+ /^instrumentation(-client)?\.(ts|js)$/,
2368
+ /^manage\.py$/,
2369
+ /^artisan$/,
2370
+ /^wp-config(-sample)?\.php$/,
2371
+ /^shopify\.app\.toml$/,
2372
+ /^app\.json$/,
2373
+ /^(requirements\.txt|pyproject\.toml|Pipfile|Gemfile|composer\.json|go\.mod|Cargo\.toml)$/,
2374
+ /^(bun\.lockb?|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$/
2375
+ ], NESTED_MARKERS = [
1555
2376
  "supabase/config.toml",
1556
2377
  "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) {
2378
+ "prisma/schema.prisma",
2379
+ "src/instrumentation.ts",
2380
+ "wp-content",
2381
+ ".github/workflows",
2382
+ "content/config.ts"
2383
+ ], EXAMPLE_ENV = /^(\.env\.(example|sample|template|dist|local\.example|development\.example|production\.example)|example\.env|\.dev\.vars\.example)$/;
2384
+ function readText(file2) {
2385
+ try {
2386
+ return readFileSync2(file2, "utf8");
2387
+ } catch {
2388
+ return null;
2389
+ }
2390
+ }
2391
+ function readJson(file2) {
2392
+ let text = readText(file2);
2393
+ if (text === null)
2394
+ return null;
1568
2395
  try {
1569
- return JSON.parse(readFileSync2(file, "utf8"));
2396
+ return JSON.parse(text);
1570
2397
  } catch {
1571
2398
  return null;
1572
2399
  }
1573
2400
  }
2401
+ function isDir(p2) {
2402
+ try {
2403
+ return statSync(p2).isDirectory();
2404
+ } catch {
2405
+ return !1;
2406
+ }
2407
+ }
2408
+ function listDir(dir) {
2409
+ try {
2410
+ return readdirSync(dir);
2411
+ } catch {
2412
+ return [];
2413
+ }
2414
+ }
2415
+ function isPackage(dir) {
2416
+ return [
2417
+ "package.json",
2418
+ "pyproject.toml",
2419
+ "requirements.txt",
2420
+ "Gemfile",
2421
+ "composer.json",
2422
+ "go.mod",
2423
+ "Cargo.toml"
2424
+ ].some((f) => existsSync2(path2.join(dir, f)));
2425
+ }
2426
+ function workspaceGlobs(root) {
2427
+ 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 : [];
2428
+ for (let g2 of list)
2429
+ if (typeof g2 === "string")
2430
+ globs.push(g2);
2431
+ let pnpm = readText(path2.join(root, "pnpm-workspace.yaml"));
2432
+ if (pnpm)
2433
+ for (let m2 of pnpm.matchAll(/^\s*-\s*["']?([^"'\s#]+)["']?/gm))
2434
+ globs.push(m2[1]);
2435
+ return globs.filter((g2) => !g2.startsWith("!"));
2436
+ }
2437
+ function expandGlob(root, glob) {
2438
+ let clean = glob.replace(/\/\*\*$/, "/*").replace(/\/$/, "");
2439
+ if (!clean.includes("*")) {
2440
+ let dir = path2.join(root, clean);
2441
+ return isDir(dir) ? [dir] : [];
2442
+ }
2443
+ let [base] = clean.split("*"), parent = path2.join(root, base);
2444
+ return listDir(parent).filter((e2) => !SKIP_DIRS.has(e2) && !e2.startsWith(".")).map((e2) => path2.join(parent, e2)).filter(isDir);
2445
+ }
1574
2446
  function packageDirs(root) {
1575
- let dirs = [root];
2447
+ let dirs = /* @__PURE__ */ new Set([root]);
2448
+ for (let g2 of workspaceGlobs(root))
2449
+ for (let d3 of expandGlob(root, g2))
2450
+ if (isPackage(d3))
2451
+ dirs.add(d3);
1576
2452
  for (let group of MONOREPO_DIRS) {
1577
- let g = path2.join(root, group);
1578
- if (!existsSync2(g))
2453
+ let g2 = path2.join(root, group);
2454
+ if (!isDir(g2))
1579
2455
  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 {}
2456
+ if (isPackage(g2))
2457
+ dirs.add(g2);
2458
+ for (let entry of listDir(g2)) {
2459
+ let d3 = path2.join(g2, entry);
2460
+ if (!SKIP_DIRS.has(entry) && isDir(d3) && isPackage(d3))
2461
+ dirs.add(d3);
2462
+ }
2463
+ }
2464
+ return [...dirs].slice(0, MAX_PACKAGES);
2465
+ }
2466
+ function pythonRequirements(text) {
2467
+ let out = [];
2468
+ for (let line of text.split(/\r?\n/)) {
2469
+ let m2 = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line.replace(/#.*/, ""));
2470
+ if (m2 && !line.trim().startsWith("-"))
2471
+ out.push(m2[1].toLowerCase());
1587
2472
  }
1588
- return dirs;
2473
+ return out;
2474
+ }
2475
+ function pythonProjectDeps(text) {
2476
+ let out = /* @__PURE__ */ new Set;
2477
+ for (let block of text.matchAll(/(?:^|\n)\s*dependencies\s*=\s*\[([\s\S]*?)\]/g))
2478
+ for (let m2 of block[1].matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)/g))
2479
+ out.add(m2[1].toLowerCase());
2480
+ for (let table of text.matchAll(/\[(?:tool\.poetry\.(?:group\.[a-z]+\.)?dependencies|(?:dev-)?packages)\]([\s\S]*?)(?=\n\[|$)/g))
2481
+ for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*=/gm))
2482
+ out.add(m2[1].toLowerCase());
2483
+ return out.delete("python"), [...out];
2484
+ }
2485
+ function gemfileDeps(text) {
2486
+ return [...text.matchAll(/^\s*gem\s+["']([^"']+)["']/gm)].map((m2) => m2[1]);
2487
+ }
2488
+ function goModDeps(text) {
2489
+ let out = [];
2490
+ for (let m2 of text.matchAll(/^\s*(?:require\s+)?([a-z0-9.-]+\.[a-z]+\/[^\s]+)\s+v[0-9]/gm))
2491
+ out.push(m2[1]);
2492
+ return out;
2493
+ }
2494
+ function cargoDeps(text) {
2495
+ let out = [];
2496
+ for (let table of text.matchAll(/\[(?:dev-|build-)?dependencies\]([\s\S]*?)(?=\n\[|$)/g))
2497
+ for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9_-]+)\s*=/gm))
2498
+ out.push(m2[1]);
2499
+ return out;
2500
+ }
2501
+ function manifestDeps(dir) {
2502
+ let deps = [], add = (prefix, names) => {
2503
+ for (let n of names)
2504
+ deps.push(`${prefix}:${n}`);
2505
+ }, req = readText(path2.join(dir, "requirements.txt"));
2506
+ if (req)
2507
+ add("py", pythonRequirements(req));
2508
+ for (let f of ["pyproject.toml", "Pipfile"]) {
2509
+ let t = readText(path2.join(dir, f));
2510
+ if (t)
2511
+ add("py", pythonProjectDeps(t));
2512
+ }
2513
+ let gem = readText(path2.join(dir, "Gemfile"));
2514
+ if (gem)
2515
+ add("gem", gemfileDeps(gem));
2516
+ let composer = readJson(path2.join(dir, "composer.json"));
2517
+ for (let k3 of ["require", "require-dev"]) {
2518
+ let d3 = composer?.[k3];
2519
+ if (d3 && typeof d3 === "object")
2520
+ add("composer", Object.keys(d3));
2521
+ }
2522
+ let gomod = readText(path2.join(dir, "go.mod"));
2523
+ if (gomod)
2524
+ add("go", goModDeps(gomod));
2525
+ let cargo = readText(path2.join(dir, "Cargo.toml"));
2526
+ if (cargo)
2527
+ add("cargo", cargoDeps(cargo));
2528
+ return deps;
1589
2529
  }
1590
2530
  function siteFromWrangler(text) {
1591
2531
  let first = [
1592
2532
  ...text.matchAll(/(?:pattern|route)["']?\s*[:=]\s*["']([^"'*/\s]+)/g),
1593
2533
  ...text.matchAll(/custom_domain["']?\s*[:=]\s*["']([^"'*/\s]+)/g)
1594
- ].map((m) => m[1].toLowerCase()).filter((h) => h.includes(".") && !h.endsWith("workers.dev"))[0];
2534
+ ].map((m2) => m2[1].toLowerCase()).filter((h2) => h2.includes(".") && !h2.endsWith("workers.dev"))[0];
1595
2535
  if (!first)
1596
2536
  return;
1597
2537
  return `https://${first.replace(/^(app|www|api|console)\./, "")}`;
1598
2538
  }
2539
+ 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)$/;
2540
+ function publicSite(url) {
2541
+ try {
2542
+ let u2 = new URL(url.startsWith("http") ? url : `https://${url}`);
2543
+ if (!u2.hostname.includes(".") || NOT_A_SITE.test(u2.hostname))
2544
+ return;
2545
+ return `https://${u2.hostname.replace(/^www\./, "")}`;
2546
+ } catch {
2547
+ return;
2548
+ }
2549
+ }
2550
+ function siteFromConfig(text) {
2551
+ let m2 = /\b(?:site|siteUrl|baseURL|baseUrl|url)\s*[:=]\s*["'`](https?:\/\/[^"'`\s]+)["'`]/.exec(text);
2552
+ return m2 ? publicSite(m2[1]) : void 0;
2553
+ }
2554
+ function githubRepo(gitConfig) {
2555
+ let remotes = [
2556
+ ...gitConfig.matchAll(/\[remote "([^"]+)"\][^[]*?url\s*=\s*(\S+)/g)
2557
+ ].sort((a, b3) => Number(b3[1] === "origin") - Number(a[1] === "origin"));
2558
+ for (let [, , url] of remotes) {
2559
+ let m2 = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
2560
+ if (m2)
2561
+ return `${m2[1]}/${m2[2]}`;
2562
+ }
2563
+ return;
2564
+ }
2565
+ function readGitConfig(root) {
2566
+ let dotGit = path2.join(root, ".git");
2567
+ if (isDir(dotGit))
2568
+ return readText(path2.join(dotGit, "config"));
2569
+ let pointer = /gitdir:\s*(.+)/.exec(readText(dotGit) ?? "")?.[1]?.trim();
2570
+ if (!pointer)
2571
+ return null;
2572
+ let gitDir = path2.resolve(root, pointer);
2573
+ return readText(path2.join(gitDir, "config")) ?? readText(path2.join(gitDir, "..", "..", "config"));
2574
+ }
2575
+ function siteFromHtml(html) {
2576
+ 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);
2577
+ return m2 ? publicSite(m2[1]) : void 0;
2578
+ }
1599
2579
  function envKeyNames(text) {
1600
2580
  let out = [];
1601
2581
  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]);
2582
+ let m2 = /^\s*(?:#\s*)?(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/.exec(line);
2583
+ if (m2)
2584
+ out.push(m2[1]);
1605
2585
  }
1606
2586
  return out;
1607
2587
  }
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"));
2588
+ 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+)$/;
2589
+ function scanProject(root, onProgress) {
2590
+ 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("/") || ".";
2591
+ onProgress?.({
2592
+ label: `Found ${dirs.length} ${dirs.length === 1 ? "package" : "packages"}`,
2593
+ detail: dirs.length > 1 ? dirs.map(relDir).join(", ") : void 0
2594
+ });
2595
+ let envFiles = 0;
2596
+ for (let dir of dirs) {
2597
+ let rel = (f) => (path2.relative(root, path2.join(dir, f)) || f).split(path2.sep).join("/"), pkg = readJson(path2.join(dir, "package.json"));
1612
2598
  if (pkg) {
1613
2599
  if (dir === root && typeof pkg.name === "string")
1614
2600
  name = pkg.name;
1615
2601
  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))
2602
+ siteUrl = publicSite(pkg.homepage);
2603
+ for (let k3 of ["dependencies", "devDependencies"]) {
2604
+ let d3 = pkg[k3];
2605
+ if (d3 && typeof d3 === "object")
2606
+ for (let n of Object.keys(d3))
1621
2607
  deps.add(n);
1622
2608
  }
1623
2609
  }
1624
- for (let f of MARKER_FILES) {
1625
- if (!existsSync2(path2.join(dir, f)))
2610
+ for (let d3 of manifestDeps(dir))
2611
+ deps.add(d3);
2612
+ for (let entry of listDir(dir)) {
2613
+ if (EXAMPLE_ENV.test(entry)) {
2614
+ let text = readText(path2.join(dir, entry));
2615
+ if (text) {
2616
+ envFiles++;
2617
+ for (let k3 of envKeyNames(text))
2618
+ envKeys.add(k3);
2619
+ }
2620
+ }
2621
+ if (!MARKERS.some((re) => re.test(entry)))
1626
2622
  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 {}
2623
+ if (files.add(rel(entry)), entry.startsWith("wrangler.")) {
2624
+ let text = readText(path2.join(dir, entry)) ?? "";
2625
+ if (/d1_databases/.test(text))
2626
+ files.add(`${rel(entry)}#d1`);
2627
+ if (!siteUrl)
2628
+ siteUrl = siteFromWrangler(text);
2629
+ } else if (entry === "CNAME" && !siteUrl)
2630
+ siteUrl = publicSite((readText(path2.join(dir, entry)) ?? "").trim());
2631
+ }
2632
+ for (let f of NESTED_MARKERS)
2633
+ if (existsSync2(path2.join(dir, f)))
2634
+ files.add(rel(f));
2635
+ if (!siteUrl)
2636
+ for (let entry of listDir(dir)) {
2637
+ if (!SITE_CONFIGS.test(entry))
2638
+ continue;
2639
+ if (siteUrl = siteFromConfig(readText(path2.join(dir, entry)) ?? ""), siteUrl)
2640
+ break;
2641
+ }
2642
+ if (!siteUrl) {
2643
+ for (let f of ["index.html", "public/index.html", "src/index.html"])
2644
+ if (siteUrl = siteFromHtml(readText(path2.join(dir, f)) ?? ""), siteUrl)
2645
+ break;
1635
2646
  }
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
2647
  }
2648
+ onProgress?.({
2649
+ label: `Read ${deps.size} ${deps.size === 1 ? "dependency" : "dependencies"}`
2650
+ }), onProgress?.({
2651
+ label: `Found ${files.size} config ${files.size === 1 ? "file" : "files"}`,
2652
+ detail: [...files].filter((f) => !f.includes("#")).slice(0, 8).join(", ") || void 0
2653
+ }), onProgress?.({
2654
+ label: envFiles ? `Read ${envKeys.size} variable names from ${envFiles} example env ${envFiles === 1 ? "file" : "files"}` : "No example env files to read",
2655
+ detail: "Names only. Your real .env files are never opened."
2656
+ });
2657
+ let gitConfig = readGitConfig(root), repo = gitConfig ? githubRepo(gitConfig) : void 0;
2658
+ if (repo)
2659
+ onProgress?.({ label: `GitHub repo ${repo}` });
1642
2660
  return {
1643
2661
  name,
1644
2662
  siteUrl,
2663
+ repo,
1645
2664
  dependencies: [...deps].sort(),
1646
2665
  files: [...files].sort(),
1647
2666
  envKeys: [...envKeys].sort()
1648
2667
  };
1649
2668
  }
1650
2669
 
2670
+ // src/ui.ts
2671
+ var import_picocolors3 = __toESM(require_picocolors(), 1), MARK = [
2672
+ " ▗▖ ▗▖ ",
2673
+ " ▐█▖▗█▌ ",
2674
+ " ▜██▛ ",
2675
+ " ▟██▙ ",
2676
+ " ▟████▙ ",
2677
+ " ▟██████▙ ",
2678
+ " ▄▄▄▄▄▄▄▄▄▄ ",
2679
+ " ▄▄▄▄▄▄ "
2680
+ ], HORIZON = /* @__PURE__ */ new Set([6, 7]);
2681
+ function motionAllowed(argv = process.argv, env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
2682
+ return isTTY && !env.CI && !env.LOPEBASE_NO_MOTION && env.TERM !== "dumb" && !argv.includes("--no-motion");
2683
+ }
2684
+ var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
2685
+ function logoLines(opts) {
2686
+ let right = {
2687
+ 3: `${import_picocolors3.default.bold("Lope")}${import_picocolors3.default.dim("Base")}${opts.version ? import_picocolors3.default.dim(` v${opts.version}`) : ""}`,
2688
+ 4: import_picocolors3.default.dim(opts.tagline)
2689
+ };
2690
+ return MARK.map((row, i) => {
2691
+ let mark = HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row);
2692
+ return right[i] ? `${mark} ${right[i]}` : mark;
2693
+ });
2694
+ }
2695
+ async function showLogo(opts) {
2696
+ let write = opts.write ?? ((s) => process.stdout.write(s)), lines = logoLines(opts);
2697
+ if (!opts.motion) {
2698
+ write(`
2699
+ ${lines.join(`
2700
+ `)}
2701
+
2702
+ `);
2703
+ return;
2704
+ }
2705
+ 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 = () => {
2706
+ write(`\x1B[?25h
2707
+ `), process.exit(130);
2708
+ };
2709
+ process.once("SIGINT", onInterrupt), write(`
2710
+ \x1B[?25l`);
2711
+ try {
2712
+ let shown = /* @__PURE__ */ new Set;
2713
+ write(`${frame(shown).join(`
2714
+ `)}
2715
+ `);
2716
+ for (let i = MARK.length - 1;i >= 0; i--)
2717
+ shown.add(i), write(`${up}\r${frame(shown).join(`
2718
+ `)}
2719
+ `), await sleep(HORIZON.has(i) ? 70 : 35);
2720
+ write(`${up}\r${lines.join(`
2721
+ `)}
2722
+
2723
+ `);
2724
+ } finally {
2725
+ process.off("SIGINT", onInterrupt), write("\x1B[?25h");
2726
+ }
2727
+ }
2728
+ async function pace(motion, ms = 90) {
2729
+ if (motion)
2730
+ await sleep(ms);
2731
+ }
2732
+ var KIND_LABEL = {
2733
+ framework: "Framework",
2734
+ language: "Language",
2735
+ hosting: "Hosting",
2736
+ database: "Data",
2737
+ email: "Email",
2738
+ billing: "Billing",
2739
+ auth: "Sign-in",
2740
+ analytics: "Analytics",
2741
+ cms: "Content"
2742
+ };
2743
+ function stackRows(detection, facts) {
2744
+ let byKind = /* @__PURE__ */ new Map;
2745
+ for (let s of detection.stack)
2746
+ byKind.set(s.kind, [...byKind.get(s.kind) ?? [], s.name]);
2747
+ let rows = [];
2748
+ for (let kind of Object.keys(KIND_LABEL)) {
2749
+ let names = byKind.get(kind);
2750
+ if (names)
2751
+ rows.push([KIND_LABEL[kind], names.join(", ")]);
2752
+ }
2753
+ if (facts.siteUrl)
2754
+ rows.push(["Site", facts.siteUrl]);
2755
+ if (facts.repo)
2756
+ rows.push(["Repo", facts.repo]);
2757
+ if (!rows.length)
2758
+ return [import_picocolors3.default.dim("Nothing recognized yet. Your agent will look closer.")];
2759
+ let width = Math.max(...rows.map(([k3]) => k3.length));
2760
+ return rows.map(([k3, v2]) => `${import_picocolors3.default.dim(k3.padEnd(width))} ${v2}`);
2761
+ }
2762
+ var PLAN = [
2763
+ {
2764
+ title: "Scan this folder",
2765
+ detail: "Reads dependency and file names only. Never your .env or code."
2766
+ },
2767
+ {
2768
+ title: "Connect this computer",
2769
+ detail: "Sign in in the browser and confirm a short code."
2770
+ },
2771
+ {
2772
+ title: "Choose what to connect",
2773
+ detail: "What your code already uses is ticked. New things are optional."
2774
+ },
2775
+ {
2776
+ title: "Hand over to your coding agent",
2777
+ detail: "It does the setup and asks you when it needs a key or a yes."
2778
+ }
2779
+ ];
2780
+ function planLines() {
2781
+ return PLAN.map((s, i) => `${import_picocolors3.default.yellow(String(i + 1))} ${import_picocolors3.default.bold(s.title)}
2782
+ ${import_picocolors3.default.dim(s.detail)}`);
2783
+ }
2784
+ function stepTitle(n) {
2785
+ return `${import_picocolors3.default.dim(`Step ${n} of ${PLAN.length}`)} ${import_picocolors3.default.bold(PLAN[n - 1].title)}`;
2786
+ }
2787
+
1651
2788
  // bin/lopebase.ts
1652
2789
  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.
2790
+ ${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} Set up LopeBase from this project's folder.
1654
2791
 
1655
- ${import_picocolors3.default.bold("Usage")}
2792
+ ${import_picocolors4.default.bold("Usage")}
1656
2793
  npx lopebase scan, connect, and hand over to your agent
1657
2794
  npx lopebase login connect this computer (--paste for a token)
1658
2795
  npx lopebase scan [--json] show what the scan found
@@ -1661,13 +2798,24 @@ ${import_picocolors3.default.bold("Usage")}
1661
2798
  npx lopebase adapter secret print a new @lopebase/adapter signing secret
1662
2799
  npx lopebase adapter check <url> test a running adapter (reads LOPEBASE_SIGNING_SECRET)
1663
2800
 
1664
- ${import_picocolors3.default.bold("Learn more")}
1665
- ${import_picocolors3.default.cyan("https://lopebase.com")}
2801
+ ${import_picocolors4.default.bold("Options")}
2802
+ --print print the agent prompt instead of starting an agent
2803
+ --no-motion no animation (also LOPEBASE_NO_MOTION=1)
2804
+
2805
+ ${import_picocolors4.default.bold("Learn more")}
2806
+ ${import_picocolors4.default.cyan("https://lopebase.com")}
1666
2807
  Help: contact@jackalope.digital
1667
2808
  `, flag = (name) => rest.includes(`--${name}`), option = (name) => {
1668
2809
  let i = rest.indexOf(`--${name}`);
1669
2810
  return i >= 0 ? rest[i + 1] : rest.find((a) => a.startsWith(`--${name}=`))?.split("=")[1];
1670
- }, cwd = process.cwd();
2811
+ }, cwd = process.cwd(), motion = motionAllowed(), TAGLINE = "Every product, one place.";
2812
+ function version() {
2813
+ try {
2814
+ return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
2815
+ } catch {
2816
+ return;
2817
+ }
2818
+ }
1671
2819
  function bail(message) {
1672
2820
  xe(message), process.exit(1);
1673
2821
  }
@@ -1677,8 +2825,11 @@ function guard(value) {
1677
2825
  return value;
1678
2826
  }
1679
2827
  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();
2828
+ let [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
2829
+ try {
2830
+ let child = spawn2(cmd, args, { stdio: "ignore", detached: !0 });
2831
+ child.on("error", () => {}), child.unref();
2832
+ } catch {}
1682
2833
  }
1683
2834
  async function checkToken(auth) {
1684
2835
  try {
@@ -1697,40 +2848,40 @@ async function checkToken(auth) {
1697
2848
  });
1698
2849
  if (res.status === 401)
1699
2850
  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) {
2851
+ let body2 = await res.json();
2852
+ return { ok: !0, products: JSON.parse(body2.result?.content?.[0]?.text ?? "[]").length };
2853
+ } catch (e2) {
1703
2854
  return {
1704
2855
  ok: !1,
1705
- message: `Could not reach ${auth.url} (${String(e)}).`
2856
+ message: `Could not reach ${auth.url} (${String(e2)}).`
1706
2857
  };
1707
2858
  }
1708
2859
  }
1709
2860
  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(/\/+$/, "");
2861
+ let known = option("url") ?? loadAuth()?.url, url = workspaceUrl(guard(await he({
2862
+ message: "Your LopeBase workspace (name or address)",
2863
+ placeholder: "yourteam or https://yourteam.lopebase.com",
2864
+ initialValue: known ?? "",
2865
+ validate: (v2) => workspaceUrl(v2 ?? "") ? void 0 : "Type your workspace name, like yourteam, or its full address"
2866
+ })));
1716
2867
  if (!flag("paste")) {
1717
2868
  let device = await startDeviceLogin(url);
1718
2869
  if (device) {
1719
2870
  Me([
1720
- `Your code: ${import_picocolors3.default.bold(import_picocolors3.default.yellow(device.userCode))}`,
2871
+ `Your code: ${import_picocolors4.default.bold(import_picocolors4.default.yellow(device.userCode))}`,
1721
2872
  "",
1722
2873
  "We opened your workspace in the browser. Sign in if asked,",
1723
2874
  "check the code matches, and press Connect."
1724
2875
  ].join(`
1725
- `), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors3.default.dim(device.verifyUrl));
2876
+ `), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors4.default.dim(device.verifyUrl));
1726
2877
  let s = Y2();
1727
2878
  s.start("Waiting for you to approve in the browser");
1728
2879
  let res = await pollDeviceLogin(url, device);
1729
2880
  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 };
2881
+ let file2 = saveAuth({ url: res.url, token: res.token });
2882
+ return s.stop(`Connected (${res.scope}). Saved to ${import_picocolors4.default.dim(file2)}`), { url: res.url, token: res.token };
1732
2883
  }
1733
- if (s.stop(import_picocolors3.default.red(res.message)), res.reason === "denied")
2884
+ if (s.stop(import_picocolors4.default.red(res.message)), res.reason === "denied")
1734
2885
  bail("Nothing was connected.");
1735
2886
  M2.warn("Falling back to pasting a token.");
1736
2887
  }
@@ -1741,47 +2892,59 @@ async function pasteLogin(url) {
1741
2892
  let settings = `${url}/settings#agent-access`;
1742
2893
  Me([
1743
2894
  "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")}.`,
2895
+ `2. Under Settings, Agent access, press ${import_picocolors4.default.bold("Create agent access")}.`,
1745
2896
  "3. Copy the token and paste it here. It is saved on this computer only."
1746
2897
  ].join(`
1747
- `), "Get an agent token"), openBrowser(settings), M2.info(import_picocolors3.default.dim(settings));
2898
+ `), "Get an agent token"), openBrowser(settings), M2.info(import_picocolors4.default.dim(settings));
1748
2899
  for (;; ) {
1749
2900
  let token = guard(await ge({
1750
2901
  message: "Paste the agent token",
1751
- validate: (v) => v?.startsWith("lb_agent_") ? void 0 : "Tokens start with lb_agent_"
2902
+ validate: (v2) => v2?.startsWith("lb_agent_") ? void 0 : "Tokens start with lb_agent_"
1752
2903
  })).trim(), s = Y2();
1753
2904
  s.start("Checking the token");
1754
2905
  let check = await checkToken({ url, token });
1755
2906
  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 };
2907
+ let file2 = saveAuth({ url, token });
2908
+ return s.stop(`Signed in. ${check.products} product(s) in this workspace. Saved to ${import_picocolors4.default.dim(file2)}`), { url, token };
1758
2909
  }
1759
- s.stop(import_picocolors3.default.red(check.message ?? "That did not work."));
2910
+ s.stop(import_picocolors4.default.red(check.message ?? "That did not work."));
1760
2911
  }
1761
2912
  }
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");
2913
+ async function printDetection(d3, facts) {
2914
+ Me(stackRows(d3, facts).join(`
2915
+ `), "Your stack"), await pace(motion, 150);
2916
+ let found = d3.sources.filter((s) => s.status === "found");
1767
2917
  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");
2918
+ Me(found.map((s) => `${import_picocolors4.default.green("●")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(s.evidence.join(", "))}`).join(`
2919
+ `), "Already in your code, ready to connect"), await pace(motion, 150);
2920
+ let possible = d3.sources.filter((s) => s.status === "possible");
1771
2921
  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)
1775
- M2.warn(gap);
2922
+ Me(possible.map((s) => `${import_picocolors4.default.yellow("?")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(`only ${s.evidence.join(", ")}`)}`).join(`
2923
+ `), "Maybe in use, your agent will check"), await pace(motion, 150);
2924
+ for (let gap of d3.gaps)
2925
+ M2.info(import_picocolors4.default.dim(gap));
2926
+ }
2927
+ async function scanWithProgress() {
2928
+ let s = Y2();
2929
+ s.start("Reading this folder");
2930
+ let steps = [], facts = scanProject(cwd, (step) => steps.push(step));
2931
+ await pace(motion, 400), s.stop("Read this folder");
2932
+ for (let step of steps)
2933
+ M2.success(step.detail ? `${step.label}
2934
+ ${import_picocolors4.default.dim(step.detail)}` : step.label), await pace(motion, 120);
2935
+ let detection = detectStack(facts);
2936
+ return { facts, detection };
1776
2937
  }
1777
2938
  function selfCommand() {
1778
2939
  let self = process.argv[1] ?? "";
1779
- return self.includes("node_modules") || !self ? ["npx", "-y", "lopebase", "mcp"] : [process.execPath, self, "mcp"];
2940
+ if (self && !self.includes("node_modules"))
2941
+ return [process.execPath, self, "mcp"];
2942
+ return process.platform === "win32" ? ["cmd", "/c", "npx", "-y", "lopebase", "mcp"] : ["npx", "-y", "lopebase", "mcp"];
1780
2943
  }
1781
2944
  function registerMcp(agent) {
1782
2945
  if (!agent.mcpAdd)
1783
2946
  return !1;
1784
- let argv = agent.mcpAdd(selfCommand()), res = spawnSync2(agent.bin, argv, {
2947
+ let argv = agent.mcpAdd(selfCommand()), res = spawnAgentSync(agent.bin, argv, {
1785
2948
  stdio: "pipe",
1786
2949
  cwd,
1787
2950
  encoding: "utf8"
@@ -1789,29 +2952,45 @@ function registerMcp(agent) {
1789
2952
  return res.status === 0 || /already exists/i.test(`${res.stderr}${res.stdout}`);
1790
2953
  }
1791
2954
  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();
2955
+ if (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(`
2956
+ `), "Here is what happens"), !guard(await ye({
2957
+ message: `Scan ${import_picocolors4.default.bold(cwd)}?`,
2958
+ initialValue: !0
2959
+ })))
2960
+ xe("Nothing was read. Run lopebase again any time."), process.exit(0);
2961
+ M2.step(stepTitle(1));
2962
+ let { facts, detection } = await scanWithProgress();
2963
+ await printDetection(detection, facts), M2.step(stepTitle(2));
2964
+ let auth = loadAuth();
2965
+ if (auth) {
2966
+ let s2 = Y2();
2967
+ s2.start(`Checking your connection to ${auth.url}`);
2968
+ let check = await checkToken(auth);
2969
+ if (check.ok)
2970
+ s2.stop(`Connected to ${import_picocolors4.default.bold(auth.url)}. ${check.products} ${check.products === 1 ? "product" : "products"} so far.`);
2971
+ else
2972
+ s2.stop(import_picocolors4.default.yellow("Your saved connection no longer works.")), auth = null;
2973
+ }
2974
+ auth ??= await login(), M2.step(stepTitle(3));
1800
2975
  let productName = guard(await he({
1801
2976
  message: "What is this product called?",
1802
2977
  initialValue: facts.name?.replace(/^@[^/]+\//, "") ?? ""
1803
2978
  })), chosen = guard(await fe({
1804
- message: "What should we connect? (space to toggle)",
2979
+ message: "What should we connect? (space to toggle, enter to go on)",
1805
2980
  options: detection.sources.map((src) => ({
1806
2981
  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)")}`,
2982
+ 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
2983
  hint: src.why
1809
2984
  })),
1810
- initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc").map((src) => src.label),
2985
+ initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc" || src.source === "uptime").map((src) => src.label),
1811
2986
  required: !1
1812
- })), prompt = onboardingPrompt({ facts, detection, chosen, productName }), agents = installedAgents();
2987
+ }));
2988
+ M2.step(stepTitle(4));
2989
+ let prompt = onboardingPrompt({ facts, detection, chosen, productName }), agents = installedAgents();
1813
2990
  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)`);
2991
+ if (!agents.length)
2992
+ M2.info("No coding agent found on this computer (Claude Code, Codex, and others). Paste the prompt below into the one you use.");
2993
+ Me(prompt, "Paste this into your coding agent"), Se(`Add the MCP server first: ${import_picocolors4.default.cyan("npx lopebase mcp")} (stdio)`);
1815
2994
  return;
1816
2995
  }
1817
2996
  let agentId = guard(await ve({
@@ -1825,24 +3004,39 @@ async function init() {
1825
3004
  Me(prompt, "Paste this into your coding agent"), Se("Done.");
1826
3005
  return;
1827
3006
  }
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.`);
3007
+ let agent = agents.find((a) => a.id === agentId), s = Y2();
3008
+ s.start(`Adding the LopeBase tools to ${agent.label}`);
3009
+ let registered = registerMcp(agent);
3010
+ if (await pace(motion, 300), registered)
3011
+ s.stop(`Added the LopeBase tools to ${agent.label} for this folder.`);
1831
3012
  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));
3013
+ s.stop(import_picocolors4.default.yellow(`Add an MCP server to ${agent.label} that runs: npx -y lopebase mcp`));
3014
+ Me([
3015
+ `${import_picocolors4.default.bold(agent.label)} will now:`,
3016
+ ` ${import_picocolors4.default.dim("1.")} add ${import_picocolors4.default.bold(productName || "this product")} to your workspace`,
3017
+ ` ${import_picocolors4.default.dim("2.")} connect ${chosen.length ? chosen.join(", ") : "what it recommends"}`,
3018
+ ` ${import_picocolors4.default.dim("3.")} stop and ask you for any sign-in, key, or yes`,
3019
+ "",
3020
+ import_picocolors4.default.dim("You can follow along in your workspace as each one turns green.")
3021
+ ].join(`
3022
+ `), "Handing over"), Se(`Starting ${agent.label}. Press Ctrl+C any time to stop.`);
3023
+ let child = spawnAgent(agent.bin, agent.args(prompt), {
3024
+ stdio: "inherit",
3025
+ cwd
3026
+ });
3027
+ child.on("error", (e2) => bail(`Could not start ${agent.label}: ${e2.message}`)), child.on("exit", (code) => process.exit(code ?? 0));
1834
3028
  }
1835
3029
  async function main() {
1836
3030
  switch (command) {
1837
3031
  case "init":
1838
3032
  return init();
1839
3033
  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.");
3034
+ 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
3035
  case "scan": {
1842
3036
  let facts = scanProject(cwd), detection = detectStack(facts);
1843
3037
  if (flag("json"))
1844
3038
  return console.log(JSON.stringify({ facts, detection }, null, 2));
1845
- return printDetection(detection);
3039
+ 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
3040
  }
1847
3041
  case "prompt": {
1848
3042
  let facts = scanProject(cwd), detection = detectStack(facts);
@@ -1862,9 +3056,9 @@ async function main() {
1862
3056
  baseUrl: rest[1],
1863
3057
  secret,
1864
3058
  token: secret ? void 0 : token,
1865
- log: (l) => console.log(l.startsWith("FAIL") ? import_picocolors3.default.red(l) : import_picocolors3.default.green(l))
3059
+ log: (l2) => console.log(l2.startsWith("FAIL") ? import_picocolors4.default.red(l2) : import_picocolors4.default.green(l2))
1866
3060
  });
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);
3061
+ 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
3062
  }
1869
3063
  return console.log(`Usage:
1870
3064
  lopebase adapter secret print a new signing secret