Files
crm-ai/scripts/register-tools.js
T

139 lines
4.8 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
'use strict';
/**
* 把 bridge 的工具 manifest 注册到 LibreDesk(Admin → AI → Tools)。
*
* 鉴权二选一:
* 1. .env 配了 LIBREDESK_API_KEY/SECRET → 用 API key
* 2. 否则用 System 账号登录(LIBREDESK_SYSTEM_USER_PASSWORD)拿 session + CSRF
*
* 用法:node scripts/register-tools.js
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
function loadEnv(file) {
const out = {};
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
const t = line.trim();
if (!t || t.startsWith('#')) continue;
const eq = t.indexOf('=');
if (eq < 0) continue;
let v = t.slice(eq + 1).trim();
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
out[t.slice(0, eq).trim()] = v;
}
return out;
}
function createClient(baseURL) {
const cookies = new Map();
let csrf = '';
let authHeader = '';
const cookieHeader = () => [...cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
function store(res) {
const list = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [res.headers.get('set-cookie')].filter(Boolean);
for (const c of list) {
const [pair] = c.split(';');
const eq = pair.indexOf('=');
if (eq > 0) {
const k = pair.slice(0, eq).trim();
const v = pair.slice(eq + 1).trim();
cookies.set(k, v);
if (k === 'csrf_token') csrf = v;
}
}
}
async function req(method, p, body) {
const headers = { Accept: 'application/json' };
if (authHeader) headers.Authorization = authHeader;
else if (cookies.size) headers.Cookie = cookieHeader();
if (body !== undefined) headers['Content-Type'] = 'application/json';
if (!authHeader && csrf && method !== 'GET') headers['X-CSRFTOKEN'] = csrf;
const res = await fetch(baseURL + p, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
store(res);
const text = await res.text();
let json = null;
try { json = text ? JSON.parse(text) : null; } catch { /* ignore */ }
if (!res.ok) throw new Error(`${method} ${p} -> HTTP ${res.status}: ${(json && json.message) || text.slice(0, 300)}`);
return json;
}
return {
setAuth: (h) => { authHeader = h; },
login: (email, password) => req('POST', '/api/v1/auth/login', { email, password }),
get: (p) => req('GET', p),
post: (p, b) => req('POST', p, b),
put: (p, b) => req('PUT', p, b),
};
}
async function main() {
const env = loadEnv(path.join(ROOT, '.env'));
const base = (env.LIBREDESK_BASE_URL || 'http://localhost:9000').replace(/\/+$/, '');
const toolsKey = env.TOOLS_SHARED_KEY;
if (!toolsKey) throw new Error('TOOLS_SHARED_KEY 未配置');
const c = createClient(base);
if (env.LIBREDESK_API_KEY && env.LIBREDESK_API_SECRET) {
c.setAuth(`token ${env.LIBREDESK_API_KEY}:${env.LIBREDESK_API_SECRET}`);
console.log('使用 API key 鉴权');
} else {
await c.login(env.LIBREDESK_SYSTEM_EMAIL || 'System', env.LIBREDESK_SYSTEM_USER_PASSWORD);
console.log('使用 System 会话鉴权');
}
const manifestRes = await fetch('http://127.0.0.1:4100/tools/manifest', { headers: { 'X-Tools-Key': toolsKey } });
if (!manifestRes.ok) throw new Error(`bridge manifest 不可用(HTTP ${manifestRes.status}),先启动全栈`);
const manifest = await manifestRes.json();
const listJson = await c.get('/api/v1/ai/tools');
const existing = Array.isArray(listJson) ? listJson : (listJson.data || []);
const byName = new Map(existing.map((t) => [t.name, t]));
let failed = 0;
for (const tool of manifest.tools) {
const body = {
name: tool.name,
description: tool.description,
url: tool.url, // http://bridge:4100/tools/<name>(容器网络内可达)
method: 'POST',
auth: { headers: [{ key: 'X-Tools-Key', value: toolsKey }] },
parameters: tool.parameters,
enabled: true,
copilot_enabled: true,
generate_reply_enabled: true,
requires_agent_approval: false,
requires_verification: Boolean(tool.requires_verification),
};
const current = byName.get(tool.name);
try {
if (current) {
await c.put(`/api/v1/ai/tools/${current.id}`, body);
console.log(` ✓ ${tool.name} (updated)`);
} else {
await c.post('/api/v1/ai/tools', body);
console.log(` ✓ ${tool.name} (created)`);
}
} catch (err) {
failed += 1;
console.error(` ✗ ${tool.name}: ${err.message}`);
}
}
if (failed) {
console.error('\n部分工具注册失败,回退到 Admin → AI → Tools 手工创建。');
process.exitCode = 1;
}
}
main().catch((err) => {
console.error('[register-tools]', err.message);
process.exit(1);
});