lopebase 0.5.0 → 0.6.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/README.md +1 -0
- package/dist/lopebase.js +194 -13
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ Made for web products, and fine with anything else:
|
|
|
83
83
|
| --- | --- |
|
|
84
84
|
| `npx lopebase` | Scan, connect, choose, and hand over to your agent. |
|
|
85
85
|
| `npx lopebase login` | Connect this computer to your workspace with a short code, creating an account if you need one. `--paste` to paste an agent token instead. |
|
|
86
|
+
| `npx lopebase update` | Update this project's LopeBase Adapter: what is new, then your agent does it. |
|
|
86
87
|
| `npx lopebase scan` | Show what the scan found. `--json` for scripts. |
|
|
87
88
|
| `npx lopebase prompt` | Print the setup prompt instead of starting an agent. |
|
|
88
89
|
| `npx lopebase mcp` | Run the LopeBase tools as a local MCP server (stdio) for any agent. |
|
package/dist/lopebase.js
CHANGED
|
@@ -502,7 +502,7 @@ var require_cross_spawn = __commonJS(function(exports, module) {
|
|
|
502
502
|
|
|
503
503
|
// bin/lopebase.ts
|
|
504
504
|
import { spawn as spawn2 } from "node:child_process";
|
|
505
|
-
import { readFileSync as
|
|
505
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
506
506
|
|
|
507
507
|
// ../../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
|
|
508
508
|
import { stripVTControlCharacters as S2 } from "node:util";
|
|
@@ -1205,6 +1205,86 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
1205
1205
|
} };
|
|
1206
1206
|
};
|
|
1207
1207
|
|
|
1208
|
+
// ../admin-adapter/src/releases.ts
|
|
1209
|
+
var ADAPTER_VERSION = "0.3.1", ADAPTER_RELEASES = [
|
|
1210
|
+
{
|
|
1211
|
+
version: "0.3.1",
|
|
1212
|
+
date: "2026-10-08",
|
|
1213
|
+
features: [
|
|
1214
|
+
"Reports its version to LopeBase, so the console can say when an update is out."
|
|
1215
|
+
],
|
|
1216
|
+
steps: []
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
version: "0.3.0",
|
|
1220
|
+
date: "2026-10-08",
|
|
1221
|
+
features: [
|
|
1222
|
+
"Service health: list the database, queues, workers, or other hosts the app needs, and LopeBase's uptime checks watch each one.",
|
|
1223
|
+
"httpCheck(id, url) for a service that has its own health address."
|
|
1224
|
+
],
|
|
1225
|
+
steps: [
|
|
1226
|
+
"Read the code for what the app cannot work without (database, queue, workers, third-party APIs called on every request) and show the human the list.",
|
|
1227
|
+
"Add them as `services` in createAdminAdapter: a check function each (a cheap query like select 1), or httpCheck for another host.",
|
|
1228
|
+
"After deploying, set the product's uptime check in LopeBase to <app>/api/lopebase/health."
|
|
1229
|
+
]
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
version: "0.2.0",
|
|
1233
|
+
date: "2026-10-01",
|
|
1234
|
+
features: [
|
|
1235
|
+
"The app's owner chooses how emails, names, phone numbers, and typed text reach LopeBase: in full, masked, or not at all."
|
|
1236
|
+
],
|
|
1237
|
+
breaking: [
|
|
1238
|
+
'An adapter with personal fields (email, name, phone, free text) will not start until `personalData` is set to "full", "masked", or "ids_only".'
|
|
1239
|
+
],
|
|
1240
|
+
steps: [
|
|
1241
|
+
"Ask the human how personal data may reach LopeBase (full, masked, or ids_only) and set `personalData` in createAdminAdapter to their answer.",
|
|
1242
|
+
'Mark free-text fields people wrote, like messages and notes, with personal: "text".'
|
|
1243
|
+
]
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
version: "0.1.2",
|
|
1247
|
+
date: "2026-09-30",
|
|
1248
|
+
features: [
|
|
1249
|
+
"Signed requests, the standard overview stats, and the conformance suite."
|
|
1250
|
+
],
|
|
1251
|
+
steps: []
|
|
1252
|
+
}
|
|
1253
|
+
];
|
|
1254
|
+
function compareVersions(a, b) {
|
|
1255
|
+
let parts = (v) => (v.match(/^\D*(\d+)\.(\d+)\.(\d+)/) ?? []).slice(1).map(Number), [x, y] = [parts(a), parts(b)];
|
|
1256
|
+
if (x.length !== 3)
|
|
1257
|
+
return y.length === 3 ? -1 : 0;
|
|
1258
|
+
if (y.length !== 3)
|
|
1259
|
+
return 1;
|
|
1260
|
+
for (let i = 0;i < 3; i++)
|
|
1261
|
+
if (x[i] !== y[i])
|
|
1262
|
+
return x[i] < y[i] ? -1 : 1;
|
|
1263
|
+
return 0;
|
|
1264
|
+
}
|
|
1265
|
+
function releasesSince(version) {
|
|
1266
|
+
return ADAPTER_RELEASES.filter((r) => !version || compareVersions(r.version, version) > 0).reverse();
|
|
1267
|
+
}
|
|
1268
|
+
function upgradePrompt(fromVersion) {
|
|
1269
|
+
let todo = releasesSince(fromVersion), breaking = todo.flatMap((r) => r.breaking ?? []), steps = todo.flatMap((r) => r.steps);
|
|
1270
|
+
return [
|
|
1271
|
+
`Update this app's LopeBase adapter (@lopebase/adapter) from ${fromVersion ?? "its current version"} to 0.3.1.`,
|
|
1272
|
+
"",
|
|
1273
|
+
"1. Install it with this repo's package manager, for example `npm install @lopebase/adapter@latest`. A 0.x range like ^0.1.2 does not pick up 0.2 or later on its own.",
|
|
1274
|
+
...breaking.length ? [
|
|
1275
|
+
"2. These changes would stop the adapter from starting; handle each one:",
|
|
1276
|
+
...breaking.map((b) => ` - ${b}`)
|
|
1277
|
+
] : ["2. Nothing in this update stops an existing adapter from starting."],
|
|
1278
|
+
...steps.length ? [
|
|
1279
|
+
"3. Adopt what is new, asking the human before each change:",
|
|
1280
|
+
...steps.map((s) => ` - ${s}`)
|
|
1281
|
+
] : ["3. Nothing to add in code."],
|
|
1282
|
+
"4. Run the app and `LOPEBASE_SIGNING_SECRET=... npx lopebase adapter check http://localhost:<port>/api/lopebase`, plus this repo's own tests. Fix anything that fails.",
|
|
1283
|
+
"5. Summarize what changed. Never print the signing secret."
|
|
1284
|
+
].join(`
|
|
1285
|
+
`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1208
1288
|
// ../admin-adapter/src/security.ts
|
|
1209
1289
|
var SIGNATURE_HEADER = "lopebase-signature";
|
|
1210
1290
|
var enc = /* @__PURE__ */ new TextEncoder;
|
|
@@ -2927,6 +3007,52 @@ function watchRows(items) {
|
|
|
2927
3007
|
return items.map((w) => w.how === "address" ? `${brand("◉")} ${import_picocolors3.default.bold(w.label)} ${import_picocolors3.default.dim("uptime check, every 15 minutes")}` : `${brand("○")} ${import_picocolors3.default.bold(w.label)} ${import_picocolors3.default.dim("a service in the adapter's health list")}`);
|
|
2928
3008
|
}
|
|
2929
3009
|
|
|
3010
|
+
// src/update.ts
|
|
3011
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3012
|
+
import path3 from "node:path";
|
|
3013
|
+
var readJson2 = (file) => {
|
|
3014
|
+
try {
|
|
3015
|
+
return JSON.parse(readFileSync3(file, "utf8"));
|
|
3016
|
+
} catch {
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
function findAdapterInstalls(root) {
|
|
3021
|
+
let out = [];
|
|
3022
|
+
for (let dir of packageDirs(root)) {
|
|
3023
|
+
let pkg = readJson2(path3.join(dir, "package.json"));
|
|
3024
|
+
if (!pkg)
|
|
3025
|
+
continue;
|
|
3026
|
+
let range = {
|
|
3027
|
+
...pkg.dependencies,
|
|
3028
|
+
...pkg.devDependencies
|
|
3029
|
+
}["@lopebase/adapter"];
|
|
3030
|
+
if (!range || range.startsWith("workspace:"))
|
|
3031
|
+
continue;
|
|
3032
|
+
let installed = [dir, root].map((d) => readJson2(path3.join(d, "node_modules/@lopebase/adapter/package.json"))).find((p) => typeof p?.version === "string")?.version;
|
|
3033
|
+
out.push({ dir: path3.relative(root, dir) || ".", range, installed });
|
|
3034
|
+
}
|
|
3035
|
+
return out;
|
|
3036
|
+
}
|
|
3037
|
+
function runningVersion(i) {
|
|
3038
|
+
return i.installed ?? i.range.replace(/^[^\d]*/, "");
|
|
3039
|
+
}
|
|
3040
|
+
var isBehind = (running, latest) => compareVersions(running, latest) < 0;
|
|
3041
|
+
async function latestOnNpm(name, timeoutMs = 1500, fetchImpl = fetch) {
|
|
3042
|
+
try {
|
|
3043
|
+
let res = await fetchImpl(`https://registry.npmjs.org/${name}/latest`, {
|
|
3044
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
3045
|
+
headers: { accept: "application/json" }
|
|
3046
|
+
});
|
|
3047
|
+
if (!res.ok)
|
|
3048
|
+
return;
|
|
3049
|
+
let body = await res.json();
|
|
3050
|
+
return typeof body.version === "string" ? body.version : void 0;
|
|
3051
|
+
} catch {
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
|
|
2930
3056
|
// bin/lopebase.ts
|
|
2931
3057
|
var [command = "init", ...rest] = process.argv.slice(2), HELP = `
|
|
2932
3058
|
${badge("LopeBase")} Set up LopeBase from this project's folder.
|
|
@@ -2934,6 +3060,7 @@ ${badge("LopeBase")} Set up LopeBase from this project's folder.
|
|
|
2934
3060
|
${import_picocolors4.default.bold("Usage")}
|
|
2935
3061
|
npx lopebase scan, connect, and hand over to your agent
|
|
2936
3062
|
npx lopebase login connect this computer (--paste for a token)
|
|
3063
|
+
npx lopebase update update this project's LopeBase Adapter to the latest
|
|
2937
3064
|
npx lopebase scan [--json] show what the scan found
|
|
2938
3065
|
npx lopebase prompt print the setup prompt instead
|
|
2939
3066
|
npx lopebase mcp run the LopeBase tools as a stdio MCP server
|
|
@@ -2954,7 +3081,7 @@ ${import_picocolors4.default.bold("Learn more")}
|
|
|
2954
3081
|
}, cwd = process.cwd(), motion = motionAllowed(), TAGLINE = "Every product, one place.", PAGEVIEWS = "LopeBase pageviews";
|
|
2955
3082
|
function version() {
|
|
2956
3083
|
try {
|
|
2957
|
-
return JSON.parse(
|
|
3084
|
+
return JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
2958
3085
|
} catch {
|
|
2959
3086
|
return;
|
|
2960
3087
|
}
|
|
@@ -3109,7 +3236,7 @@ function registerMcp(agent) {
|
|
|
3109
3236
|
return res.status === 0 || /already exists/i.test(`${res.stderr}${res.stdout}`);
|
|
3110
3237
|
}
|
|
3111
3238
|
async function init() {
|
|
3112
|
-
if (console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), await horizon({ motion }), Ie(`${badge("LopeBase")} set up this project`), Me(planLines().join(`
|
|
3239
|
+
if (console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), await horizon({ motion }), Ie(`${badge("LopeBase")} set up this project`), await noticeNewerCli(), Me(planLines().join(`
|
|
3113
3240
|
`), "Here is what happens"), !guard(await ye({
|
|
3114
3241
|
message: `Scan ${import_picocolors4.default.bold(cwd)}?`,
|
|
3115
3242
|
initialValue: !0
|
|
@@ -3159,7 +3286,19 @@ async function init() {
|
|
|
3159
3286
|
productName,
|
|
3160
3287
|
watch: watchList(detection, facts),
|
|
3161
3288
|
pageviews: chosen.includes(PAGEVIEWS)
|
|
3162
|
-
})
|
|
3289
|
+
});
|
|
3290
|
+
await handOff(prompt, (agent) => [
|
|
3291
|
+
`${import_picocolors4.default.bold(agent)} will now:`,
|
|
3292
|
+
` ${brand("1.")} add ${import_picocolors4.default.bold(productName || "this product")} to your workspace`,
|
|
3293
|
+
` ${brand("2.")} connect ${chosen.length ? chosen.join(", ") : "what it recommends"}`,
|
|
3294
|
+
` ${brand("3.")} pick what to watch for uptime: ${watchList(detection, facts).map((w) => w.label).join(", ") || "the site"}`,
|
|
3295
|
+
` ${brand("4.")} stop and ask you for any sign-in, key, or yes`,
|
|
3296
|
+
"",
|
|
3297
|
+
import_picocolors4.default.dim("You can follow along in your workspace as each one turns green.")
|
|
3298
|
+
]);
|
|
3299
|
+
}
|
|
3300
|
+
async function handOff(prompt, plan) {
|
|
3301
|
+
let agents = installedAgents();
|
|
3163
3302
|
if (!agents.length || flag("print")) {
|
|
3164
3303
|
if (!agents.length)
|
|
3165
3304
|
M2.info("No coding agent found on this computer (Claude Code, Codex, and others). Paste the prompt below into the one you use.");
|
|
@@ -3184,15 +3323,7 @@ async function init() {
|
|
|
3184
3323
|
s.stop(`Added the LopeBase tools to ${agent.label} for this folder.`);
|
|
3185
3324
|
else
|
|
3186
3325
|
s.stop(import_picocolors4.default.yellow(`Add an MCP server to ${agent.label} that runs: npx -y lopebase mcp`));
|
|
3187
|
-
Me(
|
|
3188
|
-
`${import_picocolors4.default.bold(agent.label)} will now:`,
|
|
3189
|
-
` ${brand("1.")} add ${import_picocolors4.default.bold(productName || "this product")} to your workspace`,
|
|
3190
|
-
` ${brand("2.")} connect ${chosen.length ? chosen.join(", ") : "what it recommends"}`,
|
|
3191
|
-
` ${brand("3.")} pick what to watch for uptime: ${watchList(detection, facts).map((w) => w.label).join(", ") || "the site"}`,
|
|
3192
|
-
` ${brand("4.")} stop and ask you for any sign-in, key, or yes`,
|
|
3193
|
-
"",
|
|
3194
|
-
import_picocolors4.default.dim("You can follow along in your workspace as each one turns green.")
|
|
3195
|
-
].join(`
|
|
3326
|
+
Me(plan(agent.label).join(`
|
|
3196
3327
|
`), "Handing over"), Se(`Starting ${agent.label}. Press Ctrl+C any time to stop.`);
|
|
3197
3328
|
let child = spawnAgent(agent.bin, agent.args(prompt), {
|
|
3198
3329
|
stdio: "inherit",
|
|
@@ -3200,10 +3331,60 @@ async function init() {
|
|
|
3200
3331
|
});
|
|
3201
3332
|
child.on("error", (e) => bail(`Could not start ${agent.label}: ${e.message}`)), child.on("exit", (code) => process.exit(code ?? 0));
|
|
3202
3333
|
}
|
|
3334
|
+
async function noticeNewerCli() {
|
|
3335
|
+
let mine = version(), latest = await latestOnNpm("lopebase");
|
|
3336
|
+
if (mine && latest && isBehind(mine, latest))
|
|
3337
|
+
M2.warn(`lopebase ${latest} is out (this is ${mine}). Run ${brand("npx lopebase@latest")} for the newest setup steps.`);
|
|
3338
|
+
}
|
|
3339
|
+
async function update() {
|
|
3340
|
+
console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), await horizon({ motion }), Ie(`${badge("LopeBase")} update`), await noticeNewerCli();
|
|
3341
|
+
let s = Y2();
|
|
3342
|
+
s.start("Looking for the LopeBase Adapter in this project");
|
|
3343
|
+
let [installs, npmLatest] = await Promise.all([
|
|
3344
|
+
Promise.resolve(findAdapterInstalls(cwd)),
|
|
3345
|
+
latestOnNpm("@lopebase/adapter")
|
|
3346
|
+
]);
|
|
3347
|
+
if (await pace(motion, 300), !installs.length)
|
|
3348
|
+
return s.stop("No LopeBase Adapter in this project."), Me([
|
|
3349
|
+
"Connections, uptime checks, and pageviews update on their own in",
|
|
3350
|
+
"the console, so there is nothing to install for them.",
|
|
3351
|
+
"",
|
|
3352
|
+
`To add the adapter (your users, numbers, and service health), run ${brand("npx lopebase")}.`
|
|
3353
|
+
].join(`
|
|
3354
|
+
`), "Nothing to update here"), Se("Done.");
|
|
3355
|
+
let latest = npmLatest && isBehind(ADAPTER_VERSION, npmLatest) ? npmLatest : ADAPTER_VERSION, behind = installs.filter((i) => isBehind(runningVersion(i), latest));
|
|
3356
|
+
if (s.stop(installs.map((i) => `${i.dir === "." ? "This folder" : i.dir} runs ${import_picocolors4.default.bold(runningVersion(i))}`).join(", ")), !behind.length)
|
|
3357
|
+
return Se(`Up to date with ${import_picocolors4.default.bold(latest)}.`);
|
|
3358
|
+
let from = behind.map(runningVersion).sort((a, b) => isBehind(a, b) ? -1 : 1)[0], releases = releasesSince(from);
|
|
3359
|
+
if (Me(releases.flatMap((r) => [
|
|
3360
|
+
`${brand("◆")} ${import_picocolors4.default.bold(r.version)} ${import_picocolors4.default.dim(r.date)}`,
|
|
3361
|
+
...r.features.map((f) => ` ${f}`),
|
|
3362
|
+
...(r.breaking ?? []).map((b) => ` ${import_picocolors4.default.yellow("!")} ${b}`)
|
|
3363
|
+
]).join(`
|
|
3364
|
+
`), `New since ${from}`), npmLatest && isBehind(ADAPTER_VERSION, npmLatest))
|
|
3365
|
+
M2.warn(`npm has ${npmLatest}, newer than these notes. Run ${brand("npx lopebase@latest update")} for its steps.`);
|
|
3366
|
+
if (!guard(await ye({
|
|
3367
|
+
message: "Hand the update to your coding agent? It asks before each change.",
|
|
3368
|
+
initialValue: !0
|
|
3369
|
+
})))
|
|
3370
|
+
return Me(upgradePrompt(from), "Or paste this into your coding agent"), Se("Nothing was changed.");
|
|
3371
|
+
let where = behind.map((i) => i.dir).join(", ");
|
|
3372
|
+
await handOff(`${upgradePrompt(from)}
|
|
3373
|
+
|
|
3374
|
+
The adapter is in: ${where}. If you have the lopebase MCP tools, call get_onboarding_status afterwards and do any step it marks next.`, (agent) => [
|
|
3375
|
+
`${import_picocolors4.default.bold(agent)} will now:`,
|
|
3376
|
+
` ${brand("1.")} update @lopebase/adapter to ${latest} in ${where}`,
|
|
3377
|
+
` ${brand("2.")} make the changes the new version needs, asking you first`,
|
|
3378
|
+
` ${brand("3.")} run the adapter check and your tests`
|
|
3379
|
+
]);
|
|
3380
|
+
}
|
|
3203
3381
|
async function main() {
|
|
3204
3382
|
switch (command) {
|
|
3205
3383
|
case "init":
|
|
3206
3384
|
return init();
|
|
3385
|
+
case "update":
|
|
3386
|
+
case "upgrade":
|
|
3387
|
+
return update();
|
|
3207
3388
|
case "login":
|
|
3208
3389
|
return await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${badge("LopeBase")} sign in`), await login(), Se("Ready. Run lopebase in any project.");
|
|
3209
3390
|
case "scan": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lopebase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Set up LopeBase from your project's folder: scan your stack, connect your workspace, and let your coding agent do the rest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/index.ts",
|
|
11
|
-
"./detect": "./src/detect.ts"
|
|
11
|
+
"./detect": "./src/detect.ts",
|
|
12
|
+
"./releases": "./src/releases.ts"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"dist",
|