2026-07-21 05:13:05 +08:00
|
|
|
#!/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
|
2026-07-24 11:36:06 +08:00
|
|
|
* node scripts/mnote-vault-cli.js resolve --id cred_xxx --field password --account-id acc_xxx
|
2026-07-21 05:13:05 +08:00
|
|
|
*
|
|
|
|
|
* 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 <credentialId>
|
2026-07-24 11:36:06 +08:00
|
|
|
mnote-vault-cli.js resolve --id <credentialId> --field password|apikey|token|username|email \\
|
|
|
|
|
[--account-id <accountId>] [--secret-id <secretId>] [--raw]
|
2026-07-21 05:13:05 +08:00
|
|
|
mnote-vault-cli.js login --id <credentialId> [--force]
|
|
|
|
|
mnote-vault-cli.js session --id <credentialId> --cookie <CookieHeader> [--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).
|
2026-07-24 11:36:06 +08:00
|
|
|
Multi-account: get item → pick accounts[].id → resolve --account-id …
|
2026-07-21 05:13:05 +08:00
|
|
|
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' ||
|
2026-07-24 11:36:06 +08:00
|
|
|
a === '--expires' ||
|
|
|
|
|
a === '--account-id' ||
|
|
|
|
|
a === '--secret-id'
|
2026-07-21 05:13:05 +08:00
|
|
|
) {
|
|
|
|
|
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();
|
2026-07-24 11:36:06 +08:00
|
|
|
const body = { field: args.field };
|
|
|
|
|
if (args['account-id']) body.accountId = args['account-id'];
|
|
|
|
|
if (args['secret-id']) body.secretId = args['secret-id'];
|
2026-07-21 05:13:05 +08:00
|
|
|
const result = await api(
|
|
|
|
|
'POST',
|
|
|
|
|
`/api/vault/ai/items/${encodeURIComponent(args.id)}/resolve`,
|
2026-07-24 11:36:06 +08:00
|
|
|
body
|
2026-07-21 05:13:05 +08:00
|
|
|
);
|
|
|
|
|
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);
|
|
|
|
|
});
|