@greatlhd/ailo-desktop 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +31 -8
- package/dist/config_server.js +14 -162
- package/dist/index.js +207 -173
- package/dist/static/app.css +252 -13
- package/dist/static/app.html +71 -84
- package/dist/static/app.js +326 -108
- package/package.json +3 -9
- package/src/cli.ts +35 -8
- package/src/config_server.ts +22 -171
- package/src/index.ts +221 -177
- package/src/static/app.css +252 -13
- package/src/static/app.html +71 -84
- package/src/static/app.js +326 -108
- package/src/dingtalk-types.ts +0 -26
- package/src/qq-types.ts +0 -49
- package/src/qq-ws.ts +0 -223
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI init command for ailo-desktop.
|
|
3
|
-
* Usage: ailo-desktop init [--defaults]
|
|
3
|
+
* Usage: ailo-desktop init [--defaults] [--config-dir <path>]
|
|
4
4
|
*/
|
|
5
5
|
import { createInterface } from "readline";
|
|
6
|
-
import { join } from "path";
|
|
6
|
+
import { join, resolve } from "path";
|
|
7
|
+
import { mkdirSync } from "fs";
|
|
7
8
|
import { writeConfig } from "@greatlhd/ailo-endpoint-sdk";
|
|
8
9
|
import { CONFIG_FILENAME } from "./constants.js";
|
|
9
|
-
|
|
10
|
+
function parseCliArgs() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
let useDefaults = false;
|
|
13
|
+
let configDir = process.cwd();
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
if (args[i] === "--defaults") {
|
|
16
|
+
useDefaults = true;
|
|
17
|
+
}
|
|
18
|
+
else if ((args[i] === "--config-dir" || args[i] === "-c") && args[i + 1]) {
|
|
19
|
+
configDir = resolve(args[++i]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { useDefaults, configDir };
|
|
23
|
+
}
|
|
10
24
|
async function prompt(question, defaultVal = "") {
|
|
11
25
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
12
26
|
return new Promise((resolve) => {
|
|
@@ -17,8 +31,14 @@ async function prompt(question, defaultVal = "") {
|
|
|
17
31
|
});
|
|
18
32
|
});
|
|
19
33
|
}
|
|
20
|
-
export async function runInit(
|
|
34
|
+
export async function runInit(useDefaultsArg, configDirArg) {
|
|
35
|
+
const { useDefaults, configDir } = useDefaultsArg !== undefined && configDirArg !== undefined
|
|
36
|
+
? { useDefaults: useDefaultsArg, configDir: configDirArg }
|
|
37
|
+
: parseCliArgs();
|
|
21
38
|
console.log("=== Ailo Desktop 初始化 ===\n");
|
|
39
|
+
console.log(`配置目录: ${configDir}\n`);
|
|
40
|
+
mkdirSync(configDir, { recursive: true });
|
|
41
|
+
const configPath = join(configDir, CONFIG_FILENAME);
|
|
22
42
|
const wsUrl = useDefaults ? "ws://127.0.0.1:19800/ws" : await prompt("Ailo WebSocket URL", "ws://127.0.0.1:19800/ws");
|
|
23
43
|
const apiKey = useDefaults ? "" : await prompt("API Key (留空稍后配置)");
|
|
24
44
|
const endpointId = useDefaults ? "desktop-01" : await prompt("端点 ID", "desktop-01");
|
|
@@ -29,8 +49,11 @@ export async function runInit(useDefaults = false) {
|
|
|
29
49
|
endpointId,
|
|
30
50
|
},
|
|
31
51
|
};
|
|
32
|
-
writeConfig(
|
|
33
|
-
console.log(`\n已写入 ${
|
|
34
|
-
console.log("\n
|
|
35
|
-
console.log(
|
|
52
|
+
writeConfig(configPath, config);
|
|
53
|
+
console.log(`\n已写入 ${configPath}`);
|
|
54
|
+
console.log("\n初始化完成!运行以下命令启动桌面端点:");
|
|
55
|
+
console.log(` ailo-desktop --config-dir ${configDir} --port 3000`);
|
|
56
|
+
}
|
|
57
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
58
|
+
runInit().catch(console.error);
|
|
36
59
|
}
|
package/dist/config_server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createServer } from "http";
|
|
2
2
|
import { readFileSync, existsSync } from "fs";
|
|
3
|
-
import { join,
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
6
|
import { WebSocketServer } from "ws";
|
|
@@ -165,10 +165,8 @@ export function startConfigServer(deps) {
|
|
|
165
165
|
return json(res, await runEnvInstall());
|
|
166
166
|
if (path === "/api/tools" && req.method === "GET")
|
|
167
167
|
return json(res, await getReportedTools(deps));
|
|
168
|
-
if (path === "/api/
|
|
169
|
-
return json(res, await
|
|
170
|
-
if (path === "/api/blueprints" && req.method === "GET")
|
|
171
|
-
return json(res, await getAllBlueprintsInfo(deps));
|
|
168
|
+
if (path === "/api/skills" && req.method === "GET")
|
|
169
|
+
return json(res, await getReportedSkills(deps));
|
|
172
170
|
// Ailo 连接配置(仅当 configPath 存在时,供桌面端在界面填写并保存)
|
|
173
171
|
if (deps.configPath) {
|
|
174
172
|
if (path === "/api/connection" && req.method === "GET")
|
|
@@ -176,13 +174,6 @@ export function startConfigServer(deps) {
|
|
|
176
174
|
if (path === "/api/connection" && req.method === "POST")
|
|
177
175
|
return json(res, await saveConnectionConfig(deps.configPath, await body(req), deps.onConnectionConfigSaved));
|
|
178
176
|
}
|
|
179
|
-
// 邮件配置
|
|
180
|
-
if (deps.configPath) {
|
|
181
|
-
if (path === "/api/email/config" && req.method === "GET")
|
|
182
|
-
return json(res, getEmailConfig(deps.configPath, deps.getEmailStatus));
|
|
183
|
-
if (path === "/api/email/config" && req.method === "POST")
|
|
184
|
-
return json(res, await saveEmailConfig(deps.configPath, await body(req), deps.onEmailConfigSaved));
|
|
185
|
-
}
|
|
186
177
|
// 飞书配置
|
|
187
178
|
if (deps.configPath) {
|
|
188
179
|
if (path === "/api/feishu/config" && req.method === "GET")
|
|
@@ -190,20 +181,6 @@ export function startConfigServer(deps) {
|
|
|
190
181
|
if (path === "/api/feishu/config" && req.method === "POST")
|
|
191
182
|
return json(res, await savePlatformConfig(deps.configPath, "feishu", ["appId", "appSecret"], await body(req), deps.onFeishuConfigSaved));
|
|
192
183
|
}
|
|
193
|
-
// 钉钉配置
|
|
194
|
-
if (deps.configPath) {
|
|
195
|
-
if (path === "/api/dingtalk/config" && req.method === "GET")
|
|
196
|
-
return json(res, getPlatformConfig(deps.configPath, "dingtalk", ["clientId", "clientSecret"], deps.getDingtalkStatus));
|
|
197
|
-
if (path === "/api/dingtalk/config" && req.method === "POST")
|
|
198
|
-
return json(res, await savePlatformConfig(deps.configPath, "dingtalk", ["clientId", "clientSecret"], await body(req), deps.onDingtalkConfigSaved));
|
|
199
|
-
}
|
|
200
|
-
// QQ 配置
|
|
201
|
-
if (deps.configPath) {
|
|
202
|
-
if (path === "/api/qq/config" && req.method === "GET")
|
|
203
|
-
return json(res, getPlatformConfig(deps.configPath, "qq", ["appId", "appSecret", "apiBase"], deps.getQQStatus));
|
|
204
|
-
if (path === "/api/qq/config" && req.method === "POST")
|
|
205
|
-
return json(res, await savePlatformConfig(deps.configPath, "qq", ["appId", "appSecret", "apiBase"], await body(req), deps.onQQConfigSaved));
|
|
206
|
-
}
|
|
207
184
|
// MCP
|
|
208
185
|
if (path === "/api/mcp" && req.method === "GET")
|
|
209
186
|
return json(res, getMCPList(deps.mcpManager));
|
|
@@ -392,114 +369,28 @@ async function runEnvInstall() {
|
|
|
392
369
|
}
|
|
393
370
|
return { installed, errors };
|
|
394
371
|
}
|
|
395
|
-
/** 从蓝图正文中解析 tools 列表(仅提取 name、description) */
|
|
396
|
-
function parseBlueprintTools(md) {
|
|
397
|
-
const tools = [];
|
|
398
|
-
const frontmatterMatch = md.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
399
|
-
const yaml = frontmatterMatch?.[1] ?? "";
|
|
400
|
-
let inToolsSection = false;
|
|
401
|
-
let current = null;
|
|
402
|
-
for (const line of yaml.split(/\r?\n/)) {
|
|
403
|
-
if (line.trim() === "tools:") {
|
|
404
|
-
inToolsSection = true;
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
if (!inToolsSection)
|
|
408
|
-
continue;
|
|
409
|
-
const itemMatch = line.match(/^\s+-\s+name:\s*(.+)$/);
|
|
410
|
-
const descMatch = line.match(/^\s+description:\s*(.+)$/);
|
|
411
|
-
if (itemMatch) {
|
|
412
|
-
if (current)
|
|
413
|
-
tools.push(current);
|
|
414
|
-
current = { name: itemMatch[1].trim(), description: "" };
|
|
415
|
-
}
|
|
416
|
-
else if (current && descMatch) {
|
|
417
|
-
current.description = descMatch[1].trim();
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
if (current)
|
|
421
|
-
tools.push(current);
|
|
422
|
-
return tools;
|
|
423
|
-
}
|
|
424
|
-
async function fetchBlueprintContent(url, localFallbackPath) {
|
|
425
|
-
if (url.startsWith("file://")) {
|
|
426
|
-
const filePath = url.slice(7);
|
|
427
|
-
return readFileSync(filePath, "utf-8");
|
|
428
|
-
}
|
|
429
|
-
try {
|
|
430
|
-
const res = await fetch(url);
|
|
431
|
-
if (!res.ok)
|
|
432
|
-
throw new Error(`Blueprint fetch failed: ${res.status}`);
|
|
433
|
-
return res.text();
|
|
434
|
-
}
|
|
435
|
-
catch (e) {
|
|
436
|
-
if (localFallbackPath && existsSync(localFallbackPath)) {
|
|
437
|
-
return readFileSync(localFallbackPath, "utf-8");
|
|
438
|
-
}
|
|
439
|
-
throw e;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
372
|
async function getReportedTools(deps) {
|
|
443
373
|
const out = [];
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
: undefined;
|
|
449
|
-
const md = await fetchBlueprintContent(deps.blueprintUrl, localPath);
|
|
450
|
-
for (const t of parseBlueprintTools(md)) {
|
|
451
|
-
out.push({ name: t.name, description: t.description, source: "builtin" });
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
catch (e) {
|
|
455
|
-
console.error("[config] 解析蓝图工具失败:", e);
|
|
374
|
+
// 内置工具
|
|
375
|
+
if (deps.getEndpointTools) {
|
|
376
|
+
for (const t of deps.getEndpointTools()) {
|
|
377
|
+
out.push({ name: t.name, description: t.description, source: "builtin" });
|
|
456
378
|
}
|
|
457
379
|
}
|
|
380
|
+
// MCP 工具
|
|
458
381
|
for (const t of deps.mcpManager.getAllPrivateTools()) {
|
|
459
382
|
out.push({ name: t.name, description: t.description ?? "", source: "mcp" });
|
|
460
383
|
}
|
|
461
384
|
return out;
|
|
462
385
|
}
|
|
463
|
-
async function
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const content = await fetchBlueprintContent(deps.blueprintUrl, localPath);
|
|
469
|
-
return { url: deps.blueprintUrl, content };
|
|
470
|
-
}
|
|
471
|
-
catch {
|
|
472
|
-
return { url: deps.blueprintUrl, content: null };
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
async function getAllBlueprintsInfo(deps) {
|
|
476
|
-
const paths = deps.getBlueprintPaths?.() ?? [];
|
|
477
|
-
if (paths.length === 0) {
|
|
478
|
-
// fallback:如果没有传 getBlueprintPaths,至少返回主蓝图
|
|
479
|
-
if (deps.blueprintUrl) {
|
|
480
|
-
try {
|
|
481
|
-
const localPath = deps.blueprintLocalPath ? resolve(process.cwd(), deps.blueprintLocalPath) : undefined;
|
|
482
|
-
const content = await fetchBlueprintContent(deps.blueprintUrl, localPath);
|
|
483
|
-
const name = basename(deps.blueprintLocalPath ?? deps.blueprintUrl, ".blueprint.md");
|
|
484
|
-
return [{ name, path: deps.blueprintUrl, content }];
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
return [];
|
|
488
|
-
}
|
|
386
|
+
async function getReportedSkills(deps) {
|
|
387
|
+
const out = [];
|
|
388
|
+
if (deps.getEndpointSkills) {
|
|
389
|
+
for (const s of deps.getEndpointSkills()) {
|
|
390
|
+
out.push({ name: s.name, description: s.description, source: "builtin" });
|
|
489
391
|
}
|
|
490
|
-
return [];
|
|
491
392
|
}
|
|
492
|
-
return
|
|
493
|
-
const filePath = p.startsWith("file://") ? p.slice(7) : p;
|
|
494
|
-
const name = basename(filePath, ".blueprint.md");
|
|
495
|
-
try {
|
|
496
|
-
const content = readFileSync(filePath, "utf-8");
|
|
497
|
-
return { name, path: p, content };
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
return { name, path: p, content: null };
|
|
501
|
-
}
|
|
502
|
-
}));
|
|
393
|
+
return out;
|
|
503
394
|
}
|
|
504
395
|
function getMCPList(mgr) {
|
|
505
396
|
const configs = mgr.getConfigs();
|
|
@@ -585,45 +476,6 @@ async function saveConnectionConfig(configPath, bodyStr, onSaved) {
|
|
|
585
476
|
return { ok: false, error: errMsg(e) };
|
|
586
477
|
}
|
|
587
478
|
}
|
|
588
|
-
function getEmailConfig(configPath, getStatus) {
|
|
589
|
-
const cfg = readConfig(configPath);
|
|
590
|
-
const email = (cfg.email ?? {});
|
|
591
|
-
const status = getStatus?.() ?? { configured: false, running: false };
|
|
592
|
-
return {
|
|
593
|
-
imapHost: email.imapHost ?? "",
|
|
594
|
-
imapUser: email.imapUser ?? "",
|
|
595
|
-
imapPassword: email.imapPassword ?? "",
|
|
596
|
-
imapPort: email.imapPort ?? 993,
|
|
597
|
-
smtpHost: email.smtpHost ?? "",
|
|
598
|
-
smtpPort: email.smtpPort ?? 465,
|
|
599
|
-
smtpUser: email.smtpUser ?? "",
|
|
600
|
-
smtpPassword: email.smtpPassword ?? "",
|
|
601
|
-
...status,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
async function saveEmailConfig(configPath, bodyStr, onSaved) {
|
|
605
|
-
try {
|
|
606
|
-
const bodyTrimmed = (bodyStr ?? "").trim();
|
|
607
|
-
if (!bodyTrimmed)
|
|
608
|
-
return { ok: false, error: "请求体为空" };
|
|
609
|
-
const existing = readConfig(configPath);
|
|
610
|
-
const b = JSON.parse(bodyTrimmed);
|
|
611
|
-
const emailFields = ["imapHost", "imapUser", "imapPassword", "imapPort", "smtpHost", "smtpPort", "smtpUser", "smtpPassword"];
|
|
612
|
-
for (const field of emailFields) {
|
|
613
|
-
if (b[field] !== undefined)
|
|
614
|
-
setNestedValue(existing, `email.${field}`, b[field]);
|
|
615
|
-
}
|
|
616
|
-
writeConfig(configPath, existing);
|
|
617
|
-
if (onSaved) {
|
|
618
|
-
await onSaved();
|
|
619
|
-
return { ok: true, message: "已保存,邮件通道正在重启…" };
|
|
620
|
-
}
|
|
621
|
-
return { ok: true, message: "已保存。" };
|
|
622
|
-
}
|
|
623
|
-
catch (e) {
|
|
624
|
-
return { ok: false, error: errMsg(e) };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
479
|
function getPlatformConfig(configPath, platform, fields, getStatus) {
|
|
628
480
|
const cfg = readConfig(configPath);
|
|
629
481
|
const section = (cfg[platform] ?? {});
|