chore: CRM-NEW baseline
This commit is contained in:
Executable
+138
@@ -0,0 +1,138 @@
|
||||
#!/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://192.168.31.61: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);
|
||||
});
|
||||
Reference in New Issue
Block a user