@bolloon/bolloon-agent 0.1.9 → 0.1.11

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.cjs CHANGED
File without changes
package/dist/cli-entry.js CHANGED
@@ -22,7 +22,7 @@ const YELLOW = '\x1b[33m';
22
22
  const GREEN = '\x1b[32m';
23
23
  const MAGENTA = '\x1b[35m';
24
24
  // 版本信息
25
- const VERSION = '0.1.1';
25
+ const VERSION = '0.1.11';
26
26
  function log(msg, color = RESET) {
27
27
  console.log(`${color}${msg}${RESET}`);
28
28
  }
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,353 @@
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
+ * 获取 bolloon 全局安装目录
41
+ */
42
+ function getGlobalBolloonDir() {
43
+ const possiblePaths = [
44
+ path.join(process.env.HOME || '', '.npm-global/lib/node_modules/@bolloon/bolloon-agent'),
45
+ path.join(process.env.PREFIX || '/usr/local', 'lib/node_modules/@bolloon/bolloon-agent'),
46
+ ];
47
+ for (const p of possiblePaths) {
48
+ if (fs.existsSync(path.join(p, 'package.json'))) {
49
+ return p;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * 获取当前安装的包版本
56
+ * 优先使用全局安装的版本(更准确反映实际运行的版本)
57
+ */
58
+ function getInstalledVersion(packageName) {
59
+ // 对于 @bolloon/bolloon-agent,始终优先从全局安装位置读取版本
60
+ // 这样可以准确检测实际安装的版本,而不受 cwd 影响
61
+ if (packageName === '@bolloon/bolloon-agent') {
62
+ const globalDir = getGlobalBolloonDir();
63
+ if (globalDir) {
64
+ const pkgPath = path.join(globalDir, 'package.json');
65
+ try {
66
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
67
+ return pkg.version || null;
68
+ }
69
+ catch (e) {
70
+ // 忽略
71
+ }
72
+ }
73
+ // 回退到本地 package.json
74
+ const localPkgPath = path.join(process.cwd(), 'package.json');
75
+ if (fs.existsSync(localPkgPath)) {
76
+ try {
77
+ const pkg = JSON.parse(fs.readFileSync(localPkgPath, 'utf-8'));
78
+ return pkg.version || null;
79
+ }
80
+ catch (e) {
81
+ // 忽略
82
+ }
83
+ }
84
+ }
85
+ // 检查本地 node_modules
86
+ const packageJsonPath = findPackageJson(packageName);
87
+ if (packageJsonPath) {
88
+ try {
89
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
90
+ return pkg.version || null;
91
+ }
92
+ catch (e) {
93
+ // 忽略错误
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * 查找包的 package.json 路径
100
+ */
101
+ function findPackageJson(packageName) {
102
+ // 检查当前项目的 node_modules
103
+ const checkPaths = [
104
+ path.join(process.cwd(), 'node_modules', packageName, 'package.json'),
105
+ path.join(process.cwd(), 'node_modules', '@' + packageName.split('/')[0], packageName.split('/')[1] || '', 'package.json'),
106
+ ];
107
+ for (const p of checkPaths) {
108
+ if (fs.existsSync(p))
109
+ return p;
110
+ }
111
+ return null;
112
+ }
113
+ /**
114
+ * 检查 npm 注册表获取最新版本
115
+ */
116
+ async function getLatestVersion(packageName) {
117
+ try {
118
+ const encodedName = encodeURIComponent(packageName);
119
+ const url = `https://registry.npmjs.org/${encodedName}`;
120
+ const response = await httpGet(url);
121
+ const data = JSON.parse(response);
122
+ return data['dist-tags']?.latest || null;
123
+ }
124
+ catch (e) {
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * 比较版本号
130
+ */
131
+ function compareVersions(current, latest) {
132
+ const currentParts = current.split('.').map(Number);
133
+ const latestParts = latest.split('.').map(Number);
134
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
135
+ const c = currentParts[i] || 0;
136
+ const l = latestParts[i] || 0;
137
+ if (c < l)
138
+ return -1;
139
+ if (c > l)
140
+ return 1;
141
+ }
142
+ return 0;
143
+ }
144
+ /**
145
+ * 检查 @bolloon 相关的包是否有更新
146
+ */
147
+ async function checkBolloonUpdates() {
148
+ const packagesToCheck = [
149
+ '@bolloon/bolloon-agent',
150
+ '@bolloon/constraint-runtime',
151
+ ];
152
+ const outdatedPackages = [];
153
+ let hasUpdate = false;
154
+ let currentVersion = '';
155
+ let latestVersion = '';
156
+ for (const pkg of packagesToCheck) {
157
+ const installed = getInstalledVersion(pkg);
158
+ if (!installed)
159
+ continue;
160
+ currentVersion = installed;
161
+ const latest = await getLatestVersion(pkg);
162
+ if (latest && compareVersions(installed, latest) < 0) {
163
+ hasUpdate = true;
164
+ latestVersion = latest;
165
+ outdatedPackages.push({
166
+ name: pkg,
167
+ current: installed,
168
+ wanted: latest,
169
+ latest,
170
+ location: pkg
171
+ });
172
+ }
173
+ }
174
+ if (outdatedPackages.length === 0) {
175
+ return {
176
+ name: '@bolloon/bolloon-agent',
177
+ version: currentVersion,
178
+ latest: currentVersion,
179
+ outdated: false,
180
+ packages: []
181
+ };
182
+ }
183
+ return {
184
+ name: '@bolloon/bolloon-agent',
185
+ version: currentVersion,
186
+ latest: latestVersion,
187
+ outdated: hasUpdate,
188
+ packages: outdatedPackages
189
+ };
190
+ }
191
+ /**
192
+ * 使用 npm outdated 检查所有包的更新
193
+ */
194
+ function checkNpmOutdated() {
195
+ try {
196
+ const output = execSync('npm outdated --json', {
197
+ encoding: 'utf-8',
198
+ timeout: 30000,
199
+ maxBuffer: 10 * 1024 * 1024,
200
+ cwd: process.cwd()
201
+ });
202
+ if (!output.trim())
203
+ return [];
204
+ const data = JSON.parse(output);
205
+ const packages = [];
206
+ for (const [name, info] of Object.entries(data)) {
207
+ const pkg = info;
208
+ packages.push({
209
+ name,
210
+ current: pkg.current || '0.0.0',
211
+ wanted: pkg.wanted || '0.0.0',
212
+ latest: pkg.latest || '0.0.0',
213
+ location: pkg.location || name
214
+ });
215
+ }
216
+ return packages;
217
+ }
218
+ catch (e) {
219
+ // npm outdated 在没有过时包时会返回非零退出码
220
+ if (e.status !== 0 && !e.message?.includes('npm outdated')) {
221
+ return [];
222
+ }
223
+ return [];
224
+ }
225
+ }
226
+ /**
227
+ * 自动更新 npm 包
228
+ */
229
+ async function updatePackages(packages) {
230
+ try {
231
+ const args = packages && packages.length > 0
232
+ ? ['npm', 'install', ...packages, '--save']
233
+ : ['npm', 'install', '-g', '@bolloon/bolloon-agent'];
234
+ log(`\n${CYAN}📦 正在更新包...${RESET}\n`, RESET);
235
+ // 执行 npm install
236
+ const result = execSync(args.join(' '), {
237
+ encoding: 'utf-8',
238
+ timeout: 300000, // 5分钟超时
239
+ stdio: 'inherit',
240
+ cwd: process.cwd()
241
+ });
242
+ return {
243
+ success: true,
244
+ updated: true,
245
+ message: '更新成功',
246
+ updatedPackages: packages
247
+ };
248
+ }
249
+ catch (e) {
250
+ return {
251
+ success: false,
252
+ updated: false,
253
+ message: '更新失败',
254
+ error: e.message
255
+ };
256
+ }
257
+ }
258
+ /**
259
+ * 检查并自动更新(启动时调用)
260
+ */
261
+ export async function checkAndUpdate() {
262
+ // 检查是否有 --no-update 标志
263
+ if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
264
+ return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
265
+ }
266
+ // 检查环境变量
267
+ if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
268
+ return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
269
+ }
270
+ log(`\n${CYAN}🔍 检查更新...${RESET}`, RESET);
271
+ try {
272
+ // 检查 @bolloon 包的更新
273
+ const bolloonInfo = await checkBolloonUpdates();
274
+ if (bolloonInfo && bolloonInfo.outdated) {
275
+ log(`\n${YELLOW}⚠️ 发现新版本: ${bolloonInfo.latest}${RESET}\n`, RESET);
276
+ log(` 当前版本: ${bolloonInfo.version}\n`, RESET);
277
+ log(` 最新版本: ${bolloonInfo.latest}\n\n`, RESET);
278
+ // 自动更新
279
+ const result = await updatePackages(bolloonInfo.packages.map(p => p.name));
280
+ if (result.success) {
281
+ log(`\n${GREEN}✅ 更新成功!请重新启动应用${RESET}\n`, RESET);
282
+ // 提示用户重启
283
+ log(`${YELLOW}💡 请重新运行 bolloon 以使用新版本${RESET}\n\n`, RESET);
284
+ // 通知主进程更新完成
285
+ process.emit('bolloon-update-complete', result);
286
+ return {
287
+ hasUpdate: true,
288
+ info: bolloonInfo,
289
+ updated: true,
290
+ message: `已更新到 ${bolloonInfo.latest}`
291
+ };
292
+ }
293
+ else {
294
+ return {
295
+ hasUpdate: true,
296
+ info: bolloonInfo,
297
+ updated: false,
298
+ message: `更新失败: ${result.error}`
299
+ };
300
+ }
301
+ }
302
+ else {
303
+ log(` ${GREEN}✓${RESET} 已是最新版本 (${bolloonInfo?.version || 'unknown'})\n`, RESET);
304
+ return {
305
+ hasUpdate: false,
306
+ info: bolloonInfo,
307
+ updated: false,
308
+ message: '已是最新版本'
309
+ };
310
+ }
311
+ }
312
+ catch (e) {
313
+ log(` ${YELLOW}⚠${RESET} 更新检查失败: ${e.message}\n`, RESET);
314
+ return {
315
+ hasUpdate: false,
316
+ info: null,
317
+ updated: false,
318
+ message: `检查失败: ${e.message}`
319
+ };
320
+ }
321
+ }
322
+ /**
323
+ * 仅检查更新,不自动安装
324
+ */
325
+ export async function checkForUpdates() {
326
+ return await checkBolloonUpdates();
327
+ }
328
+ /**
329
+ * 手动触发更新
330
+ */
331
+ export async function performUpdate(packages) {
332
+ return await updatePackages(packages);
333
+ }
334
+ // CLI 入口
335
+ if (process.argv[1]?.includes('auto-update')) {
336
+ (async () => {
337
+ const command = process.argv[2];
338
+ switch (command) {
339
+ case 'check':
340
+ const info = await checkForUpdates();
341
+ if (info) {
342
+ console.log(JSON.stringify(info, null, 2));
343
+ }
344
+ break;
345
+ case 'update':
346
+ const result = await performUpdate(process.argv.slice(3));
347
+ console.log(JSON.stringify(result, null, 2));
348
+ break;
349
+ default:
350
+ await checkAndUpdate();
351
+ }
352
+ })();
353
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bolloon/bolloon-agent",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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.8",
35
+ "@bolloon/bolloon-agent": "^0.1.11",
36
36
  "@bolloon/constraint-runtime": "0.1.0",
37
37
  "@chainsafe/libp2p-noise": "^17.0.0",
38
38
  "@chainsafe/libp2p-yamux": "^8.0.1",
@@ -47,7 +47,7 @@ function initUserDirs() {
47
47
  const configPath = path.join(bolloonDir, 'config.json');
48
48
  if (!fs.existsSync(configPath)) {
49
49
  const defaultConfig = {
50
- version: '0.1.1',
50
+ version: '0.1.11',
51
51
  initializedAt: new Date().toISOString(),
52
52
  defaults: {
53
53
  port: 54188,
package/src/cli-entry.ts CHANGED
@@ -26,7 +26,7 @@ const GREEN = '\x1b[32m';
26
26
  const MAGENTA = '\x1b[35m';
27
27
 
28
28
  // 版本信息
29
- const VERSION = '0.1.1';
29
+ const VERSION = '0.1.11';
30
30
 
31
31
  function log(msg: string, color: string = RESET) {
32
32
  console.log(`${color}${msg}${RESET}`);
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,413 @@
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
+ * 获取 bolloon 全局安装目录
70
+ */
71
+ function getGlobalBolloonDir(): string | null {
72
+ const possiblePaths = [
73
+ path.join(process.env.HOME || '', '.npm-global/lib/node_modules/@bolloon/bolloon-agent'),
74
+ path.join(process.env.PREFIX || '/usr/local', 'lib/node_modules/@bolloon/bolloon-agent'),
75
+ ];
76
+ for (const p of possiblePaths) {
77
+ if (fs.existsSync(path.join(p, 'package.json'))) {
78
+ return p;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
84
+ /**
85
+ * 获取当前安装的包版本
86
+ * 优先使用全局安装的版本(更准确反映实际运行的版本)
87
+ */
88
+ function getInstalledVersion(packageName: string): string | null {
89
+ // 对于 @bolloon/bolloon-agent,始终优先从全局安装位置读取版本
90
+ // 这样可以准确检测实际安装的版本,而不受 cwd 影响
91
+ if (packageName === '@bolloon/bolloon-agent') {
92
+ const globalDir = getGlobalBolloonDir();
93
+ if (globalDir) {
94
+ const pkgPath = path.join(globalDir, 'package.json');
95
+ try {
96
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
97
+ return pkg.version || null;
98
+ } catch (e) {
99
+ // 忽略
100
+ }
101
+ }
102
+
103
+ // 回退到本地 package.json
104
+ const localPkgPath = path.join(process.cwd(), 'package.json');
105
+ if (fs.existsSync(localPkgPath)) {
106
+ try {
107
+ const pkg = JSON.parse(fs.readFileSync(localPkgPath, 'utf-8'));
108
+ return pkg.version || null;
109
+ } catch (e) {
110
+ // 忽略
111
+ }
112
+ }
113
+ }
114
+
115
+ // 检查本地 node_modules
116
+ const packageJsonPath = findPackageJson(packageName);
117
+ if (packageJsonPath) {
118
+ try {
119
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
120
+ return pkg.version || null;
121
+ } catch (e) {
122
+ // 忽略错误
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * 查找包的 package.json 路径
130
+ */
131
+ function findPackageJson(packageName: string): string | null {
132
+ // 检查当前项目的 node_modules
133
+ const checkPaths = [
134
+ path.join(process.cwd(), 'node_modules', packageName, 'package.json'),
135
+ path.join(process.cwd(), 'node_modules', '@' + packageName.split('/')[0], packageName.split('/')[1] || '', 'package.json'),
136
+ ];
137
+
138
+ for (const p of checkPaths) {
139
+ if (fs.existsSync(p)) return p;
140
+ }
141
+ return null;
142
+ }
143
+
144
+ /**
145
+ * 检查 npm 注册表获取最新版本
146
+ */
147
+ async function getLatestVersion(packageName: string): Promise<string | null> {
148
+ try {
149
+ const encodedName = encodeURIComponent(packageName);
150
+ const url = `https://registry.npmjs.org/${encodedName}`;
151
+ const response = await httpGet(url);
152
+ const data = JSON.parse(response);
153
+ return data['dist-tags']?.latest || null;
154
+ } catch (e) {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * 比较版本号
161
+ */
162
+ function compareVersions(current: string, latest: string): -1 | 0 | 1 {
163
+ const currentParts = current.split('.').map(Number);
164
+ const latestParts = latest.split('.').map(Number);
165
+
166
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
167
+ const c = currentParts[i] || 0;
168
+ const l = latestParts[i] || 0;
169
+ if (c < l) return -1;
170
+ if (c > l) return 1;
171
+ }
172
+ return 0;
173
+ }
174
+
175
+ /**
176
+ * 检查 @bolloon 相关的包是否有更新
177
+ */
178
+ async function checkBolloonUpdates(): Promise<PackageInfo | null> {
179
+ const packagesToCheck = [
180
+ '@bolloon/bolloon-agent',
181
+ '@bolloon/constraint-runtime',
182
+ ];
183
+
184
+ const outdatedPackages: OutdatedPackage[] = [];
185
+ let hasUpdate = false;
186
+ let currentVersion = '';
187
+ let latestVersion = '';
188
+
189
+ for (const pkg of packagesToCheck) {
190
+ const installed = getInstalledVersion(pkg);
191
+ if (!installed) continue;
192
+
193
+ currentVersion = installed;
194
+ const latest = await getLatestVersion(pkg);
195
+
196
+ if (latest && compareVersions(installed, latest) < 0) {
197
+ hasUpdate = true;
198
+ latestVersion = latest;
199
+ outdatedPackages.push({
200
+ name: pkg,
201
+ current: installed,
202
+ wanted: latest,
203
+ latest,
204
+ location: pkg
205
+ });
206
+ }
207
+ }
208
+
209
+ if (outdatedPackages.length === 0) {
210
+ return {
211
+ name: '@bolloon/bolloon-agent',
212
+ version: currentVersion,
213
+ latest: currentVersion,
214
+ outdated: false,
215
+ packages: []
216
+ };
217
+ }
218
+
219
+ return {
220
+ name: '@bolloon/bolloon-agent',
221
+ version: currentVersion,
222
+ latest: latestVersion,
223
+ outdated: hasUpdate,
224
+ packages: outdatedPackages
225
+ };
226
+ }
227
+
228
+ /**
229
+ * 使用 npm outdated 检查所有包的更新
230
+ */
231
+ function checkNpmOutdated(): OutdatedPackage[] {
232
+ try {
233
+ const output = execSync('npm outdated --json', {
234
+ encoding: 'utf-8',
235
+ timeout: 30000,
236
+ maxBuffer: 10 * 1024 * 1024,
237
+ cwd: process.cwd()
238
+ });
239
+
240
+ if (!output.trim()) return [];
241
+
242
+ const data = JSON.parse(output);
243
+ const packages: OutdatedPackage[] = [];
244
+
245
+ for (const [name, info] of Object.entries(data)) {
246
+ const pkg = info as any;
247
+ packages.push({
248
+ name,
249
+ current: pkg.current || '0.0.0',
250
+ wanted: pkg.wanted || '0.0.0',
251
+ latest: pkg.latest || '0.0.0',
252
+ location: pkg.location || name
253
+ });
254
+ }
255
+
256
+ return packages;
257
+ } catch (e: any) {
258
+ // npm outdated 在没有过时包时会返回非零退出码
259
+ if (e.status !== 0 && !e.message?.includes('npm outdated')) {
260
+ return [];
261
+ }
262
+ return [];
263
+ }
264
+ }
265
+
266
+ /**
267
+ * 自动更新 npm 包
268
+ */
269
+ async function updatePackages(packages?: string[]): Promise<UpdateResult> {
270
+ try {
271
+ const args = packages && packages.length > 0
272
+ ? ['npm', 'install', ...packages, '--save']
273
+ : ['npm', 'install', '-g', '@bolloon/bolloon-agent'];
274
+
275
+ log(`\n${CYAN}📦 正在更新包...${RESET}\n`, RESET);
276
+
277
+ // 执行 npm install
278
+ const result = execSync(args.join(' '), {
279
+ encoding: 'utf-8',
280
+ timeout: 300000, // 5分钟超时
281
+ stdio: 'inherit',
282
+ cwd: process.cwd()
283
+ });
284
+
285
+ return {
286
+ success: true,
287
+ updated: true,
288
+ message: '更新成功',
289
+ updatedPackages: packages
290
+ };
291
+ } catch (e: any) {
292
+ return {
293
+ success: false,
294
+ updated: false,
295
+ message: '更新失败',
296
+ error: e.message
297
+ };
298
+ }
299
+ }
300
+
301
+ /**
302
+ * 检查并自动更新(启动时调用)
303
+ */
304
+ export async function checkAndUpdate(): Promise<{
305
+ hasUpdate: boolean;
306
+ info: PackageInfo | null;
307
+ updated: boolean;
308
+ message: string;
309
+ }> {
310
+ // 检查是否有 --no-update 标志
311
+ if (process.argv.includes('--no-update') || process.argv.includes('--skip-update')) {
312
+ return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查' };
313
+ }
314
+
315
+ // 检查环境变量
316
+ if (process.env.BOLLOON_SKIP_UPDATE === 'true') {
317
+ return { hasUpdate: false, info: null, updated: false, message: '跳过更新检查(环境变量)' };
318
+ }
319
+
320
+ log(`\n${CYAN}🔍 检查更新...${RESET}`, RESET);
321
+
322
+ try {
323
+ // 检查 @bolloon 包的更新
324
+ const bolloonInfo = await checkBolloonUpdates();
325
+
326
+ if (bolloonInfo && bolloonInfo.outdated) {
327
+ log(`\n${YELLOW}⚠️ 发现新版本: ${bolloonInfo.latest}${RESET}\n`, RESET);
328
+ log(` 当前版本: ${bolloonInfo.version}\n`, RESET);
329
+ log(` 最新版本: ${bolloonInfo.latest}\n\n`, RESET);
330
+
331
+ // 自动更新
332
+ const result = await updatePackages(bolloonInfo.packages.map(p => p.name));
333
+
334
+ if (result.success) {
335
+ log(`\n${GREEN}✅ 更新成功!请重新启动应用${RESET}\n`, RESET);
336
+
337
+ // 提示用户重启
338
+ log(`${YELLOW}💡 请重新运行 bolloon 以使用新版本${RESET}\n\n`, RESET);
339
+
340
+ // 通知主进程更新完成
341
+ process.emit('bolloon-update-complete', result);
342
+
343
+ return {
344
+ hasUpdate: true,
345
+ info: bolloonInfo,
346
+ updated: true,
347
+ message: `已更新到 ${bolloonInfo.latest}`
348
+ };
349
+ } else {
350
+ return {
351
+ hasUpdate: true,
352
+ info: bolloonInfo,
353
+ updated: false,
354
+ message: `更新失败: ${result.error}`
355
+ };
356
+ }
357
+ } else {
358
+ log(` ${GREEN}✓${RESET} 已是最新版本 (${bolloonInfo?.version || 'unknown'})\n`, RESET);
359
+ return {
360
+ hasUpdate: false,
361
+ info: bolloonInfo,
362
+ updated: false,
363
+ message: '已是最新版本'
364
+ };
365
+ }
366
+ } catch (e: any) {
367
+ log(` ${YELLOW}⚠${RESET} 更新检查失败: ${e.message}\n`, RESET);
368
+ return {
369
+ hasUpdate: false,
370
+ info: null,
371
+ updated: false,
372
+ message: `检查失败: ${e.message}`
373
+ };
374
+ }
375
+ }
376
+
377
+ /**
378
+ * 仅检查更新,不自动安装
379
+ */
380
+ export async function checkForUpdates(): Promise<PackageInfo | null> {
381
+ return await checkBolloonUpdates();
382
+ }
383
+
384
+ /**
385
+ * 手动触发更新
386
+ */
387
+ export async function performUpdate(packages?: string[]): Promise<UpdateResult> {
388
+ return await updatePackages(packages);
389
+ }
390
+
391
+ // CLI 入口
392
+ if (process.argv[1]?.includes('auto-update')) {
393
+ (async () => {
394
+ const command = process.argv[2];
395
+
396
+ switch (command) {
397
+ case 'check':
398
+ const info = await checkForUpdates();
399
+ if (info) {
400
+ console.log(JSON.stringify(info, null, 2));
401
+ }
402
+ break;
403
+
404
+ case 'update':
405
+ const result = await performUpdate(process.argv.slice(3));
406
+ console.log(JSON.stringify(result, null, 2));
407
+ break;
408
+
409
+ default:
410
+ await checkAndUpdate();
411
+ }
412
+ })();
413
+ }