127 lines
3.7 KiB
JavaScript
127 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const https = require('https');
|
|
|
|
// 加载配置
|
|
const CONFIG_PATH = process.env.VAULTWARDEN_CONFIG_PATH ||
|
|
path.join(process.env.HOME || process.env.USERPROFILE, '.config', 'vaultwarden', 'config.json');
|
|
|
|
let config = { url: 'https://bit.180356.xyz', client_id: '', client_secret: '' };
|
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
try {
|
|
config = { ...config, ...JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) };
|
|
} catch (e) {}
|
|
}
|
|
|
|
const VAULT_URL = config.url;
|
|
const CLIENT_ID = config.client_id;
|
|
const CLIENT_SECRET = config.client_secret;
|
|
|
|
const token = null;
|
|
const deviceId = 'openclaw-cli-' + require('crypto').randomUUID();
|
|
|
|
async function getToken() {
|
|
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 request('/identity/connect/token', 'POST', params.toString(), false, 'application/x-www-form-urlencoded');
|
|
return response.access_token;
|
|
}
|
|
|
|
async function request(endpoint, method = 'GET', data = null, useAuth = true, contentType = 'application/json') {
|
|
return new Promise((resolve, reject) => {
|
|
const url = new URL(VAULT_URL + endpoint);
|
|
const options = {
|
|
hostname: url.hostname,
|
|
port: url.port || 443,
|
|
path: url.pathname + url.search,
|
|
method: method,
|
|
headers: { 'Content-Type': contentType, 'Accept': 'application/json' },
|
|
timeout: 30000
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const json = body ? JSON.parse(body) : {};
|
|
if (res.statusCode >= 200 && res.statusCode < 300) resolve(json);
|
|
else reject(new Error(`HTTP ${res.statusCode}: ${json.message || body}`));
|
|
} catch (e) { reject(e); }
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
req.on('timeout', () => { req.destroy(); reject(new Error('超时')); });
|
|
if (data) req.write(data);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function createItem(name, password, username, notes) {
|
|
const token = await getToken();
|
|
const itemData = {
|
|
name: name,
|
|
notes: notes,
|
|
type: 1,
|
|
favorite: false,
|
|
login: { username: username, password: password, totp: null },
|
|
fields: [],
|
|
collectionIds: []
|
|
};
|
|
|
|
return 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: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
timeout: 30000
|
|
};
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', chunk => body += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const json = body ? JSON.parse(body) : {};
|
|
if (res.statusCode >= 200 && res.statusCode < 300) resolve(json);
|
|
else reject(new Error(`HTTP ${res.statusCode}: ${json.message || body}`));
|
|
} catch (e) { reject(e); }
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
req.write(JSON.stringify(itemData));
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
console.log('\n📝 保存新项目\n');
|
|
await createItem('Test Account', 'testpassword123', 'testuser', 'Test notes');
|
|
console.log('✅ 已保存: Test Account\n');
|
|
} catch (error) {
|
|
console.error('\n❌ 错误:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|