#!/usr/bin/env node /** * Multi-agent CLI for the shared AI password book (unique strategy). * * Same backend as Pi tools / HTTP: * list | get | resolve * * Auth: cookie session to mnote-web (default http://127.0.0.1:3000). * export MNOTE_BASE_URL=http://127.0.0.1:3000 * export MNOTE_COOKIE='mnote_session=...' # or paste from browser * * Examples: * node scripts/mnote-vault-cli.js list * node scripts/mnote-vault-cli.js get --id cred_xxx * node scripts/mnote-vault-cli.js resolve --id cred_xxx --field password * node scripts/mnote-vault-cli.js resolve --id cred_xxx --field password --account-id acc_xxx * * Never print secrets to logs other than resolve stdout value line when --raw. * Default resolve prints JSON including value for tool piping; use --raw for plain value only. */ const BASE = (process.env.MNOTE_BASE_URL || process.env.MNOTE_URL || 'http://127.0.0.1:3000').replace( /\/$/, '' ); const COOKIE = process.env.MNOTE_COOKIE || process.env.MNOTE_SESSION_COOKIE || ''; function usage() { console.error(`Usage: mnote-vault-cli.js auth-e2e # print export MNOTE_COOKIE=... for local mnote-web mnote-vault-cli.js list [--status active|deleted] mnote-vault-cli.js get --id mnote-vault-cli.js resolve --id --field password|apikey|token|username|email \\ [--account-id ] [--secret-id ] [--raw] mnote-vault-cli.js login --id [--force] mnote-vault-cli.js session --id --cookie [--source human_bridge] Env: MNOTE_BASE_URL, MNOTE_COOKIE auth-e2e uses MNOTE_E2E_EMAIL / MNOTE_E2E_PASSWORD (defaults: mnote.e2e@example.com / MnoteE2E123!) Steady-state: auth-e2e once → list (optional) → login (reuse session). Multi-account: get item → pick accounts[].id → resolve --account-id … Human Cloudflare: browser then "session" write-back.`); process.exit(2); } function parseArgs(argv) { const out = { _: [] }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; if ( a === '--id' || a === '--field' || a === '--status' || a === '--cookie' || a === '--source' || a === '--expires' || a === '--account-id' || a === '--secret-id' ) { out[a.slice(2)] = argv[++i]; } else if (a === '--raw' || a === '--force') { out[a.slice(2)] = true; } else if (a.startsWith('--')) { console.error('Unknown flag', a); usage(); } else { out._.push(a); } } return out; } async function api(method, path, body) { const headers = { Accept: 'application/json', }; if (COOKIE) headers.Cookie = COOKIE; if (body != null) { headers['Content-Type'] = 'application/json'; } const res = await fetch(BASE + path, { method, headers, body: body != null ? JSON.stringify(body) : undefined, }); let data = null; try { data = await res.json(); } catch { data = null; } if (!res.ok || (data && data.ok === false)) { const msg = (data && (data.message || (data.error && data.error.message))) || `HTTP ${res.status}`; const code = (data && data.code) || res.status; const err = new Error(String(msg)); err.code = code; err.payload = data; throw err; } return data && data.result != null ? data.result : data; } async function authE2e() { const email = process.env.MNOTE_E2E_EMAIL || 'mnote.e2e@example.com'; const password = process.env.MNOTE_E2E_PASSWORD || 'MnoteE2E123!'; const body = { action: 'auth:signIn', args: { provider: 'password', params: { password, flow: 'signIn', account: email, email, name: email.includes('@') ? email.split('@')[0] : email, }, }, }; const res = await fetch(BASE + '/api/auth', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify(body), }); const setCookies = res.headers.getSetCookie ? res.headers.getSetCookie() : []; // Node <18 fallback: raw get may only return one const raw = res.headers.raw ? res.headers.raw()['set-cookie'] || [] : setCookies; const parts = []; for (const sc of raw) { const first = String(sc).split(';')[0].trim(); if (first) parts.push(first); } if (!parts.length) { const text = await res.text(); throw new Error( `auth-e2e failed HTTP ${res.status}: no Set-Cookie (${text.slice(0, 120)})` ); } const cookieHeader = parts.join('; '); // Print only export line for eval/source console.log(`export MNOTE_COOKIE=${JSON.stringify(cookieHeader)}`); console.error('[mnote-vault-cli] auth-e2e ok; eval the export line (do not paste cookie into chat)'); } async function main() { const args = parseArgs(process.argv.slice(2)); const cmd = args._[0]; if (!cmd) usage(); if (cmd === 'auth-e2e') { await authE2e(); return; } if (!COOKIE) { console.error( 'Warning: MNOTE_COOKIE empty — run: eval $(node scripts/mnote-vault-cli.js auth-e2e)' ); } if (cmd === 'list') { const status = args.status || 'active'; const q = new URLSearchParams({ status }); const result = await api('GET', `/api/vault/ai/list?${q}`); console.log(JSON.stringify(result, null, 2)); return; } if (cmd === 'get') { if (!args.id) usage(); const result = await api('GET', `/api/vault/ai/items/${encodeURIComponent(args.id)}`); console.log(JSON.stringify(result, null, 2)); return; } if (cmd === 'resolve') { if (!args.id || !args.field) usage(); const body = { field: args.field }; if (args['account-id']) body.accountId = args['account-id']; if (args['secret-id']) body.secretId = args['secret-id']; const result = await api( 'POST', `/api/vault/ai/items/${encodeURIComponent(args.id)}/resolve`, body ); if (args.raw) { if (result && result.resolved && result.value != null) { process.stdout.write(String(result.value)); if (!String(result.value).endsWith('\n')) process.stdout.write('\n'); } else { console.error( result && result.transcriptHint ? result.transcriptHint : 'not resolved' ); process.exit(1); } return; } console.log(JSON.stringify(result, null, 2)); return; } if (cmd === 'login') { if (!args.id) usage(); const result = await api( 'POST', `/api/vault/ai/items/${encodeURIComponent(args.id)}/login`, { forceRefresh: !!args.force } ); // Redact cookie in default print? Agent needs cookie — print full JSON; warn stderr. if (result && result.cookieHeader) { console.error('[mnote-vault-cli] cookieHeader present (do not paste into chat)'); } console.log(JSON.stringify(result, null, 2)); if (result && result.ok === false && result.code === 'vault_login_human_required') { process.exit(3); } return; } if (cmd === 'session') { if (!args.id || !args.cookie) usage(); const result = await api( 'POST', `/api/vault/ai/items/${encodeURIComponent(args.id)}/session`, { cookieHeader: args.cookie, expiresAt: args.expires || undefined, source: args.source || 'human_bridge', } ); console.log(JSON.stringify(result, null, 2)); return; } usage(); } main().catch((err) => { console.error(err.message || String(err)); if (err.code) console.error('code:', err.code); process.exit(1); });