@ia-ccun/code-agent-cli 0.0.51 → 0.0.52-beta.2

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.
Files changed (38) hide show
  1. package/bin/cli.js +11 -10
  2. package/config/agent/extensions/aicode-advisor.ts +707 -0
  3. package/config/agent/extensions/aicode-btw.ts +2273 -0
  4. package/config/agent/extensions/aicode-plan.ts +903 -0
  5. package/config/agent/extensions/aicode-tips.ts +44 -8
  6. package/config/agent/extensions/bash-check.ts +1 -1
  7. package/config/agent/extensions/context-monitor.ts +1 -1
  8. package/config/agent/extensions/context.ts +3 -3
  9. package/config/agent/extensions/custom-footer.ts +22 -4
  10. package/config/agent/extensions/custom.ts +118 -84
  11. package/config/agent/extensions/error-monitor.ts +1 -1
  12. package/config/agent/extensions/goal.ts +450 -0
  13. package/config/agent/extensions/msg-debug.ts +49 -10
  14. package/config/agent/extensions/review.ts +3 -3
  15. package/config/agent/extensions/working-msg.ts +44 -23
  16. package/config/agent/help.md +25 -7
  17. package/config/agent/models.json +7 -7
  18. package/config/agent/settings.json +0 -2
  19. package/dist/banner.d.ts +5 -1
  20. package/dist/banner.d.ts.map +1 -1
  21. package/dist/banner.js +106 -7
  22. package/dist/banner.js.map +1 -1
  23. package/dist/config-loader.d.ts +2 -0
  24. package/dist/config-loader.d.ts.map +1 -1
  25. package/dist/config-loader.js +81 -7
  26. package/dist/config-loader.js.map +1 -1
  27. package/dist/config.js +1 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/index.d.ts +1 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/security-logger.d.ts +69 -0
  34. package/dist/security-logger.d.ts.map +1 -0
  35. package/dist/security-logger.js +178 -0
  36. package/dist/security-logger.js.map +1 -0
  37. package/package.json +18 -7
  38. package/scripts/postinstall.js +188 -50
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 安全日志模块
3
+ * 🔒 Security: Structured logging for security events
4
+ */
5
+ import { createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { homedir } from 'os';
8
+ // 日志目录和文件
9
+ const LOG_DIR = join(homedir(), '.aicode-cli', 'logs');
10
+ const SECURITY_LOG = join(LOG_DIR, 'security.log');
11
+ const ERROR_LOG = join(LOG_DIR, 'error.log');
12
+ /**
13
+ * 日志级别
14
+ */
15
+ export var LogLevel;
16
+ (function (LogLevel) {
17
+ LogLevel["INFO"] = "info";
18
+ LogLevel["WARN"] = "warn";
19
+ LogLevel["ERROR"] = "error";
20
+ })(LogLevel || (LogLevel = {}));
21
+ /**
22
+ * 安全事件类型
23
+ */
24
+ export var SecurityEventType;
25
+ (function (SecurityEventType) {
26
+ // 配置相关
27
+ SecurityEventType["CONFIG_LOAD"] = "config_load";
28
+ SecurityEventType["CONFIG_ERROR"] = "config_error";
29
+ // 路径验证
30
+ SecurityEventType["PATH_VALIDATION"] = "path_validation";
31
+ SecurityEventType["PATH_TRAVERSAL_BLOCKED"] = "path_traversal_blocked";
32
+ // 文件操作
33
+ SecurityEventType["FILE_MODIFIED"] = "file_modified";
34
+ SecurityEventType["FILE_ACCESS"] = "file_access";
35
+ // 依赖和更新
36
+ SecurityEventType["DEPENDENCY_CHECK"] = "dependency_check";
37
+ SecurityEventType["VERSION_CHECK"] = "version_check";
38
+ // 安装和设置
39
+ SecurityEventType["INSTALL_START"] = "install_start";
40
+ SecurityEventType["INSTALL_COMPLETE"] = "install_complete";
41
+ SecurityEventType["SYMLINK_CREATE"] = "symlink_create";
42
+ // 通用
43
+ SecurityEventType["SUSPICIOUS_ACTIVITY"] = "suspicious_activity";
44
+ })(SecurityEventType || (SecurityEventType = {}));
45
+ /**
46
+ * 日志流(懒加载)
47
+ */
48
+ let securityLogStream = null;
49
+ let errorLogStream = null;
50
+ /**
51
+ * 初始化日志目录和流
52
+ */
53
+ function initLogs() {
54
+ // 确保日志目录存在
55
+ if (!existsSync(LOG_DIR)) {
56
+ mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
57
+ }
58
+ // 创建安全日志流(追加模式,仅所有者可读写)
59
+ if (!securityLogStream) {
60
+ securityLogStream = createWriteStream(SECURITY_LOG, {
61
+ flags: 'a',
62
+ mode: 0o600,
63
+ });
64
+ }
65
+ // 创建错误日志流
66
+ if (!errorLogStream) {
67
+ errorLogStream = createWriteStream(ERROR_LOG, {
68
+ flags: 'a',
69
+ mode: 0o600,
70
+ });
71
+ }
72
+ }
73
+ /**
74
+ * 格式化日志条目为 JSON
75
+ */
76
+ function formatLog(entry) {
77
+ return JSON.stringify(entry) + '\n';
78
+ }
79
+ /**
80
+ * 记录安全事件
81
+ * @param event 事件类型
82
+ * @param details 事件详情
83
+ * @param level 日志级别(默认 info)
84
+ */
85
+ export function logSecurity(event, details, level = LogLevel.INFO) {
86
+ initLogs();
87
+ const entry = {
88
+ timestamp: new Date().toISOString(),
89
+ level,
90
+ event,
91
+ message: `Security event: ${event}`,
92
+ details: sanitizeDetails(details),
93
+ pid: process.pid,
94
+ version: process.version,
95
+ };
96
+ securityLogStream?.write(formatLog(entry));
97
+ // 开发环境下输出到控制台
98
+ if (process.env.DEBUG || process.env.NODE_ENV === 'development') {
99
+ console.error(`[🔒 安全] ${event}:`, details);
100
+ }
101
+ }
102
+ /**
103
+ * 记录错误
104
+ * @param message 错误消息
105
+ * @param error 错误对象
106
+ * @param context 上下文信息
107
+ */
108
+ export function logError(message, error, context) {
109
+ initLogs();
110
+ const entry = {
111
+ timestamp: new Date().toISOString(),
112
+ level: LogLevel.ERROR,
113
+ message,
114
+ details: {
115
+ name: error.name,
116
+ message: error.message,
117
+ stack: error.stack,
118
+ ...sanitizeDetails(context || {}),
119
+ },
120
+ pid: process.pid,
121
+ version: process.version,
122
+ };
123
+ errorLogStream?.write(formatLog(entry));
124
+ // 开发环境下输出到控制台
125
+ if (process.env.DEBUG || process.env.NODE_ENV === 'development') {
126
+ console.error(`[❌ 错误] ${message}:`, error);
127
+ }
128
+ }
129
+ /**
130
+ * 脱敏处理(移除敏感信息)
131
+ */
132
+ function sanitizeDetails(details) {
133
+ const sensitiveKeys = ['apiKey', 'api_key', 'token', 'password', 'secret', 'auth'];
134
+ const sanitized = { ...details };
135
+ for (const key of sensitiveKeys) {
136
+ if (sanitized[key]) {
137
+ // 仅保留前 4 个字符用于识别
138
+ const value = String(sanitized[key]);
139
+ sanitized[key] = value.length > 8 ? value.substring(0, 4) + '***' : '***';
140
+ }
141
+ }
142
+ return sanitized;
143
+ }
144
+ /**
145
+ * 获取日志文件路径
146
+ */
147
+ export function getLogPaths() {
148
+ return {
149
+ security: SECURITY_LOG,
150
+ error: ERROR_LOG,
151
+ dir: LOG_DIR,
152
+ };
153
+ }
154
+ /**
155
+ * 清除旧日志(可选的维护功能)
156
+ * @param maxAge 最大保留天数(默认 30 天)
157
+ */
158
+ export function rotateLogs(maxAge = 30) {
159
+ const now = Date.now();
160
+ const maxAgeMs = maxAge * 24 * 60 * 60 * 1000;
161
+ try {
162
+ if (!existsSync(LOG_DIR))
163
+ return;
164
+ const files = readdirSync(LOG_DIR);
165
+ for (const file of files) {
166
+ const filePath = join(LOG_DIR, file);
167
+ const stats = statSync(filePath);
168
+ if (stats.isFile() && now - stats.mtimeMs > maxAgeMs) {
169
+ unlinkSync(filePath);
170
+ console.log(`[日志轮转] 已删除旧日志:${file}`);
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ logError('日志轮转失败', error, { maxAge });
176
+ }
177
+ }
178
+ //# sourceMappingURL=security-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-logger.js","sourceRoot":"","sources":["../src/security-logger.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ia-ccun/code-agent-cli",
3
- "version": "0.0.51",
4
- "description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent(pi)原理封装的编码智能体(供学习使用)。",
3
+ "version": "0.0.52-beta.2",
4
+ "description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent原理封装的编码智能体(供学习使用)。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -35,21 +35,32 @@
35
35
  "prepare": "husky"
36
36
  },
37
37
  "dependencies": {
38
- "@mariozechner/pi-agent-core": "0.62.0",
39
- "@mariozechner/pi-ai": "0.62.0",
40
- "@mariozechner/pi-coding-agent": "0.62.0",
41
- "@mariozechner/pi-tui": "0.62.0"
38
+ "@earendil-works/pi-agent-core": "^0.77.0",
39
+ "@earendil-works/pi-ai": "^0.77.0",
40
+ "@earendil-works/pi-coding-agent": "^0.77.0",
41
+ "@earendil-works/pi-tui": "^0.77.0"
42
42
  },
43
43
  "overrides": {
44
44
  "glob": "^11.0.0",
45
- "node-domexception": "^2.0.0"
45
+ "node-domexception": "^2.0.0",
46
+ "protobufjs": "^7.5.5",
47
+ "basic-ftp": "^6.0.1",
48
+ "flatted": "^3.4.2",
49
+ "undici": "^7.24.8",
50
+ "picomatch": "^4.0.4",
51
+ "brace-expansion": "^5.0.5",
52
+ "file-type": "^21.3.2",
53
+ "ip-address": "^10.1.1",
54
+ "yaml": "^2.8.3"
46
55
  },
47
56
  "devDependencies": {
57
+ "@devkade/pi-plan": "^0.2.2",
48
58
  "@types/node": "^22.10.0",
49
59
  "@typescript-eslint/eslint-plugin": "^8.57.0",
50
60
  "@typescript-eslint/parser": "^8.57.0",
51
61
  "eslint": "^10.0.3",
52
62
  "husky": "^9.1.7",
63
+ "playwright": "^1.60.0",
53
64
  "shx": "^0.4.0",
54
65
  "typescript": "^5.7.2",
55
66
  "typescript-eslint": "^8.57.0"
@@ -1,18 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Post-install script for aicode-cli
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, readdirSync, statSync } from 'fs';
5
- import { execSync } from 'child_process';
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, readdirSync, statSync, createWriteStream } from 'fs';
5
+ import { symlink as fsSymlink } from 'fs/promises';
6
6
  import { join, dirname, relative } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
+ import { spawn } from 'child_process';
9
10
 
10
11
  // Get __dirname equivalent for ES modules
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = dirname(__filename);
13
14
 
15
+ // ============================================================================
16
+ // Security Logger (inline for postinstall script)
17
+ // ============================================================================
18
+ const LOG_DIR = join(homedir(), '.aicode-cli', 'logs');
19
+ const SECURITY_LOG = join(LOG_DIR, 'security.log');
20
+
21
+ function ensureLogDir() {
22
+ if (!existsSync(LOG_DIR)) {
23
+ mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
24
+ }
25
+ }
26
+
27
+ function logSecurity(event, details, level = 'info') {
28
+ ensureLogDir();
29
+ const entry = {
30
+ timestamp: new Date().toISOString(),
31
+ level,
32
+ event,
33
+ message: `PostInstall: ${event}`,
34
+ details,
35
+ pid: process.pid,
36
+ };
37
+
38
+ try {
39
+ const stream = createWriteStream(SECURITY_LOG, { flags: 'a', mode: 0o600 });
40
+ stream.write(JSON.stringify(entry) + '\n');
41
+ stream.end();
42
+ } catch (e) {
43
+ // Ignore logging errors
44
+ }
45
+
46
+ if (process.env.DEBUG) {
47
+ console.error(`[🔒 安全] ${event}:`, details);
48
+ }
49
+ }
50
+
51
+ // ============================================================================
52
+ // Main Installation Logic
53
+ // ============================================================================
54
+
14
55
  console.log('\n🚀 Setting up aicode-cli...');
15
56
 
57
+ // Record installation start
58
+ logSecurity('install_start', {
59
+ version: '0.0.51-beta.1',
60
+ homeDir: homedir(),
61
+ timestamp: new Date().toISOString(),
62
+ });
63
+
16
64
  // Get the current user's home directory
17
65
  const homeDir = homedir();
18
66
  const aicodeCliDir = join(homeDir, '.aicode-cli');
@@ -21,7 +69,7 @@ const piDir = join(homeDir, '.pi');
21
69
  // ============================================================================
22
70
  // Fix pi-coding-agent APP_NAME
23
71
  // ============================================================================
24
- const piAgentPackageJson = join(__dirname, '..', 'node_modules', '@mariozechner', 'pi-coding-agent', 'package.json');
72
+ const piAgentPackageJson = join(__dirname, '..', 'node_modules', '@earendil-works', 'pi-coding-agent', 'package.json');
25
73
  if (existsSync(piAgentPackageJson)) {
26
74
  try {
27
75
  const piPkg = JSON.parse(readFileSync(piAgentPackageJson, 'utf-8'));
@@ -33,13 +81,24 @@ if (existsSync(piAgentPackageJson)) {
33
81
  piPkg.piConfig.configDir = '.aicode-cli';
34
82
  writeFileSync(piAgentPackageJson, JSON.stringify(piPkg, null, 2) + '\n');
35
83
  console.log(`✓ Updated pi-coding-agent app name: ${oldName} -> aicode-cli`);
84
+
85
+ logSecurity('file_modified', {
86
+ file: piAgentPackageJson,
87
+ action: 'Updated piConfig',
88
+ changes: ['name', 'configDir'],
89
+ });
36
90
  } catch (e) {
37
91
  console.log(`⚠ Could not update pi-coding-agent config: ${e.message}`);
92
+ logSecurity('file_modified', {
93
+ file: piAgentPackageJson,
94
+ action: 'Update failed',
95
+ error: e.message,
96
+ }, 'warn');
38
97
  }
39
98
  }
40
99
 
41
100
  // Fix pi-coding-agent cli.js process.title
42
- const piAgentCliJs = join(__dirname, '..', 'node_modules', '@mariozechner', 'pi-coding-agent', 'dist', 'cli.js');
101
+ const piAgentCliJs = join(__dirname, '..', 'node_modules', '@earendil-works', 'pi-coding-agent', 'dist', 'cli.js');
43
102
  if (existsSync(piAgentCliJs)) {
44
103
  try {
45
104
  let cliContent = readFileSync(piAgentCliJs, 'utf-8');
@@ -47,6 +106,11 @@ if (existsSync(piAgentCliJs)) {
47
106
  cliContent = cliContent.replace(/process\.title = "pi"/g, 'process.title = "aicode-cli"');
48
107
  writeFileSync(piAgentCliJs, cliContent);
49
108
  console.log(`✓ Updated pi-coding-agent process.title`);
109
+
110
+ logSecurity('file_modified', {
111
+ file: piAgentCliJs,
112
+ action: 'Updated process.title',
113
+ });
50
114
  }
51
115
  } catch (e) {
52
116
  console.log(`⚠ Could not update pi-coding-agent cli.js: ${e.message}`);
@@ -54,7 +118,7 @@ if (existsSync(piAgentCliJs)) {
54
118
  }
55
119
 
56
120
  // Fix pi-coding-agent config.js APP_NAME default
57
- const piAgentConfigJs = join(__dirname, '..', 'node_modules', '@mariozechner', 'pi-coding-agent', 'dist', 'config.js');
121
+ const piAgentConfigJs = join(__dirname, '..', 'node_modules', '@earendil-works', 'pi-coding-agent', 'dist', 'config.js');
58
122
  if (existsSync(piAgentConfigJs)) {
59
123
  try {
60
124
  let configContent = readFileSync(piAgentConfigJs, 'utf-8');
@@ -62,6 +126,11 @@ if (existsSync(piAgentConfigJs)) {
62
126
  configContent = configContent.replace(/pkg\.piConfig\?\.name \|\| "pi"/g, 'pkg.piConfig?.name || "aicode-cli"');
63
127
  writeFileSync(piAgentConfigJs, configContent);
64
128
  console.log(`✓ Updated pi-coding-agent config.js APP_NAME`);
129
+
130
+ logSecurity('file_modified', {
131
+ file: piAgentConfigJs,
132
+ action: 'Updated APP_NAME',
133
+ });
65
134
  }
66
135
  } catch (e) {
67
136
  console.log(`⚠ Could not update pi-coding-agent config.js: ${e.message}`);
@@ -69,7 +138,7 @@ if (existsSync(piAgentConfigJs)) {
69
138
  }
70
139
 
71
140
  // Fix pi-coding-agent slash commands - change "Quit pi" to "Quit aicode-cli"
72
- const slashCommandsJs = join(__dirname, '..', 'node_modules', '@mariozechner', 'pi-coding-agent', 'dist', 'core', 'slash-commands.js');
141
+ const slashCommandsJs = join(__dirname, '..', 'node_modules', '@earendil-works', 'pi-coding-agent', 'dist', 'core', 'slash-commands.js');
73
142
  if (existsSync(slashCommandsJs)) {
74
143
  try {
75
144
  let content = readFileSync(slashCommandsJs, 'utf-8');
@@ -77,6 +146,11 @@ if (existsSync(slashCommandsJs)) {
77
146
  content = content.replace(/Quit pi/g, 'Quit aicode-cli');
78
147
  writeFileSync(slashCommandsJs, content);
79
148
  console.log(`✓ Updated slash command description`);
149
+
150
+ logSecurity('file_modified', {
151
+ file: slashCommandsJs,
152
+ action: 'Updated slash commands',
153
+ });
80
154
  }
81
155
  } catch (e) {
82
156
  console.log(`⚠ Could not update slash commands: ${e.message}`);
@@ -103,7 +177,6 @@ function copyDirRecursive(src, dest) {
103
177
  const isDirectory = exists && stats.isDirectory();
104
178
 
105
179
  if (isDirectory) {
106
- // Create directory if it doesn't exist
107
180
  if (!existsSync(dest)) {
108
181
  mkdirSync(dest, { recursive: true });
109
182
  }
@@ -115,9 +188,7 @@ function copyDirRecursive(src, dest) {
115
188
  copyDirRecursive(srcPath, destPath);
116
189
  });
117
190
  } else {
118
- // Only copy if source exists AND destination doesn't exist (preserve user files)
119
191
  if (exists && !existsSync(dest)) {
120
- // Ensure parent directory exists
121
192
  const parentDir = dirname(dest);
122
193
  if (!existsSync(parentDir)) {
123
194
  mkdirSync(parentDir, { recursive: true });
@@ -171,19 +242,14 @@ const FORCE_OVERWRITE_LIST = [
171
242
  'SYSTEM.md',
172
243
  'dangerous-patterns.json',
173
244
  'settings.json',
174
- 'extensions/working-msg.ts',
175
- 'extensions/custom-footer.ts',
176
- 'extensions/custom.ts',
177
- 'extensions/bash-check.ts',
178
- 'extensions/aicode-tips.ts',
179
- // Force overwrite all prompts .md files
245
+ 'help.md',
246
+ 'extensions/*.ts',
180
247
  'prompts/*.md',
181
- // Force overwrite all skills SKILL.md files
182
248
  'skills/*/SKILL.md',
183
- 'skills/*/*/SKILL.md',
249
+ 'skills/*/*/SKILL.md',
184
250
  ];
185
251
 
186
- // Function to expand glob patterns like "skills/*/SKILL.md" or "prompts/*.md"
252
+ // Function to expand glob patterns
187
253
  function expandGlob(pattern) {
188
254
  const results = [];
189
255
 
@@ -201,21 +267,17 @@ function expandGlob(pattern) {
201
267
  if (!existsSync(cp)) continue;
202
268
 
203
269
  if (hasWildcard) {
204
- // Handle wildcard patterns like * or *.md
205
270
  const entries = readdirSync(cp);
206
- // Convert glob pattern to regex: *.md -> ^.*\.md$
207
271
  const regex = new RegExp('^' + part.replace(/\*/g, '.*') + '$');
208
272
 
209
273
  for (const entry of entries) {
210
274
  if (regex.test(entry)) {
211
275
  const fullPath = join(cp, entry);
212
276
  if (isLastPart) {
213
- // Last part - match files (not directories)
214
277
  if (statSync(fullPath).isFile()) {
215
278
  nextPaths.push(fullPath);
216
279
  }
217
280
  } else {
218
- // Not the last part - only directories
219
281
  if (statSync(fullPath).isDirectory()) {
220
282
  nextPaths.push(fullPath);
221
283
  }
@@ -243,7 +305,6 @@ function expandGlob(pattern) {
243
305
 
244
306
  // Function to force overwrite specific files/folders
245
307
  function forceOverwriteList(relativePaths) {
246
- // Expand glob patterns first
247
308
  const expandedPaths = [];
248
309
  for (const relativePath of relativePaths) {
249
310
  expandedPaths.push(...expandGlob(relativePath));
@@ -257,14 +318,12 @@ function forceOverwriteList(relativePaths) {
257
318
  try {
258
319
  const stats = statSync(srcPath);
259
320
  if (stats.isDirectory()) {
260
- // Force copy directory
261
321
  if (!existsSync(destPath)) {
262
322
  mkdirSync(destPath, { recursive: true });
263
323
  }
264
324
  forceCopyDirRecursive(srcPath, destPath);
265
325
  console.log(`✓ Force overwritten (dir): ${relativePath}`);
266
326
  } else {
267
- // Force copy file
268
327
  const parentDir = dirname(destPath);
269
328
  if (!existsSync(parentDir)) {
270
329
  mkdirSync(parentDir, { recursive: true });
@@ -281,20 +340,17 @@ function forceOverwriteList(relativePaths) {
281
340
  });
282
341
  }
283
342
 
284
- // Copy config/agent contents to ~/.aicode-cli/agent only if they don't exist (preserve user customizations)
343
+ // Copy config/agent contents to ~/.aicode-cli/agent
285
344
  if (existsSync(packageConfigDir)) {
286
345
  console.log(`📁 Syncing default config files to ${agentDir} (preserving user customizations)`);
287
346
 
288
- // Copy directory recursively (only if destination doesn't exist)
289
347
  copyDirRecursive(packageConfigDir, agentDir);
290
348
  console.log('✓ Default config files synced successfully');
291
349
 
292
- // Force overwrite specified files/folders
293
350
  if (FORCE_OVERWRITE_LIST.length > 0) {
294
351
  forceOverwriteList(FORCE_OVERWRITE_LIST);
295
352
  }
296
353
 
297
- // Check for extensions
298
354
  const extensionsDir = join(agentDir, 'extensions');
299
355
  if (existsSync(extensionsDir)) {
300
356
  try {
@@ -311,19 +367,17 @@ if (existsSync(packageConfigDir)) {
311
367
  }
312
368
 
313
369
  // ============================================================================
314
- // Reset User-Agent placeholder in models.json (so CLI can replace it on each run)
370
+ // Reset User-Agent placeholder in models.json
315
371
  // ============================================================================
316
372
  const modelsPath = join(agentDir, 'models.json');
317
373
  if (existsSync(modelsPath)) {
318
374
  try {
319
375
  let content = readFileSync(modelsPath, 'utf-8');
320
- // 只重置 headers.User-Agent 下的版本号为占位符
321
376
  try {
322
377
  const config = JSON.parse(content);
323
378
  for (const provider of Object.values(config.providers || {})) {
324
379
  if (provider.headers && provider.headers['User-Agent']) {
325
380
  const ua = provider.headers['User-Agent'];
326
- // 如果是以 aicode-cli/ 开头,替换为占位符
327
381
  if (ua && ua.startsWith('aicode-cli/')) {
328
382
  provider.headers['User-Agent'] = '${VERSION}';
329
383
  }
@@ -334,6 +388,11 @@ if (existsSync(modelsPath)) {
334
388
  if (newContent !== content) {
335
389
  writeFileSync(modelsPath, newContent, 'utf-8');
336
390
  console.log('✓ Reset User-Agent placeholder in models.json');
391
+
392
+ logSecurity('file_modified', {
393
+ file: modelsPath,
394
+ action: 'Reset User-Agent placeholder',
395
+ });
337
396
  }
338
397
  } catch (e) {
339
398
  console.log(`⚠ Could not reset User-Agent placeholder: ${e.message}`);
@@ -349,24 +408,38 @@ if (existsSync(modelsPath)) {
349
408
  }
350
409
 
351
410
  // Create symlink ~/.pi linking to ~/.aicode-cli (if not exists)
352
- try {
353
- if (!existsSync(piDir)) {
354
- // Create symbolic link using shell command (works on all platforms)
355
- execSync(`ln -s "${aicodeCliDir}" "${piDir}"`, { stdio: 'pipe' });
356
- console.log(`✓ Created symbolic link: ${piDir} -> ${aicodeCliDir}`);
357
- } else {
358
- console.log(`✓ ${piDir} already exists`);
359
- }
360
- } catch (e) {
361
- if (e.message.includes('File exists')) {
362
- console.log(`✓ Symbolic link already exists: ${piDir}`);
363
- } else {
364
- console.log(`⚠ Could not create symbolic link: ${e.message}`);
365
- console.log('\n📌 To fix this, please run manually:');
366
- console.log(' ln -s ~/.aicode-cli ~/.pi');
367
- console.log('\n (If ~/.pi exists, run: rm -rf ~/.pi && ln -s ~/.aicode-cli ~/.pi)');
411
+ (async () => {
412
+ try {
413
+ if (!existsSync(piDir)) {
414
+ await fsSymlink(aicodeCliDir, piDir, 'dir');
415
+ console.log(`✓ Created symbolic link: ${piDir} -> ${aicodeCliDir}`);
416
+
417
+ logSecurity('symlink_create', {
418
+ target: piDir,
419
+ source: aicodeCliDir,
420
+ success: true,
421
+ });
422
+ } else {
423
+ console.log(`✓ ${piDir} already exists`);
424
+ }
425
+ } catch (e) {
426
+ if (e.code === 'EEXIST') {
427
+ console.log(`✓ Symbolic link already exists: ${piDir}`);
428
+ } else {
429
+ console.log(`⚠ Could not create symbolic link: ${e.message}`);
430
+ console.log('\n📌 To fix this, please run manually:');
431
+ console.log(' ln -s ~/.aicode-cli ~/.pi');
432
+ console.log('\n (If ~/.pi exists, run: rm -rf ~/.pi && ln -s ~/.aicode-cli ~/.pi)');
433
+
434
+ logSecurity('symlink_create', {
435
+ target: piDir,
436
+ source: aicodeCliDir,
437
+ success: false,
438
+ error: e.message,
439
+ }, 'warn');
440
+ }
368
441
  }
369
- }
442
+ })();
370
443
 
371
444
  // Check and display models config
372
445
  if (existsSync(modelsPath)) {
@@ -383,8 +456,73 @@ if (existsSync(modelsPath)) {
383
456
  console.log(`⚠ No models.json found at ${modelsPath}`);
384
457
  }
385
458
 
459
+ // Record installation complete
460
+ logSecurity('install_complete', {
461
+ success: true,
462
+ aicodeCliDir,
463
+ agentDir,
464
+ piDir,
465
+ });
466
+
467
+ // ============================================================================
468
+ // Auto-install codegraph (optional dependency)
469
+ // ============================================================================
470
+
471
+ const CODEGRAPH_PKG = '@colbymchenry/codegraph@0.9.7';
472
+
473
+ async function installCodegraph() {
474
+ return new Promise((resolve) => {
475
+ const child = spawn('npm', ['i', '-g', CODEGRAPH_PKG], {
476
+ stdio: 'pipe',
477
+ shell: process.platform === 'win32',
478
+ });
479
+
480
+ let stderr = '';
481
+
482
+ child.stderr.on('data', (data) => {
483
+ stderr += data.toString();
484
+ });
485
+
486
+ child.on('close', (code) => {
487
+ if (code === 0) {
488
+ console.log('✓ Installed codegraph globally,See UniDoc《2026.5 aicode+codegraph 安装与使用》');
489
+ logSecurity('codegraph_install', {
490
+ package: CODEGRAPH_PKG,
491
+ success: true
492
+ });
493
+ } else {
494
+ // 静默失败,不中断主流程
495
+ if (process.env.DEBUG) {
496
+ console.log(`⚠ Could not install codegraph: ${stderr.slice(0, 100)}`);
497
+ }
498
+ logSecurity('codegraph_install', {
499
+ package: CODEGRAPH_PKG,
500
+ success: false,
501
+ error: stderr.slice(0, 200)
502
+ }, 'warn');
503
+ }
504
+ resolve();
505
+ });
506
+
507
+ child.on('error', (err) => {
508
+ if (process.env.DEBUG) {
509
+ console.log(`⚠ Could not install codegraph: ${err.message}`);
510
+ }
511
+ logSecurity('codegraph_install', {
512
+ package: CODEGRAPH_PKG,
513
+ success: false,
514
+ error: err.message
515
+ }, 'warn');
516
+ resolve();
517
+ });
518
+ });
519
+ }
520
+
521
+ // Execute codegraph installation
522
+ await installCodegraph();
523
+
386
524
  console.log('\n✨ Setup complete!');
387
525
  console.log('📝 Configuration files are in ~/.aicode-cli/');
388
526
  console.log('🔗 ~/.pi is linked to ~/.aicode-cli (ensuring model configs work)');
389
527
  console.log('🚀 Run with: aicode-cli');
390
- console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');
528
+ console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');