()
| 33 | * Generates a 12-word recovery phrase and derives everything from it. |
| 34 | */ |
| 35 | export async function initCommand(): Promise<void> { |
| 36 | if (existsSync(DB_PATH)) { |
| 37 | console.log("SharedContext is already initialized at ~/.sharedcontext/"); |
| 38 | console.log("To reinitialize, delete ~/.sharedcontext/ first."); |
| 39 | return; |
| 40 | } |
| 41 | |
| 42 | console.log(BANNER); |
| 43 | console.log("Initializing SharedContext...\n"); |
| 44 | |
| 45 | mkdirSync(SHAREDCONTEXT_DIR, { recursive: true }); |
| 46 | |
| 47 | // Generate 12-word recovery phrase |
| 48 | const words = generatePhrase(); |
| 49 | |
| 50 | // Show phrase on alternate screen (like vim/less — vanishes when done) |
| 51 | const half = PHRASE_WORD_COUNT / 2; |
| 52 | const idx1 = Math.floor(Math.random() * half); // word 1..6 |
| 53 | const idx2 = half + Math.floor(Math.random() * half); // word 7..12 |
| 54 | |
| 55 | // Enter alternate screen buffer |
| 56 | process.stderr.write("\x1b[?1049h"); |
| 57 | // Move cursor to top and clear |
| 58 | process.stderr.write("\x1b[H\x1b[2J"); |
| 59 | |
| 60 | process.stderr.write("\n RECOVERY PHRASE — write this down, then confirm below.\n"); |
| 61 | process.stderr.write(" This screen will disappear after confirmation.\n\n"); |
| 62 | process.stderr.write(` ${words.map((w, i) => `${i + 1}.${w}`).join(" ")}\n\n`); |
| 63 | |
| 64 | const answer = await prompt( |
| 65 | ` Type word ${idx1 + 1} and ${idx2 + 1} to confirm: ` |
| 66 | ); |
| 67 | |
| 68 | // Leave alternate screen — phrase is gone from terminal |
| 69 | process.stderr.write("\x1b[?1049l"); |
| 70 | |
| 71 | const parts = answer.split(/\s+/); |
| 72 | if ( |
| 73 | parts.length < 2 || |
| 74 | parts[0].toLowerCase() !== words[idx1].toLowerCase() || |
| 75 | parts[1].toLowerCase() !== words[idx2].toLowerCase() |
| 76 | ) { |
| 77 | console.error("Confirmation failed. Please run `sharedcontext init` again."); |
| 78 | process.exit(1); |
| 79 | } |
| 80 | |
| 81 | const phrase = phraseToString(words); |
| 82 | |
| 83 | // Derive deterministic identity from phrase |
| 84 | console.log("\nDeriving identity from recovery phrase..."); |
| 85 | const keypair = deriveKeypairFromPhrase(phrase); |
| 86 | |
| 87 | // Generate random salt for AES key derivation (stored locally + on Arweave) |
| 88 | const salt = generateSalt(); |
| 89 | writeFileSync(SALT_PATH, Buffer.from(salt), { mode: 0o600 }); |
| 90 | |
| 91 | console.log("Deriving encryption key (this takes a few seconds)..."); |
| 92 | const key = deriveKey(phrase, salt); |
no test coverage detected