#!/usr/bin/env node const fs = require('fs'); const https = require('https'); const CONFIG_PATH = process.env.HOME + '/.config/vaultwarden/config.json'; let config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); const VAULT_URL = config.url; const CLIENT_ID = config.client_id; const CLIENT_SECRET = config.client_secret; let token = null; let tokenExpiry = null; const deviceId = 'openclaw-cli-' + require('crypto').randomUUID(); async function getToken() { if (token && tokenExpiry && Date.now() < tokenExpiry - 300000) { return token; } const params = new URLSearchParams({ grant_type: 'client_credentials', scope: 'api', client_id: CLIENT_ID, client_secret: CLIENT_SECRET, device_identifier: deviceId, device_type: '14', device_name: 'OpenClaw CLI' }); const response = await new Promise((resolve, reject) => { const url = new URL(VAULT_URL + '/identity/connect/token'); const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname + url.search, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 30000 }; const req = https.request(options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const json = JSON.parse(body); resolve(json); } catch (e) { reject(e); } }); }); req.on('error', reject); req.write(params.toString()); req.end(); }); if (!response.access_token) { throw new Error('认证失败'); } token = response.access_token; tokenExpiry = Date.now() + ((response.expires_in || 7200) - 300) * 1000; return token; } async function getItem(id) { const t = await getToken(); return new Promise((resolve, reject) => { const url = new URL(VAULT_URL + `/api/ciphers/${id}`); const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname + url.search, method: 'GET', headers: { 'Authorization': `Bearer ${t}` }, timeout: 30000 }; const req = https.request(options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const json = JSON.parse(body); resolve(json); } catch (e) { reject(e); } }); }); req.on('error', reject); req.end(); }); } async function getAllItems() { const t = await getToken(); const response = await new Promise((resolve, reject) => { const url = new URL(VAULT_URL + '/api/ciphers'); const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname + url.search, method: 'GET', headers: { 'Authorization': `Bearer ${t}` }, timeout: 30000 }; const req = https.request(options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const json = JSON.parse(body); resolve(json); } catch (e) { reject(e); } }); }); req.on('error', reject); req.end(); }); return response.data || []; } async function main() { try { const items = await getAllItems(); console.log(`\n📋 密码列表 (${items.length})\n`); console.log('=' .repeat(70)); for (const item of items) { const detail = await getItem(item.id); console.log(`\n🔐 ${detail.name}`); console.log('-'.repeat(50)); console.log(` ID: ${detail.id}`); if (detail.login?.username) console.log(` 用户名: ${detail.login.username}`); if (detail.login?.password) console.log(` 密码: ${detail.login.password}`); if (detail.login?.uris?.length > 0) { detail.login.uris.forEach(uri => { console.log(` URL: ${uri.uri}`); }); } if (detail.notes) console.log(` 备注: ${detail.notes}`); } console.log('\n' + '='.repeat(70)); console.log(`\n✅ 共 ${items.length} 个密码\n`); } catch (error) { console.error('\n❌ 错误:', error.message); process.exit(1); } } main();