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