tools: add --onboarding flag to lchat-dev-up.mjs for fresh instance setup (#344)
1 file changed,
+50
−14
tools/lchat-dev-up.mjs+50 −14
diff --git a/tools/lchat-dev-up.mjs b/tools/lchat-dev-up.mjs| index 6aec2d8..1cf66e3 100755 |
| --- a/tools/lchat-dev-up.mjs |
| +++ b/tools/lchat-dev-up.mjs |
| @@ -1,22 +1,24 @@ |
| #!/usr/bin/env node |
| // lchat-dev-up — spin up a throwaway local lchat server from the current |
| -// checkout (whatever branch/worktree you run it from), pre-seeded with a |
| -// second member and a group chat, and print an invite link that signs you |
| -// straight into it. |
| +// checkout (whatever branch/worktree you run it from), pre-seeded with an |
| +// invite link that signs you straight in. |
| // |
| -// Usage: tools/lchat-dev-up.mjs [--port N] [--buddy-name NAME] [--group-name NAME] |
| +// Usage: tools/lchat-dev-up.mjs [--port N] [--buddy-name NAME] [--group-name NAME] [--onboarding] |
| // |
| // Builds the Go binary from this checkout, runs it against a fresh temp SQLite |
| // DB + a random admin token, then uses web/static/crypto.js (the exact crypto |
| // the browser runs) to, without ever touching a browser: |
| -// 1. mint + claim a second member ("Dev Buddy") via the admin API, so |
| -// there's someone else in the chat |
| -// 2. create a group thread with you + Dev Buddy and post a welcome message |
| -// 3. mint an admin-issued "add a device" invite for a brand-new "you" |
| +// 1. mint an admin-issued "add a device" invite for a brand-new "you" |
| // account — join.js (see its `payload.priv` branch) recognises this as a |
| // device link and signs the browser straight in as that member, with no |
| // name entry and no second session, exactly like using a real add-device |
| // QR code |
| +// With --onboarding (default mode): just create the invite with no buddies or chats |
| +// Without --onboarding (group mode): |
| +// 2. mint + claim a second member ("Dev Buddy") via the admin API, so |
| +// there's someone else in the chat |
| +// 3. create a group thread with you + Dev Buddy and post a welcome message |
| +// 4. point the invite at the group thread so it opens automatically on sign-in |
| // The one thing no HTTP endpoint exposes — pointing that invite at the group |
| // thread so it's the one that opens automatically on sign-in — is patched |
| // directly into the sqlite file via the `sqlite3` CLI, best-effort: if that's |
| @@ -52,8 +54,9 @@ function arg(name, def) { |
| if (process.argv.includes("-h") || process.argv.includes("--help")) { |
| process.stdout.write( |
| - "lchat-dev-up — build + run this checkout, pre-seeded with a group chat, and print a sign-in link\n" + |
| - " tools/lchat-dev-up.mjs [--port N] [--buddy-name NAME] [--group-name NAME]\n", |
| + "lchat-dev-up — build + run this checkout, pre-seeded with a sign-in link\n" + |
| + " tools/lchat-dev-up.mjs [--port N] [--buddy-name NAME] [--group-name NAME] [--onboarding]\n" + |
| + " --onboarding: fresh instance with no buddies or chats (default: includes Dev Buddy + group chat)\n", |
| ); |
| process.exit(0); |
| } |
| @@ -61,6 +64,7 @@ if (process.argv.includes("-h") || process.argv.includes("--help")) { |
| const BUDDY_NAME = arg("buddy-name", "Dev Buddy"); |
| const GROUP_NAME = arg("group-name", "Dev chat"); |
| const explicitPort = arg("port"); |
| +const isOnboarding = process.argv.includes("--onboarding"); |
| function log(msg) { |
| process.stderr.write(`lchat-dev-up: ${msg}\n`); |
| @@ -98,6 +102,27 @@ async function waitForServer(base, server) { |
| throw new Error("server didn't become ready in time"); |
| } |
| +// Provisions a brand-new "you" account for onboarding mode (fresh instance, |
| +// no buddies or chats). Returns the invite link. |
| +async function seedOnboarding(base, adminToken) { |
| + const admin = { "Content-Type": "application/json", Authorization: `Bearer ${adminToken}` }; |
| + |
| + const youKp = await generateUserKeypair(); |
| + const youT = randomSecret(); |
| + const youDerived = await deriveInvite(youT); |
| + const youWrapped = await encrypt(youDerived.wrapKey, { pub: youKp.publicKey, priv: youKp.privateKey }); |
| + await postJSON( |
| + `${base}/api/admin/key-invites`, |
| + { id: youDerived.id, wrapped: youWrapped, claim_hash: youDerived.claimHash }, |
| + admin, |
| + ); |
| + |
| + return { |
| + youInviteId: youDerived.id, |
| + inviteURL: `${base}/join/join#${buildInviteFragment(youT, "You")}`, |
| + }; |
| +} |
| + |
| // Provisions "Dev Buddy" (a real, fully-joined member, so it can post) and a |
| // group thread containing them + a brand-new "you" account, then posts a |
| // welcome message. Returns what the caller needs to build the invite link and |
| @@ -211,16 +236,27 @@ async function main() { |
| server.stderr.pipe(process.stderr); |
| await waitForServer(base, server); |
| - log("seeding a second member + group chat..."); |
| - const seeded = await seed(base, adminToken); |
| - patchInviteThread(dbPath, seeded.youInviteId, seeded.threadId); |
| + |
| + let seeded; |
| + if (isOnboarding) { |
| + log("seeding fresh onboarding instance (no buddies or chats)..."); |
| + seeded = await seedOnboarding(base, adminToken); |
| + } else { |
| + log("seeding a second member + group chat..."); |
| + seeded = await seed(base, adminToken); |
| + patchInviteThread(dbPath, seeded.youInviteId, seeded.threadId); |
| + } |
| console.log(""); |
| console.log(`Server: ${base}`); |
| console.log(`Admin: ${base}/admin#token=${adminToken}`); |
| console.log(`Invite: ${seeded.inviteURL}`); |
| console.log(""); |
| - console.log("Open the invite link to land straight in the group chat. Ctrl-C to stop and clean up."); |
| + if (isOnboarding) { |
| + console.log("Open the invite link to start onboarding. Ctrl-C to stop and clean up."); |
| + } else { |
| + console.log("Open the invite link to land straight in the group chat. Ctrl-C to stop and clean up."); |
| + } |
| await new Promise((resolve) => server.on("exit", resolve)); |
| } |