Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
237 lines
7.0 KiB
JavaScript
237 lines
7.0 KiB
JavaScript
#!/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
|
|
*
|
|
* 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>
|
|
mnote-vault-cli.js resolve --id <credentialId> --field password|apikey|token [--raw]
|
|
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).
|
|
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'
|
|
) {
|
|
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 result = await api(
|
|
'POST',
|
|
`/api/vault/ai/items/${encodeURIComponent(args.id)}/resolve`,
|
|
{ field: args.field }
|
|
);
|
|
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);
|
|
});
|