@bolloon/bolloon-agent 0.1.8 → 0.1.10
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/bin/bolloon-cli.cjs +8 -1
- package/bin/bolloon.cjs +157 -0
- package/dist/index.js +59 -0
- package/dist/utils/auto-update.js +310 -0
- package/package.json +2 -2
- package/src/index.ts +63 -0
- package/src/utils/auto-update.ts +369 -0
package/bin/bolloon-cli.cjs
CHANGED
|
@@ -114,10 +114,17 @@ async function startCLI(additionalArgs) {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
async function main() {
|
|
117
|
+
// Get version from package.json
|
|
118
|
+
const binPath = require.main.filename;
|
|
119
|
+
const binDir = path.dirname(binPath);
|
|
120
|
+
const packageDir = path.dirname(binDir);
|
|
121
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf-8"));
|
|
122
|
+
const version = packageJson.version;
|
|
123
|
+
|
|
117
124
|
const { mode, args } = parseArgs();
|
|
118
125
|
switch (mode) {
|
|
119
126
|
case "version":
|
|
120
|
-
console.log("Bolloon Agent
|
|
127
|
+
console.log("Bolloon Agent v" + version);
|
|
121
128
|
break;
|
|
122
129
|
case "help":
|
|
123
130
|
printBanner();
|
package/bin/bolloon.cjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
|
|
6
|
+
const RESET = "\x1b[0m";
|
|
7
|
+
const BOLD = "\x1b[1m";
|
|
8
|
+
const CYAN = "\x1b[36m";
|
|
9
|
+
const GREEN = "\x1b[32m";
|
|
10
|
+
const MAGENTA = "\x1b[35m";
|
|
11
|
+
|
|
12
|
+
function log(msg, color) {
|
|
13
|
+
console.log((color || RESET) + msg + RESET);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function printBanner() {
|
|
17
|
+
console.log("\n" + CYAN + BOLD + [
|
|
18
|
+
" ╔═══════════════════════════════════════════╗",
|
|
19
|
+
" ║ 🤖 Bolloon Agent ║",
|
|
20
|
+
" ║ P2P AI Document Processor ║",
|
|
21
|
+
" ╚═══════════════════════════════════════════╝"
|
|
22
|
+
].join("\n") + RESET + "\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getMainEntry() {
|
|
26
|
+
const distDir = path.dirname(require.main.filename);
|
|
27
|
+
const distIndex = path.join(distDir, "index.js");
|
|
28
|
+
if (fs.existsSync(distIndex)) {
|
|
29
|
+
return distIndex;
|
|
30
|
+
}
|
|
31
|
+
return path.join(process.cwd(), "src", "index.ts");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseArgs() {
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
if (args.length === 0) {
|
|
37
|
+
return { mode: "gui", args: [] };
|
|
38
|
+
}
|
|
39
|
+
const first = args[0];
|
|
40
|
+
switch (first) {
|
|
41
|
+
case "-v":
|
|
42
|
+
case "--version":
|
|
43
|
+
return { mode: "version", args: [] };
|
|
44
|
+
case "-h":
|
|
45
|
+
case "--help":
|
|
46
|
+
return { mode: "help", args: [] };
|
|
47
|
+
case "-g":
|
|
48
|
+
case "--gui":
|
|
49
|
+
return { mode: "gui", args: args.slice(1) };
|
|
50
|
+
case "-w":
|
|
51
|
+
case "--web":
|
|
52
|
+
return { mode: "web", args: args.slice(1) };
|
|
53
|
+
case "-c":
|
|
54
|
+
case "--cli":
|
|
55
|
+
return { mode: "cli", args: args.slice(1) };
|
|
56
|
+
default:
|
|
57
|
+
return { mode: "passthrough", args };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function startElectron(additionalArgs) {
|
|
62
|
+
try {
|
|
63
|
+
const electron = require("electron");
|
|
64
|
+
const distDir = path.dirname(require.main.filename);
|
|
65
|
+
let mainPath = path.join(distDir, "electron.js");
|
|
66
|
+
if (!fs.existsSync(mainPath)) {
|
|
67
|
+
mainPath = path.join(process.cwd(), "src", "electron.ts");
|
|
68
|
+
}
|
|
69
|
+
log("启动 Electron...", CYAN);
|
|
70
|
+
const child = spawn(electron, [mainPath, ...additionalArgs], {
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
73
|
+
});
|
|
74
|
+
child.on("error", (err) => {
|
|
75
|
+
log("Electron 启动失败: " + err.message, MAGENTA);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
78
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
log("Electron 不可用,切换到 Web 模式...", CYAN);
|
|
81
|
+
await startWebServer(additionalArgs);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function startWebServer(additionalArgs) {
|
|
86
|
+
const mainPath = getMainEntry();
|
|
87
|
+
const webArgs = ["--web", ...additionalArgs];
|
|
88
|
+
log("启动 Web 服务...", CYAN);
|
|
89
|
+
const child = spawn(process.execPath, [mainPath, ...webArgs], { stdio: "inherit" });
|
|
90
|
+
child.on("error", (err) => {
|
|
91
|
+
log("Web 服务启动失败: " + err.message, MAGENTA);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function startCLI(additionalArgs) {
|
|
98
|
+
const mainPath = getMainEntry();
|
|
99
|
+
log("启动命令行界面...", CYAN);
|
|
100
|
+
const child = spawn(process.execPath, [mainPath, ...additionalArgs], { stdio: "inherit" });
|
|
101
|
+
child.on("error", (err) => {
|
|
102
|
+
log("CLI 启动失败: " + err.message, MAGENTA);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
|
105
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function main() {
|
|
109
|
+
const { mode, args } = parseArgs();
|
|
110
|
+
switch (mode) {
|
|
111
|
+
case "version":
|
|
112
|
+
console.log("Bolloon Agent v0.1.1");
|
|
113
|
+
break;
|
|
114
|
+
case "help":
|
|
115
|
+
printBanner();
|
|
116
|
+
console.log(BOLD + "用法:" + RESET + " bolloon [选项] [命令] [参数]");
|
|
117
|
+
console.log(BOLD + "选项:" + RESET + " --gui, -g 启动图形界面");
|
|
118
|
+
console.log(" --web, -w 启动 Web UI");
|
|
119
|
+
console.log(" --cli, -c 启动命令行界面");
|
|
120
|
+
console.log(" --version, -v 显示版本");
|
|
121
|
+
console.log(" --help, -h 显示帮助");
|
|
122
|
+
console.log(BOLD + "示例:" + RESET + " bolloon # 启动图形界面");
|
|
123
|
+
console.log(" bolloon --web # 启动 Web UI");
|
|
124
|
+
console.log(" bolloon --read file # 读取文档");
|
|
125
|
+
break;
|
|
126
|
+
case "gui":
|
|
127
|
+
printBanner();
|
|
128
|
+
await startElectron(args);
|
|
129
|
+
break;
|
|
130
|
+
case "web":
|
|
131
|
+
printBanner();
|
|
132
|
+
await startWebServer(args);
|
|
133
|
+
break;
|
|
134
|
+
case "cli":
|
|
135
|
+
await startCLI(args);
|
|
136
|
+
break;
|
|
137
|
+
case "passthrough":
|
|
138
|
+
const mainPath = getMainEntry();
|
|
139
|
+
const child = spawn(process.execPath, [mainPath, ...args], { stdio: "inherit" });
|
|
140
|
+
child.on("error", (err) => {
|
|
141
|
+
log("执行失败: " + err.message, MAGENTA);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
144
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
log("未知模式: " + mode, MAGENTA);
|
|
148
|
+
printBanner();
|
|
149
|
+
console.log("输入 --help 查看帮助");
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main().catch((err) => {
|
|
155
|
+
console.error("Fatal error:", err);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
|
|
|
11
11
|
import { getGlobalSharedContext } from './social/global-shared-context.js';
|
|
12
12
|
import { createBollharnessIntegration } from './bollharness-integration/index.js';
|
|
13
13
|
import * as readline from 'readline';
|
|
14
|
+
import { checkAndUpdate } from './utils/auto-update.js';
|
|
14
15
|
const RESET = '\x1b[0m';
|
|
15
16
|
const BOLD = '\x1b[1m';
|
|
16
17
|
const DIM = '\x1b[2m';
|
|
@@ -496,6 +497,8 @@ const AVAILABLE_TOOLS = [
|
|
|
496
497
|
{ name: 'harness_classify', description: '分类变更类型', example: '--harness-classify <description>' },
|
|
497
498
|
{ name: 'harness_context', description: '获取文件上下文', example: '--harness-context <file>' },
|
|
498
499
|
{ name: 'harness_check', description: '执行 Guard 检查', example: '--harness-check <file>' },
|
|
500
|
+
{ name: 'update_check', description: '检查 npm 包更新', example: '--update-check' },
|
|
501
|
+
{ name: 'update_now', description: '立即更新到最新版本', example: '--update-now [package]' },
|
|
499
502
|
];
|
|
500
503
|
async function runToolCommand(tool, args, outputJson, comm) {
|
|
501
504
|
const a = await getAgent();
|
|
@@ -891,6 +894,38 @@ async function runToolCommand(tool, args, outputJson, comm) {
|
|
|
891
894
|
response = `✅ 已添加用户行动: ${content.substring(0, 50)}...`;
|
|
892
895
|
break;
|
|
893
896
|
}
|
|
897
|
+
// ==================== Update Commands ====================
|
|
898
|
+
case 'update-check': {
|
|
899
|
+
const { checkForUpdates } = await import('./utils/auto-update.js');
|
|
900
|
+
const info = await checkForUpdates();
|
|
901
|
+
if (info && info.outdated) {
|
|
902
|
+
response = `📦 发现更新可用:\n\n` +
|
|
903
|
+
`当前版本: ${info.version}\n` +
|
|
904
|
+
`最新版本: ${info.latest}\n\n` +
|
|
905
|
+
`待更新包:\n${info.packages.map(p => ` - ${p.name}: ${p.current} → ${p.latest}`).join('\n')}\n\n` +
|
|
906
|
+
`运行 --update-now 进行更新`;
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
response = `✅ 已是最新版本 (${info?.version || 'unknown'})`;
|
|
910
|
+
}
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
case 'update-now': {
|
|
914
|
+
const { performUpdate } = await import('./utils/auto-update.js');
|
|
915
|
+
const packages = args.length > 0 ? args : undefined;
|
|
916
|
+
const result = await performUpdate(packages);
|
|
917
|
+
if (result.success) {
|
|
918
|
+
response = `✅ 更新成功${result.updatedPackages ? `: ${result.updatedPackages.join(', ')}` : ''}`;
|
|
919
|
+
if (result.updated) {
|
|
920
|
+
response += `\n\n${YELLOW}请重新启动应用以使用新版本${RESET}`;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
response = `❌ 更新失败: ${result.error}`;
|
|
925
|
+
error = response;
|
|
926
|
+
}
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
894
929
|
default:
|
|
895
930
|
response = `错误: 未知工具 "${tool}"`;
|
|
896
931
|
error = response;
|
|
@@ -1155,6 +1190,19 @@ function parseArgs() {
|
|
|
1155
1190
|
}
|
|
1156
1191
|
result.toolArgs = sessionArgs;
|
|
1157
1192
|
break;
|
|
1193
|
+
case '--update-check':
|
|
1194
|
+
result.updateCheck = true;
|
|
1195
|
+
result.tool = 'update-check';
|
|
1196
|
+
break;
|
|
1197
|
+
case '--update-now':
|
|
1198
|
+
result.updateNow = true;
|
|
1199
|
+
result.tool = 'update-now';
|
|
1200
|
+
const updateArgs = [];
|
|
1201
|
+
while (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
1202
|
+
updateArgs.push(args[++i]);
|
|
1203
|
+
}
|
|
1204
|
+
result.toolArgs = updateArgs;
|
|
1205
|
+
break;
|
|
1158
1206
|
case '--tui':
|
|
1159
1207
|
result.tui = true;
|
|
1160
1208
|
break;
|
|
@@ -1224,6 +1272,10 @@ function printHelp() {
|
|
|
1224
1272
|
--harness-sessions 列出 Session 归档记录
|
|
1225
1273
|
--harness-session-context [id] 获取 Session 上下文
|
|
1226
1274
|
|
|
1275
|
+
# 自动更新
|
|
1276
|
+
--update-check 检查 npm 包更新
|
|
1277
|
+
--update-now [pkg] 更新到最新版本
|
|
1278
|
+
|
|
1227
1279
|
# AI 对话
|
|
1228
1280
|
--prompt, -p <text> 通用 AI 对话(默认)
|
|
1229
1281
|
--model <name> 指定使用的模型
|
|
@@ -1272,6 +1324,13 @@ async function main() {
|
|
|
1272
1324
|
printHelp();
|
|
1273
1325
|
process.exit(0);
|
|
1274
1326
|
}
|
|
1327
|
+
// 启动时检查 npm 包更新
|
|
1328
|
+
const updateResult = await checkAndUpdate();
|
|
1329
|
+
if (updateResult.hasUpdate && updateResult.updated) {
|
|
1330
|
+
console.log(`\n${YELLOW}更新完成,请重新启动 bolloon${RESET}\n`);
|
|
1331
|
+
// 更新完成后退出,让用户重新启动
|
|
1332
|
+
process.exit(0);
|
|
1333
|
+
}
|
|
1275
1334
|
const mode = args.web ? 'web' : 'cli';
|
|
1276
1335
|
const isNonInteractive = !!(args.tool || args.prompt);
|
|
1277
1336
|
const isTuiMode = mode === 'cli' && !isNonInteractive && args.tui;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm 自动更新检查器
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 启动时检查 npm 注册表是否有新版本
|
|
6
|
+
* - 自动下载并安装最新版本
|
|
7
|
+
* - 支持增量更新(只更新新增的包)
|
|
8
|
+
*/
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as https from 'https';
|
|
13
|
+
import * as http from 'http';
|
|
14
|
+
// ANSI 颜色
|
|
15
|
+
const RESET = '\x1b[0m';
|
|
16
|
+
const BOLD = '\x1b[1m';
|
|
17
|
+
const CYAN = '\x1b[36m';
|
|
18
|
+
const GREEN = '\x1b[32m';
|
|
19
|
+
const YELLOW = '\x1b[33m';
|
|
20
|
+
const MAGENTA = '\x1b[35m';
|
|
21
|
+
function log(msg, color = RESET) {
|
|
22
|
+
process.stdout.write(`${color}${msg}${RESET}`);
|
|
23
|
+
}
|
|
24
|
+
function httpGet(url) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const client = url.startsWith('https') ? https : http;
|
|
27
|
+
const req = client.get(url, { timeout: 10000 }, (res) => {
|
|
28
|
+
let data = '';
|
|
29
|
+
res.on('data', (chunk) => data += chunk);
|
|
30
|
+
res.on('end', () => resolve(data));
|
|
31
|
+
});
|
|
32
|
+
req.on('error', reject);
|
|
33
|
+
req.on('timeout', () => {
|
|
34
|
+
req.destroy();
|
|
35
|
+
reject(new Error('请求超时'));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 获取当前安装的包版本
|
|
41
|
+
*/
|
|
42
|
+
function getInstalledVersion(packageName) {
|
|
43
|
+
try {
|
|
44
|
+
const packageJsonPath = findPackageJson(packageName);
|
|
45
|
+
if (packageJsonPath) {
|
|
46
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
47
|
+
return pkg.version || null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
// 忽略错误
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 查找包的 package.json 路径
|
|
57
|
+
*/
|
|
58
|
+
function findPackageJson(packageName) {
|
|
59
|
+
// 检查当前项目的 node_modules
|
|
60
|
+
const checkPaths = [
|
|
61
|
+
path.join(process.cwd(), 'node_modules', packageName, 'package.json'),
|
|
62
|
+
path.join(process.cwd(), 'node_modules', '@' + packageName.split('/')[0], packageName.split('/')[1] || '', 'package.json'),
|
|
63
|
+
];
|
|
64
|
+
for (const p of checkPaths) {
|
|
65
|
+
if (fs.existsSync(p))
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 检查 npm 注册表获取最新版本
|
|
72
|
+
*/
|
|
73
|
+
async function getLatestVersion(packageName) {
|
|
74
|
+
try {
|
|
75
|
+
const encodedName = encodeURIComponent(packageName);
|
|
76
|
+
const url = `https://registry.npmjs.org/${encodedName}`;
|
|
77
|
+
const response = await httpGet(url);
|
|
78
|
+
const data = JSON.parse(response);
|
|
79
|
+
return data['dist-tags']?.latest || null;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 比较版本号
|
|
87
|
+
*/
|
|
88
|
+
function compareVersions(current, latest) {
|
|
89
|
+
const currentParts = current.split('.').map(Number);
|
|
90
|
+
const latestParts = latest.split('.').map(Number);
|
|
91
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
92
|
+
const c = currentParts[i] || 0;
|
|
93
|
+
const l = latestParts[i] || 0;
|
|
94
|
+
if (c < l)
|
|
95
|
+
return -1;
|
|
96
|
+
if (c > l)
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 检查 @bolloon 相关的包是否有更新
|
|
103
|
+
*/
|
|
104
|
+
async function checkBolloonUpdates() {
|
|
105
|
+
const packagesToCheck = [
|
|
106
|
+
'@bolloon/bolloon-agent',
|
|
107
|
+
'@bolloon/constraint-runtime',
|
|
108
|
+
];
|
|
109
|
+
const outdatedPackages = [];
|
|
110
|
+
let hasUpdate = false;
|
|
111
|
+
let currentVersion = '';
|
|
112
|
+
let latestVersion = '';
|
|
113
|
+
for (const pkg of packagesToCheck) {
|
|
114
|
+
const installed = getInstalledVersion(pkg);
|
|
115
|
+
if (!installed)
|
|
116
|
+
continue;
|
|
117
|
+
currentVersion = installed;
|
|
118
|
+
const latest = await getLatestVersion(pkg);
|
|
119
|
+
if (latest && compareVersions(installed, latest) < 0) {
|
|
120
|
+
hasUpdate = true;
|
|
121
|
+
latestVersion = latest;
|
|
122
|
+
outdatedPackages.push({
|
|
123
|
+
name: pkg,
|
|
124
|
+
current: installed,
|
|
125
|
+
wanted: latest,
|
|
126
|
+
latest,
|
|
127
|
+
location: pkg
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (outdatedPackages.length === 0) {
|
|
132
|
+
return {
|
|
133
|
+
name: '@bolloon/bolloon-agent',
|
|
134
|
+
version: currentVersion,
|
|
135
|
+
latest: currentVersion,
|
|
136
|
+
outdated: false,
|
|
137
|
+
packages: []
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
name: '@bolloon/bolloon-agent',
|
|
142
|
+
version: currentVersion,
|
|
143
|
+
latest: latestVersion,
|
|
144
|
+
outdated: hasUpdate,
|
|
145
|
+
packages: outdatedPackages
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 使用 npm outdated 检查所有包的更新
|
|
150
|
+
*/
|
|
151
|
+
function checkNpmOutdated() {
|
|
152
|
+
try {
|
|
153
|
+
const output = execSync('npm outdated --json', {
|
|
154
|
+
encoding: 'utf-8',
|
|
155
|
+
timeout: 30000,
|
|
156
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
157
|
+
cwd: process.cwd()
|
|
158
|
+
});
|
|
159
|
+
if (!output.trim())
|
|
160
|
+
return [];
|
|
161
|
+
const data = JSON.parse(output);
|
|
162
|
+
const packages = [];
|
|
163
|
+
for (const [name, info] of Object.entries(data)) {
|
|
164
|
+
const pkg = info;
|
|
165
|
+
packages.push({
|
|
166
|
+
name,
|
|
167
|
+
current: pkg.current || '0.0.0',
|
|
168
|
+
wanted: pkg.wanted || '0.0.0',
|
|
169
|
+
latest: pkg.latest || '0.0.0',
|
|
170
|
+
location: pkg.location || name
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return packages;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
// npm outdated 在没有过时包时会返回非零退出码
|
|
177
|
+
if (e.status !== 0 && !e.message?.includes('npm outdated')) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 自动更新 npm 包
|
|
185
|
+
*/
|
|
186
|
+
async function updatePackages(packages) {
|
|
187
|
+
try {
|
|
188
|
+
const args = packages && packages.length > 0
|
|
189
|
+
? ['npm', 'install', ...packages, '--save']
|
|
190
|
+
: ['npm', 'install', '-g', '@bolloon/bolloon-agent'];
|
|
191
|
+
log(`\n${CYAN}📦 正在更新包...${RESET}\n`, RESET);
|
|
192
|
+
// 执行 npm install
|
|
193
|
+
const result = execSync(args.join(' '), {
|
|
194
|
+
encoding: 'utf-8',
|
|
195
|
+
timeout: 300000, // 5分钟超时
|
|
196
|
+
stdio: 'inherit',
|
|
197
|
+
cwd: process.cwd()
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
updated: true,
|
|
202
|
+
message: '更新成功',
|
|
203
|
+
updatedPackages: packages
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
updated: false,
|
|
210
|
+
message: '更新失败',
|
|
211
|
+
error: e.message
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 检查并自动更新(启动时调用)
|
|
217
|
+
*/
|
|
218
|
+
export async function checkAndUpdate() {
|
|
219
|
+
// 检查是否有 --no-update 标志
|
|
220
|
+
if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
|
|
221
|
+
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
|
|
222
|
+
}
|
|
223
|
+
// 检查环境变量
|
|
224
|
+
if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
|
|
225
|
+
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
|
|
226
|
+
}
|
|
227
|
+
log(`\n${CYAN}🔍 检查更新...${RESET}`, RESET);
|
|
228
|
+
try {
|
|
229
|
+
// 检查 @bolloon 包的更新
|
|
230
|
+
const bolloonInfo = await checkBolloonUpdates();
|
|
231
|
+
if (bolloonInfo && bolloonInfo.outdated) {
|
|
232
|
+
log(`\n${YELLOW}⚠️ 发现新版本: ${bolloonInfo.latest}${RESET}\n`, RESET);
|
|
233
|
+
log(` 当前版本: ${bolloonInfo.version}\n`, RESET);
|
|
234
|
+
log(` 最新版本: ${bolloonInfo.latest}\n\n`, RESET);
|
|
235
|
+
// 自动更新
|
|
236
|
+
const result = await updatePackages(bolloonInfo.packages.map(p => p.name));
|
|
237
|
+
if (result.success) {
|
|
238
|
+
log(`\n${GREEN}✅ 更新成功!请重新启动应用${RESET}\n`, RESET);
|
|
239
|
+
// 提示用户重启
|
|
240
|
+
log(`${YELLOW}💡 请重新运行 bolloon 以使用新版本${RESET}\n\n`, RESET);
|
|
241
|
+
// 通知主进程更新完成
|
|
242
|
+
process.emit('bolloon-update-complete', result);
|
|
243
|
+
return {
|
|
244
|
+
hasUpdate: true,
|
|
245
|
+
info: bolloonInfo,
|
|
246
|
+
updated: true,
|
|
247
|
+
message: `已更新到 ${bolloonInfo.latest}`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
return {
|
|
252
|
+
hasUpdate: true,
|
|
253
|
+
info: bolloonInfo,
|
|
254
|
+
updated: false,
|
|
255
|
+
message: `更新失败: ${result.error}`
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
log(` ${GREEN}✓${RESET} 已是最新版本 (${bolloonInfo?.version || 'unknown'})\n`, RESET);
|
|
261
|
+
return {
|
|
262
|
+
hasUpdate: false,
|
|
263
|
+
info: bolloonInfo,
|
|
264
|
+
updated: false,
|
|
265
|
+
message: '已是最新版本'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
log(` ${YELLOW}⚠${RESET} 更新检查失败: ${e.message}\n`, RESET);
|
|
271
|
+
return {
|
|
272
|
+
hasUpdate: false,
|
|
273
|
+
info: null,
|
|
274
|
+
updated: false,
|
|
275
|
+
message: `检查失败: ${e.message}`
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 仅检查更新,不自动安装
|
|
281
|
+
*/
|
|
282
|
+
export async function checkForUpdates() {
|
|
283
|
+
return await checkBolloonUpdates();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 手动触发更新
|
|
287
|
+
*/
|
|
288
|
+
export async function performUpdate(packages) {
|
|
289
|
+
return await updatePackages(packages);
|
|
290
|
+
}
|
|
291
|
+
// CLI 入口
|
|
292
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
293
|
+
(async () => {
|
|
294
|
+
const command = process.argv[2];
|
|
295
|
+
switch (command) {
|
|
296
|
+
case 'check':
|
|
297
|
+
const info = await checkForUpdates();
|
|
298
|
+
if (info) {
|
|
299
|
+
console.log(JSON.stringify(info, null, 2));
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case 'update':
|
|
303
|
+
const result = await performUpdate(process.argv.slice(3));
|
|
304
|
+
console.log(JSON.stringify(result, null, 2));
|
|
305
|
+
break;
|
|
306
|
+
default:
|
|
307
|
+
await checkAndUpdate();
|
|
308
|
+
}
|
|
309
|
+
})();
|
|
310
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bolloon/bolloon-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "P2P AI Document Agent - 全局安装后执行 `bolloon` 启动产品",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"src/constraint-runtime"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@bolloon/bolloon-agent": "^0.1.
|
|
35
|
+
"@bolloon/bolloon-agent": "^0.1.9",
|
|
36
36
|
"@bolloon/constraint-runtime": "0.1.0",
|
|
37
37
|
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
38
38
|
"@chainsafe/libp2p-yamux": "^8.0.1",
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { createSubAgentManager } from './agents/subagent-manager.js';
|
|
|
22
22
|
import { getGlobalSharedContext } from './social/global-shared-context.js';
|
|
23
23
|
import { BollharnessIntegration, createBollharnessIntegration } from './bollharness-integration/index.js';
|
|
24
24
|
import * as readline from 'readline';
|
|
25
|
+
import { checkAndUpdate } from './utils/auto-update.js';
|
|
25
26
|
|
|
26
27
|
const RESET = '\x1b[0m';
|
|
27
28
|
const BOLD = '\x1b[1m';
|
|
@@ -601,6 +602,8 @@ const AVAILABLE_TOOLS = [
|
|
|
601
602
|
{ name: 'harness_classify', description: '分类变更类型', example: '--harness-classify <description>' },
|
|
602
603
|
{ name: 'harness_context', description: '获取文件上下文', example: '--harness-context <file>' },
|
|
603
604
|
{ name: 'harness_check', description: '执行 Guard 检查', example: '--harness-check <file>' },
|
|
605
|
+
{ name: 'update_check', description: '检查 npm 包更新', example: '--update-check' },
|
|
606
|
+
{ name: 'update_now', description: '立即更新到最新版本', example: '--update-now [package]' },
|
|
604
607
|
];
|
|
605
608
|
|
|
606
609
|
async function runToolCommand(
|
|
@@ -1036,6 +1039,39 @@ async function runToolCommand(
|
|
|
1036
1039
|
break;
|
|
1037
1040
|
}
|
|
1038
1041
|
|
|
1042
|
+
// ==================== Update Commands ====================
|
|
1043
|
+
|
|
1044
|
+
case 'update-check': {
|
|
1045
|
+
const { checkForUpdates } = await import('./utils/auto-update.js');
|
|
1046
|
+
const info = await checkForUpdates();
|
|
1047
|
+
if (info && info.outdated) {
|
|
1048
|
+
response = `📦 发现更新可用:\n\n` +
|
|
1049
|
+
`当前版本: ${info.version}\n` +
|
|
1050
|
+
`最新版本: ${info.latest}\n\n` +
|
|
1051
|
+
`待更新包:\n${info.packages.map(p => ` - ${p.name}: ${p.current} → ${p.latest}`).join('\n')}\n\n` +
|
|
1052
|
+
`运行 --update-now 进行更新`;
|
|
1053
|
+
} else {
|
|
1054
|
+
response = `✅ 已是最新版本 (${info?.version || 'unknown'})`;
|
|
1055
|
+
}
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
case 'update-now': {
|
|
1060
|
+
const { performUpdate } = await import('./utils/auto-update.js');
|
|
1061
|
+
const packages = args.length > 0 ? args : undefined;
|
|
1062
|
+
const result = await performUpdate(packages as string[] | undefined);
|
|
1063
|
+
if (result.success) {
|
|
1064
|
+
response = `✅ 更新成功${result.updatedPackages ? `: ${result.updatedPackages.join(', ')}` : ''}`;
|
|
1065
|
+
if (result.updated) {
|
|
1066
|
+
response += `\n\n${YELLOW}请重新启动应用以使用新版本${RESET}`;
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
response = `❌ 更新失败: ${result.error}`;
|
|
1070
|
+
error = response;
|
|
1071
|
+
}
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1039
1075
|
default:
|
|
1040
1076
|
response = `错误: 未知工具 "${tool}"`;
|
|
1041
1077
|
error = response;
|
|
@@ -1151,6 +1187,8 @@ interface ParsedArgs {
|
|
|
1151
1187
|
globalAgents?: boolean;
|
|
1152
1188
|
addAction?: boolean;
|
|
1153
1189
|
tui?: boolean;
|
|
1190
|
+
updateCheck?: boolean;
|
|
1191
|
+
updateNow?: boolean;
|
|
1154
1192
|
}
|
|
1155
1193
|
|
|
1156
1194
|
function parseArgs(): ParsedArgs {
|
|
@@ -1338,6 +1376,19 @@ function parseArgs(): ParsedArgs {
|
|
|
1338
1376
|
}
|
|
1339
1377
|
result.toolArgs = sessionArgs;
|
|
1340
1378
|
break;
|
|
1379
|
+
case '--update-check':
|
|
1380
|
+
result.updateCheck = true;
|
|
1381
|
+
result.tool = 'update-check';
|
|
1382
|
+
break;
|
|
1383
|
+
case '--update-now':
|
|
1384
|
+
result.updateNow = true;
|
|
1385
|
+
result.tool = 'update-now';
|
|
1386
|
+
const updateArgs: string[] = [];
|
|
1387
|
+
while (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
1388
|
+
updateArgs.push(args[++i]);
|
|
1389
|
+
}
|
|
1390
|
+
result.toolArgs = updateArgs;
|
|
1391
|
+
break;
|
|
1341
1392
|
case '--tui':
|
|
1342
1393
|
result.tui = true;
|
|
1343
1394
|
break;
|
|
@@ -1409,6 +1460,10 @@ function printHelp(): void {
|
|
|
1409
1460
|
--harness-sessions 列出 Session 归档记录
|
|
1410
1461
|
--harness-session-context [id] 获取 Session 上下文
|
|
1411
1462
|
|
|
1463
|
+
# 自动更新
|
|
1464
|
+
--update-check 检查 npm 包更新
|
|
1465
|
+
--update-now [pkg] 更新到最新版本
|
|
1466
|
+
|
|
1412
1467
|
# AI 对话
|
|
1413
1468
|
--prompt, -p <text> 通用 AI 对话(默认)
|
|
1414
1469
|
--model <name> 指定使用的模型
|
|
@@ -1461,6 +1516,14 @@ async function main() {
|
|
|
1461
1516
|
process.exit(0);
|
|
1462
1517
|
}
|
|
1463
1518
|
|
|
1519
|
+
// 启动时检查 npm 包更新
|
|
1520
|
+
const updateResult = await checkAndUpdate();
|
|
1521
|
+
if (updateResult.hasUpdate && updateResult.updated) {
|
|
1522
|
+
console.log(`\n${YELLOW}更新完成,请重新启动 bolloon${RESET}\n`);
|
|
1523
|
+
// 更新完成后退出,让用户重新启动
|
|
1524
|
+
process.exit(0);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1464
1527
|
const mode = args.web ? 'web' : 'cli';
|
|
1465
1528
|
const isNonInteractive = !!(args.tool || args.prompt);
|
|
1466
1529
|
const isTuiMode = mode === 'cli' && !isNonInteractive && args.tui;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm 自动更新检查器
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 启动时检查 npm 注册表是否有新版本
|
|
6
|
+
* - 自动下载并安装最新版本
|
|
7
|
+
* - 支持增量更新(只更新新增的包)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as https from 'https';
|
|
14
|
+
import * as http from 'http';
|
|
15
|
+
|
|
16
|
+
// ANSI 颜色
|
|
17
|
+
const RESET = '\x1b[0m';
|
|
18
|
+
const BOLD = '\x1b[1m';
|
|
19
|
+
const CYAN = '\x1b[36m';
|
|
20
|
+
const GREEN = '\x1b[32m';
|
|
21
|
+
const YELLOW = '\x1b[33m';
|
|
22
|
+
const MAGENTA = '\x1b[35m';
|
|
23
|
+
|
|
24
|
+
interface PackageInfo {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
latest: string;
|
|
28
|
+
outdated: boolean;
|
|
29
|
+
packages: OutdatedPackage[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface OutdatedPackage {
|
|
33
|
+
name: string;
|
|
34
|
+
current: string;
|
|
35
|
+
wanted: string;
|
|
36
|
+
latest: string;
|
|
37
|
+
location: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface UpdateResult {
|
|
41
|
+
success: boolean;
|
|
42
|
+
updated: boolean;
|
|
43
|
+
message: string;
|
|
44
|
+
updatedPackages?: string[];
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function log(msg: string, color: string = RESET) {
|
|
49
|
+
process.stdout.write(`${color}${msg}${RESET}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function httpGet(url: string): Promise<string> {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const client = url.startsWith('https') ? https : http;
|
|
55
|
+
const req = client.get(url, { timeout: 10000 }, (res: any) => {
|
|
56
|
+
let data = '';
|
|
57
|
+
res.on('data', (chunk: string) => data += chunk);
|
|
58
|
+
res.on('end', () => resolve(data));
|
|
59
|
+
});
|
|
60
|
+
req.on('error', reject);
|
|
61
|
+
req.on('timeout', () => {
|
|
62
|
+
req.destroy();
|
|
63
|
+
reject(new Error('请求超时'));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取当前安装的包版本
|
|
70
|
+
*/
|
|
71
|
+
function getInstalledVersion(packageName: string): string | null {
|
|
72
|
+
try {
|
|
73
|
+
const packageJsonPath = findPackageJson(packageName);
|
|
74
|
+
if (packageJsonPath) {
|
|
75
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
76
|
+
return pkg.version || null;
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// 忽略错误
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 查找包的 package.json 路径
|
|
86
|
+
*/
|
|
87
|
+
function findPackageJson(packageName: string): string | null {
|
|
88
|
+
// 检查当前项目的 node_modules
|
|
89
|
+
const checkPaths = [
|
|
90
|
+
path.join(process.cwd(), 'node_modules', packageName, 'package.json'),
|
|
91
|
+
path.join(process.cwd(), 'node_modules', '@' + packageName.split('/')[0], packageName.split('/')[1] || '', 'package.json'),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (const p of checkPaths) {
|
|
95
|
+
if (fs.existsSync(p)) return p;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 检查 npm 注册表获取最新版本
|
|
102
|
+
*/
|
|
103
|
+
async function getLatestVersion(packageName: string): Promise<string | null> {
|
|
104
|
+
try {
|
|
105
|
+
const encodedName = encodeURIComponent(packageName);
|
|
106
|
+
const url = `https://registry.npmjs.org/${encodedName}`;
|
|
107
|
+
const response = await httpGet(url);
|
|
108
|
+
const data = JSON.parse(response);
|
|
109
|
+
return data['dist-tags']?.latest || null;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 比较版本号
|
|
117
|
+
*/
|
|
118
|
+
function compareVersions(current: string, latest: string): -1 | 0 | 1 {
|
|
119
|
+
const currentParts = current.split('.').map(Number);
|
|
120
|
+
const latestParts = latest.split('.').map(Number);
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
123
|
+
const c = currentParts[i] || 0;
|
|
124
|
+
const l = latestParts[i] || 0;
|
|
125
|
+
if (c < l) return -1;
|
|
126
|
+
if (c > l) return 1;
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 检查 @bolloon 相关的包是否有更新
|
|
133
|
+
*/
|
|
134
|
+
async function checkBolloonUpdates(): Promise<PackageInfo | null> {
|
|
135
|
+
const packagesToCheck = [
|
|
136
|
+
'@bolloon/bolloon-agent',
|
|
137
|
+
'@bolloon/constraint-runtime',
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const outdatedPackages: OutdatedPackage[] = [];
|
|
141
|
+
let hasUpdate = false;
|
|
142
|
+
let currentVersion = '';
|
|
143
|
+
let latestVersion = '';
|
|
144
|
+
|
|
145
|
+
for (const pkg of packagesToCheck) {
|
|
146
|
+
const installed = getInstalledVersion(pkg);
|
|
147
|
+
if (!installed) continue;
|
|
148
|
+
|
|
149
|
+
currentVersion = installed;
|
|
150
|
+
const latest = await getLatestVersion(pkg);
|
|
151
|
+
|
|
152
|
+
if (latest && compareVersions(installed, latest) < 0) {
|
|
153
|
+
hasUpdate = true;
|
|
154
|
+
latestVersion = latest;
|
|
155
|
+
outdatedPackages.push({
|
|
156
|
+
name: pkg,
|
|
157
|
+
current: installed,
|
|
158
|
+
wanted: latest,
|
|
159
|
+
latest,
|
|
160
|
+
location: pkg
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (outdatedPackages.length === 0) {
|
|
166
|
+
return {
|
|
167
|
+
name: '@bolloon/bolloon-agent',
|
|
168
|
+
version: currentVersion,
|
|
169
|
+
latest: currentVersion,
|
|
170
|
+
outdated: false,
|
|
171
|
+
packages: []
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
name: '@bolloon/bolloon-agent',
|
|
177
|
+
version: currentVersion,
|
|
178
|
+
latest: latestVersion,
|
|
179
|
+
outdated: hasUpdate,
|
|
180
|
+
packages: outdatedPackages
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 使用 npm outdated 检查所有包的更新
|
|
186
|
+
*/
|
|
187
|
+
function checkNpmOutdated(): OutdatedPackage[] {
|
|
188
|
+
try {
|
|
189
|
+
const output = execSync('npm outdated --json', {
|
|
190
|
+
encoding: 'utf-8',
|
|
191
|
+
timeout: 30000,
|
|
192
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
193
|
+
cwd: process.cwd()
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!output.trim()) return [];
|
|
197
|
+
|
|
198
|
+
const data = JSON.parse(output);
|
|
199
|
+
const packages: OutdatedPackage[] = [];
|
|
200
|
+
|
|
201
|
+
for (const [name, info] of Object.entries(data)) {
|
|
202
|
+
const pkg = info as any;
|
|
203
|
+
packages.push({
|
|
204
|
+
name,
|
|
205
|
+
current: pkg.current || '0.0.0',
|
|
206
|
+
wanted: pkg.wanted || '0.0.0',
|
|
207
|
+
latest: pkg.latest || '0.0.0',
|
|
208
|
+
location: pkg.location || name
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return packages;
|
|
213
|
+
} catch (e: any) {
|
|
214
|
+
// npm outdated 在没有过时包时会返回非零退出码
|
|
215
|
+
if (e.status !== 0 && !e.message?.includes('npm outdated')) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 自动更新 npm 包
|
|
224
|
+
*/
|
|
225
|
+
async function updatePackages(packages?: string[]): Promise<UpdateResult> {
|
|
226
|
+
try {
|
|
227
|
+
const args = packages && packages.length > 0
|
|
228
|
+
? ['npm', 'install', ...packages, '--save']
|
|
229
|
+
: ['npm', 'install', '-g', '@bolloon/bolloon-agent'];
|
|
230
|
+
|
|
231
|
+
log(`\n${CYAN}📦 正在更新包...${RESET}\n`, RESET);
|
|
232
|
+
|
|
233
|
+
// 执行 npm install
|
|
234
|
+
const result = execSync(args.join(' '), {
|
|
235
|
+
encoding: 'utf-8',
|
|
236
|
+
timeout: 300000, // 5分钟超时
|
|
237
|
+
stdio: 'inherit',
|
|
238
|
+
cwd: process.cwd()
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
updated: true,
|
|
244
|
+
message: '更新成功',
|
|
245
|
+
updatedPackages: packages
|
|
246
|
+
};
|
|
247
|
+
} catch (e: any) {
|
|
248
|
+
return {
|
|
249
|
+
success: false,
|
|
250
|
+
updated: false,
|
|
251
|
+
message: '更新失败',
|
|
252
|
+
error: e.message
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 检查并自动更新(启动时调用)
|
|
259
|
+
*/
|
|
260
|
+
export async function checkAndUpdate(): Promise<{
|
|
261
|
+
hasUpdate: boolean;
|
|
262
|
+
info: PackageInfo | null;
|
|
263
|
+
updated: boolean;
|
|
264
|
+
message: string;
|
|
265
|
+
}> {
|
|
266
|
+
// 检查是否有 --no-update 标志
|
|
267
|
+
if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
|
|
268
|
+
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 检查环境变量
|
|
272
|
+
if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
|
|
273
|
+
return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
log(`\n${CYAN}🔍 检查更新...${RESET}`, RESET);
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// 检查 @bolloon 包的更新
|
|
280
|
+
const bolloonInfo = await checkBolloonUpdates();
|
|
281
|
+
|
|
282
|
+
if (bolloonInfo && bolloonInfo.outdated) {
|
|
283
|
+
log(`\n${YELLOW}⚠️ 发现新版本: ${bolloonInfo.latest}${RESET}\n`, RESET);
|
|
284
|
+
log(` 当前版本: ${bolloonInfo.version}\n`, RESET);
|
|
285
|
+
log(` 最新版本: ${bolloonInfo.latest}\n\n`, RESET);
|
|
286
|
+
|
|
287
|
+
// 自动更新
|
|
288
|
+
const result = await updatePackages(bolloonInfo.packages.map(p => p.name));
|
|
289
|
+
|
|
290
|
+
if (result.success) {
|
|
291
|
+
log(`\n${GREEN}✅ 更新成功!请重新启动应用${RESET}\n`, RESET);
|
|
292
|
+
|
|
293
|
+
// 提示用户重启
|
|
294
|
+
log(`${YELLOW}💡 请重新运行 bolloon 以使用新版本${RESET}\n\n`, RESET);
|
|
295
|
+
|
|
296
|
+
// 通知主进程更新完成
|
|
297
|
+
process.emit('bolloon-update-complete', result);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
hasUpdate: true,
|
|
301
|
+
info: bolloonInfo,
|
|
302
|
+
updated: true,
|
|
303
|
+
message: `已更新到 ${bolloonInfo.latest}`
|
|
304
|
+
};
|
|
305
|
+
} else {
|
|
306
|
+
return {
|
|
307
|
+
hasUpdate: true,
|
|
308
|
+
info: bolloonInfo,
|
|
309
|
+
updated: false,
|
|
310
|
+
message: `更新失败: ${result.error}`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
log(` ${GREEN}✓${RESET} 已是最新版本 (${bolloonInfo?.version || 'unknown'})\n`, RESET);
|
|
315
|
+
return {
|
|
316
|
+
hasUpdate: false,
|
|
317
|
+
info: bolloonInfo,
|
|
318
|
+
updated: false,
|
|
319
|
+
message: '已是最新版本'
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
} catch (e: any) {
|
|
323
|
+
log(` ${YELLOW}⚠${RESET} 更新检查失败: ${e.message}\n`, RESET);
|
|
324
|
+
return {
|
|
325
|
+
hasUpdate: false,
|
|
326
|
+
info: null,
|
|
327
|
+
updated: false,
|
|
328
|
+
message: `检查失败: ${e.message}`
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 仅检查更新,不自动安装
|
|
335
|
+
*/
|
|
336
|
+
export async function checkForUpdates(): Promise<PackageInfo | null> {
|
|
337
|
+
return await checkBolloonUpdates();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 手动触发更新
|
|
342
|
+
*/
|
|
343
|
+
export async function performUpdate(packages?: string[]): Promise<UpdateResult> {
|
|
344
|
+
return await updatePackages(packages);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// CLI 入口
|
|
348
|
+
if (process.argv[1]?.includes('auto-update')) {
|
|
349
|
+
(async () => {
|
|
350
|
+
const command = process.argv[2];
|
|
351
|
+
|
|
352
|
+
switch (command) {
|
|
353
|
+
case 'check':
|
|
354
|
+
const info = await checkForUpdates();
|
|
355
|
+
if (info) {
|
|
356
|
+
console.log(JSON.stringify(info, null, 2));
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
|
|
360
|
+
case 'update':
|
|
361
|
+
const result = await performUpdate(process.argv.slice(3));
|
|
362
|
+
console.log(JSON.stringify(result, null, 2));
|
|
363
|
+
break;
|
|
364
|
+
|
|
365
|
+
default:
|
|
366
|
+
await checkAndUpdate();
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
}
|