eleven / messenger Public

tools: add --onboarding flag to lchat-dev-up.mjs for fresh instance setup (#344)

James Adam c7f110972a1bc147039d6591881c7ec0017f2c60 parent d7f0c67
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));
}