Password Manager experiment built on ATProto Spaces
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306#!/usr/bin/env nodeimport { Command } from 'commander';import * as path from 'node:path';import * as os from 'node:os';import * as readline from 'node:readline/promises';import * as fs from 'node:fs/promises';import { TwoPasswordVault, FileSpaceStorageAdapter, generatePassword, generatePassphrase, generateTotp, InvalidMasterPasswordError} from '@2password/core';
const program = new Command();
function getVaultPath(customPath?: string): string { if (customPath) return path.resolve(customPath); if (process.env.TWO_PASSWORD_VAULT_PATH) return path.resolve(process.env.TWO_PASSWORD_VAULT_PATH); return path.join(os.homedir(), '.2password', 'vault.json');}
async function promptPassword(prompt = 'Enter Master Password: '): Promise<string> { const envPass = process.env.TWO_PASSWORD_MASTER; if (envPass) return envPass;
const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const pass = await rl.question(prompt); rl.close(); return pass.trim();}
program .name('2pwd') .description('2password: Zero-Knowledge Password Manager built on AT Protocol and Spaces') .version('0.1.0') .option('-v, --vault <path>', 'Path to local encrypted vault storage');
program .command('init') .description('Initialize a new 2password vault') .requiredOption('--did <did>', 'User DID (e.g. did:plc:... or did:web:...)') .option('-p, --password <password>', 'Master Password') .action(async (options) => { const vaultPath = getVaultPath(program.opts().vault); const password = options.password || (await promptPassword('Choose Master Password: ')); const storage = new FileSpaceStorageAdapter(vaultPath); const vault = new TwoPasswordVault(storage, options.did);
try { await vault.initialize(password); console.log(`\nVault successfully initialized!`); console.log(`DID: ${options.did}`); console.log(`Encrypted Vault Path: ${vaultPath}`); console.log(`KDF: Argon2id (64MB memory, 3 iterations)`); } catch (err: any) { console.error(`Initialization failed: ${err.message}`); process.exit(1); } });
program .command('status') .description('Show vault information and configuration') .action(async () => { const vaultPath = getVaultPath(program.opts().vault); const storage = new FileSpaceStorageAdapter(vaultPath); const vaultRecord = await storage.getVaultRecord();
console.log(`\n=== 2password Vault Status ===`); console.log(`Path: ${vaultPath}`); if (!vaultRecord) { console.log(`Status: Not Initialized (Run '2pwd init --did <your-did>' to create one)`); } else { console.log(`Status: Initialized`); console.log(`Schema Version: ${vaultRecord.version}`); console.log(`KDF Algorithm: ${vaultRecord.kdf.algorithm}`); console.log(`KDF Memory: ${vaultRecord.kdf.memory} KiB (${vaultRecord.kdf.memory / 1024} MB)`); console.log(`KDF Iterations: ${vaultRecord.kdf.iterations}`); console.log(`Created: ${vaultRecord.createdAt}`); const items = await storage.listItemRecords(); console.log(`Encrypted Items in Space: ${items.length}`); } });
async function getUnlockedVault(customPassword?: string): Promise<TwoPasswordVault> { const vaultPath = getVaultPath(program.opts().vault); const storage = new FileSpaceStorageAdapter(vaultPath); const record = await storage.getVaultRecord(); if (!record) { console.error(`Vault at ${vaultPath} is not initialized. Run '2pwd init' first.`); process.exit(1); }
const password = customPassword || (await promptPassword()); const vault = new TwoPasswordVault(storage);
try { await vault.unlock(password); return vault; } catch (err) { if (err instanceof InvalidMasterPasswordError) { console.error('\nAuthentication Failed: Invalid Master Password.'); } else { console.error('\nFailed to unlock vault:', err); } process.exit(1); }}
program .command('list') .description('List all credentials in the vault') .option('-p, --password <password>', 'Master Password') .action(async (options) => { const vault = await getUnlockedVault(options.password); const items = await vault.getItems();
if (items.length === 0) { console.log('\nVault is empty. Use `2pwd add login` to add your first item.'); return; }
console.log(`\n=== Vault Items (${items.length}) ===`); console.log(`${'ID'.padEnd(20)} | ${'TYPE'.padEnd(12)} | ${'NAME'.padEnd(25)} | ${'DETAILS'}`); console.log('-'.repeat(80));
for (const item of items) { let preview = ''; if (item.type === 'login') preview = item.login?.username || ''; else if (item.type === 'card') preview = item.card?.number ? `**** ${item.card.number.slice(-4)}` : ''; else if (item.type === 'secureNote') preview = item.notes ? `${item.notes.slice(0, 25)}...` : ''; else if (item.type === 'identity') preview = `${item.identity?.firstName || ''} ${item.identity?.lastName || ''}`;
const fav = item.favorite ? '★ ' : ' '; console.log( `${item.id.padEnd(20)} | ${item.type.padEnd(12)} | ${(fav + item.name).padEnd(25)} | ${preview}` ); } });
program .command('show <idOrName>') .description('Show full decrypted details of an item') .option('-p, --password <password>', 'Master Password') .action(async (idOrName, options) => { const vault = await getUnlockedVault(options.password); const items = await vault.getItems(); const item = items.find((i: any) => i.id === idOrName || i.name.toLowerCase() === idOrName.toLowerCase());
if (!item) { console.error(`Item '${idOrName}' not found.`); process.exit(1); }
console.log(`\n=== ${item.name} (${item.type}) ===`); console.log(`ID: ${item.id}`); if (item.favorite) console.log(`Favorite: Yes`);
if (item.type === 'login' && item.login) { if (item.login.username) console.log(`Username: ${item.login.username}`); if (item.login.password) console.log(`Password: ${item.login.password}`); if (item.login.uris && item.login.uris.length > 0) { console.log(`URIs: ${item.login.uris.map((u: any) => u.uri).join(', ')}`); } if (item.login.totp) { try { const { code, remainingSeconds } = await generateTotp(item.login.totp); console.log(`TOTP Code: ${code} (${remainingSeconds}s remaining)`); } catch { console.log(`TOTP Secret: ${item.login.totp}`); } } } else if (item.type === 'secureNote') { console.log(`\nNotes:\n${item.notes || '(none)'}`); } else if (item.type === 'card' && item.card) { console.log(`Cardholder: ${item.card.cardholderName || ''}`); console.log(`Brand: ${item.card.brand || ''}`); console.log(`Number: ${item.card.number || ''}`); console.log(`Expires: ${item.card.expMonth || ''}/${item.card.expYear || ''}`); console.log(`CVV: ${item.card.code || ''}`); }
if (item.notes && item.type !== 'secureNote') { console.log(`Notes: ${item.notes}`); } });
program .command('totp <idOrName>') .description('Generate live TOTP 2FA code for a login item') .option('-p, --password <password>', 'Master Password') .action(async (idOrName, options) => { const vault = await getUnlockedVault(options.password); const items = await vault.getItems(); const item = items.find((i: any) => i.id === idOrName || i.name.toLowerCase() === idOrName.toLowerCase());
if (!item || !item.login?.totp) { console.error(`Item '${idOrName}' has no TOTP configuration.`); process.exit(1); }
const { code, remainingSeconds, period } = await generateTotp(item.login.totp); const progress = Math.round((remainingSeconds / period) * 10); const bar = '='.repeat(progress).padEnd(10, ' '); console.log(`\n${item.name} 2FA Code:`); console.log(`\x1b[1m\x1b[32m${code.slice(0, 3)} ${code.slice(3)}\x1b[0m [${bar}] (${remainingSeconds}s remaining)`); });
const addCmd = program.command('add').description('Add an item to the vault');
addCmd .command('login <name>') .description('Add a login credential') .requiredOption('-u, --username <username>', 'Login username / email') .option('-p, --password <password>', 'Login password (omit to generate one)') .option('--totp <seed>', 'TOTP seed or otpauth:// URI') .option('--url <url>', 'Website URL') .option('--notes <notes>', 'Notes') .option('-m, --master-password <pwd>', 'Master Password') .action(async (name, options) => { const vault = await getUnlockedVault(options.masterPassword); const password = options.password || generatePassword({ length: 20 });
const item = await vault.addItem({ type: 'login', name, notes: options.notes, login: { username: options.username, password, totp: options.totp, uris: options.url ? [{ uri: options.url }] : undefined } });
console.log(`\nAdded login '${item.name}' [ID: ${item.id}]`); console.log(`Username: ${options.username}`); console.log(`Password: ${password}`); });
addCmd .command('note <name>') .description('Add a secure note') .requiredOption('--notes <content>', 'Note content') .option('-m, --master-password <pwd>', 'Master Password') .action(async (name, options) => { const vault = await getUnlockedVault(options.masterPassword); const item = await vault.addItem({ type: 'secureNote', name, notes: options.notes }); console.log(`\nAdded secure note '${item.name}' [ID: ${item.id}]`); });
program .command('generate') .description('Generate a cryptographically secure password or passphrase') .option('-l, --length <n>', 'Password length', '20') .option('--no-special', 'Exclude special symbols') .option('--no-numbers', 'Exclude numbers') .option('--passphrase', 'Generate a multi-word passphrase') .option('-w, --words <n>', 'Word count for passphrase', '4') .action((options) => { if (options.passphrase) { const phrase = generatePassphrase(parseInt(options.words, 10)); console.log(`\nGenerated Passphrase: \x1b[1m\x1b[36m${phrase}\x1b[0m`); } else { const pwd = generatePassword({ length: parseInt(options.length, 10), special: options.special, numbers: options.numbers }); console.log(`\nGenerated Password: \x1b[1m\x1b[32m${pwd}\x1b[0m`); } });
program .command('import <file>') .description('Import credentials from a Bitwarden unencrypted JSON export file') .option('-p, --password <password>', 'Master Password') .action(async (file, options) => { const vault = await getUnlockedVault(options.password); const content = await fs.readFile(path.resolve(file), 'utf-8'); const result = await vault.importBitwarden(content); console.log(`\nSuccessfully imported ${result.importedCount} items into your 2password vault!`); });
program .command('export <outputFile>') .description('Export vault items to Bitwarden-compatible JSON format') .option('-p, --password <password>', 'Master Password') .action(async (outputFile, options) => { const vault = await getUnlockedVault(options.password); const json = await vault.exportBitwarden(); await fs.writeFile(path.resolve(outputFile), json, 'utf-8'); console.log(`\nVault successfully exported to ${outputFile}`); });
program.parse();