End-to-end join: user runs `claudemesh join ic://join/<base64>` and walks away with a signed member record + persistent keypair. new modules: - src/crypto/keypair.ts: libsodium ed25519 keypair generation. Format is crypto_sign_keypair raw bytes, hex-encoded (32-byte pub, 64-byte secret = seed || pub). Same format libsodium will need in Step 18 for sign/verify. - src/invite/parse.ts: ic://join/<base64url(JSON)> parser with Zod shape validation + expiry check. encodeInviteLink helper for tests. - src/invite/enroll.ts: POST /join to broker, converts ws:// to http:// transparently. rewritten join command wires them together: 1. parse invite → 2. generate keypair → 3. POST /join → 4. persist config → 5. print success. state/config.ts: saveConfig now chmods the file to 0600 after write, since it holds ed25519 secret keys. No-op on Windows. signature verification (step 18) + invite-token one-time-use tracking are deferred. For now the invite link is a plain bearer token; any client with the link can join. verified end-to-end via apps/cli/scripts/join-roundtrip.ts: build invite → run join subprocess → load new config → connect as new member → send A→B → receive push. Flow passes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 lines
641 B
TypeScript
25 lines
641 B
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Build a test invite link from a seeded mesh (reads /tmp/cli-seed.json).
|
|
* Writes the link to stdout.
|
|
*/
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { encodeInviteLink } from "../src/invite/parse";
|
|
|
|
const seed = JSON.parse(readFileSync("/tmp/cli-seed.json", "utf-8")) as {
|
|
meshId: string;
|
|
};
|
|
|
|
const link = encodeInviteLink({
|
|
v: 1,
|
|
mesh_id: seed.meshId,
|
|
mesh_slug: "rt-join",
|
|
broker_url: process.env.BROKER_WS_URL ?? "ws://localhost:7900/ws",
|
|
expires_at: Math.floor(Date.now() / 1000) + 3600,
|
|
mesh_root_key: "Y2xhdWRlbWVzaC10ZXN0LW1lc2gta2V5LWRldm9ubHk",
|
|
role: "member",
|
|
});
|
|
|
|
console.log(link);
|