@ia-ccun/code-agent-cli 0.0.51-beta.1 → 0.0.51-beta.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.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 安全日志模块
3
+ * 🔒 Security: Structured logging for security events
4
+ */
5
+ /**
6
+ * 日志级别
7
+ */
8
+ export declare enum LogLevel {
9
+ INFO = "info",
10
+ WARN = "warn",
11
+ ERROR = "error"
12
+ }
13
+ /**
14
+ * 安全事件类型
15
+ */
16
+ export declare enum SecurityEventType {
17
+ CONFIG_LOAD = "config_load",
18
+ CONFIG_ERROR = "config_error",
19
+ PATH_VALIDATION = "path_validation",
20
+ PATH_TRAVERSAL_BLOCKED = "path_traversal_blocked",
21
+ FILE_MODIFIED = "file_modified",
22
+ FILE_ACCESS = "file_access",
23
+ DEPENDENCY_CHECK = "dependency_check",
24
+ VERSION_CHECK = "version_check",
25
+ INSTALL_START = "install_start",
26
+ INSTALL_COMPLETE = "install_complete",
27
+ SYMLINK_CREATE = "symlink_create",
28
+ SUSPICIOUS_ACTIVITY = "suspicious_activity"
29
+ }
30
+ /**
31
+ * 日志条目接口
32
+ */
33
+ export interface LogEntry {
34
+ timestamp: string;
35
+ level: LogLevel;
36
+ event?: SecurityEventType;
37
+ message: string;
38
+ details?: Record<string, unknown>;
39
+ pid: number;
40
+ version?: string;
41
+ }
42
+ /**
43
+ * 记录安全事件
44
+ * @param event 事件类型
45
+ * @param details 事件详情
46
+ * @param level 日志级别(默认 info)
47
+ */
48
+ export declare function logSecurity(event: SecurityEventType, details: Record<string, unknown>, level?: LogLevel): void;
49
+ /**
50
+ * 记录错误
51
+ * @param message 错误消息
52
+ * @param error 错误对象
53
+ * @param context 上下文信息
54
+ */
55
+ export declare function logError(message: string, error: Error, context?: Record<string, unknown>): void;
56
+ /**
57
+ * 获取日志文件路径
58
+ */
59
+ export declare function getLogPaths(): {
60
+ security: string;
61
+ error: string;
62
+ dir: string;
63
+ };
64
+ /**
65
+ * 清除旧日志(可选的维护功能)
66
+ * @param maxAge 最大保留天数(默认 30 天)
67
+ */
68
+ export declare function rotateLogs(maxAge?: number): void;
69
+ //# sourceMappingURL=security-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-logger.d.ts","sourceRoot":"","sources":["../src/security-logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;GAEG;AACH,oBAAY,QAAQ;IAClB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED;;GAEG;AACH,oBAAY,iBAAiB;IAE3B,WAAW,gBAAgB;IAC3B,YAAY,iBAAiB;IAG7B,eAAe,oBAAoB;IACnC,sBAAsB,2BAA2B;IAGjD,aAAa,kBAAkB;IAC/B,WAAW,gBAAgB;IAG3B,gBAAgB,qBAAqB;IACrC,aAAa,kBAAkB;IAG/B,aAAa,kBAAkB;IAC/B,gBAAgB,qBAAqB;IACrC,cAAc,mBAAmB;IAGjC,mBAAmB,wBAAwB;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyCD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,KAAK,GAAE,QAAwB,GAC9B,IAAI,CAmBN;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAuBN;AAoBD;;GAEG;AACH,wBAAgB,WAAW,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAM9E;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,MAAW,GAAG,IAAI,CAoBpD"}
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-ccun/code-agent-cli",
3
- "version": "0.0.51-beta.1",
3
+ "version": "0.0.51-beta.10",
4
4
  "description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent(pi)原理封装的编码智能体(供学习使用)。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,9 +42,19 @@
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",
@@ -1,8 +1,8 @@
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';
@@ -11,8 +11,55 @@ import { fileURLToPath } from 'url';
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
13
 
14
+ // ============================================================================
15
+ // Security Logger (inline for postinstall script)
16
+ // ============================================================================
17
+ const LOG_DIR = join(homedir(), '.aicode-cli', 'logs');
18
+ const SECURITY_LOG = join(LOG_DIR, 'security.log');
19
+
20
+ function ensureLogDir() {
21
+ if (!existsSync(LOG_DIR)) {
22
+ mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
23
+ }
24
+ }
25
+
26
+ function logSecurity(event, details, level = 'info') {
27
+ ensureLogDir();
28
+ const entry = {
29
+ timestamp: new Date().toISOString(),
30
+ level,
31
+ event,
32
+ message: `PostInstall: ${event}`,
33
+ details,
34
+ pid: process.pid,
35
+ };
36
+
37
+ try {
38
+ const stream = createWriteStream(SECURITY_LOG, { flags: 'a', mode: 0o600 });
39
+ stream.write(JSON.stringify(entry) + '\n');
40
+ stream.end();
41
+ } catch (e) {
42
+ // Ignore logging errors
43
+ }
44
+
45
+ if (process.env.DEBUG) {
46
+ console.error(`[🔒 安全] ${event}:`, details);
47
+ }
48
+ }
49
+
50
+ // ============================================================================
51
+ // Main Installation Logic
52
+ // ============================================================================
53
+
14
54
  console.log('\n🚀 Setting up aicode-cli...');
15
55
 
56
+ // Record installation start
57
+ logSecurity('install_start', {
58
+ version: '0.0.51-beta.1',
59
+ homeDir: homedir(),
60
+ timestamp: new Date().toISOString(),
61
+ });
62
+
16
63
  // Get the current user's home directory
17
64
  const homeDir = homedir();
18
65
  const aicodeCliDir = join(homeDir, '.aicode-cli');
@@ -33,8 +80,19 @@ if (existsSync(piAgentPackageJson)) {
33
80
  piPkg.piConfig.configDir = '.aicode-cli';
34
81
  writeFileSync(piAgentPackageJson, JSON.stringify(piPkg, null, 2) + '\n');
35
82
  console.log(`✓ Updated pi-coding-agent app name: ${oldName} -> aicode-cli`);
83
+
84
+ logSecurity('file_modified', {
85
+ file: piAgentPackageJson,
86
+ action: 'Updated piConfig',
87
+ changes: ['name', 'configDir'],
88
+ });
36
89
  } catch (e) {
37
90
  console.log(`⚠ Could not update pi-coding-agent config: ${e.message}`);
91
+ logSecurity('file_modified', {
92
+ file: piAgentPackageJson,
93
+ action: 'Update failed',
94
+ error: e.message,
95
+ }, 'warn');
38
96
  }
39
97
  }
40
98
 
@@ -47,6 +105,11 @@ if (existsSync(piAgentCliJs)) {
47
105
  cliContent = cliContent.replace(/process\.title = "pi"/g, 'process.title = "aicode-cli"');
48
106
  writeFileSync(piAgentCliJs, cliContent);
49
107
  console.log(`✓ Updated pi-coding-agent process.title`);
108
+
109
+ logSecurity('file_modified', {
110
+ file: piAgentCliJs,
111
+ action: 'Updated process.title',
112
+ });
50
113
  }
51
114
  } catch (e) {
52
115
  console.log(`⚠ Could not update pi-coding-agent cli.js: ${e.message}`);
@@ -62,6 +125,11 @@ if (existsSync(piAgentConfigJs)) {
62
125
  configContent = configContent.replace(/pkg\.piConfig\?\.name \|\| "pi"/g, 'pkg.piConfig?.name || "aicode-cli"');
63
126
  writeFileSync(piAgentConfigJs, configContent);
64
127
  console.log(`✓ Updated pi-coding-agent config.js APP_NAME`);
128
+
129
+ logSecurity('file_modified', {
130
+ file: piAgentConfigJs,
131
+ action: 'Updated APP_NAME',
132
+ });
65
133
  }
66
134
  } catch (e) {
67
135
  console.log(`⚠ Could not update pi-coding-agent config.js: ${e.message}`);
@@ -77,6 +145,11 @@ if (existsSync(slashCommandsJs)) {
77
145
  content = content.replace(/Quit pi/g, 'Quit aicode-cli');
78
146
  writeFileSync(slashCommandsJs, content);
79
147
  console.log(`✓ Updated slash command description`);
148
+
149
+ logSecurity('file_modified', {
150
+ file: slashCommandsJs,
151
+ action: 'Updated slash commands',
152
+ });
80
153
  }
81
154
  } catch (e) {
82
155
  console.log(`⚠ Could not update slash commands: ${e.message}`);
@@ -103,7 +176,6 @@ function copyDirRecursive(src, dest) {
103
176
  const isDirectory = exists && stats.isDirectory();
104
177
 
105
178
  if (isDirectory) {
106
- // Create directory if it doesn't exist
107
179
  if (!existsSync(dest)) {
108
180
  mkdirSync(dest, { recursive: true });
109
181
  }
@@ -115,9 +187,7 @@ function copyDirRecursive(src, dest) {
115
187
  copyDirRecursive(srcPath, destPath);
116
188
  });
117
189
  } else {
118
- // Only copy if source exists AND destination doesn't exist (preserve user files)
119
190
  if (exists && !existsSync(dest)) {
120
- // Ensure parent directory exists
121
191
  const parentDir = dirname(dest);
122
192
  if (!existsSync(parentDir)) {
123
193
  mkdirSync(parentDir, { recursive: true });
@@ -171,19 +241,14 @@ const FORCE_OVERWRITE_LIST = [
171
241
  'SYSTEM.md',
172
242
  'dangerous-patterns.json',
173
243
  '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
244
+ 'help.md',
245
+ 'extensions/*.ts',
180
246
  'prompts/*.md',
181
- // Force overwrite all skills SKILL.md files
182
247
  'skills/*/SKILL.md',
183
- 'skills/*/*/SKILL.md',
248
+ 'skills/*/*/SKILL.md',
184
249
  ];
185
250
 
186
- // Function to expand glob patterns like "skills/*/SKILL.md" or "prompts/*.md"
251
+ // Function to expand glob patterns
187
252
  function expandGlob(pattern) {
188
253
  const results = [];
189
254
 
@@ -201,21 +266,17 @@ function expandGlob(pattern) {
201
266
  if (!existsSync(cp)) continue;
202
267
 
203
268
  if (hasWildcard) {
204
- // Handle wildcard patterns like * or *.md
205
269
  const entries = readdirSync(cp);
206
- // Convert glob pattern to regex: *.md -> ^.*\.md$
207
270
  const regex = new RegExp('^' + part.replace(/\*/g, '.*') + '$');
208
271
 
209
272
  for (const entry of entries) {
210
273
  if (regex.test(entry)) {
211
274
  const fullPath = join(cp, entry);
212
275
  if (isLastPart) {
213
- // Last part - match files (not directories)
214
276
  if (statSync(fullPath).isFile()) {
215
277
  nextPaths.push(fullPath);
216
278
  }
217
279
  } else {
218
- // Not the last part - only directories
219
280
  if (statSync(fullPath).isDirectory()) {
220
281
  nextPaths.push(fullPath);
221
282
  }
@@ -243,7 +304,6 @@ function expandGlob(pattern) {
243
304
 
244
305
  // Function to force overwrite specific files/folders
245
306
  function forceOverwriteList(relativePaths) {
246
- // Expand glob patterns first
247
307
  const expandedPaths = [];
248
308
  for (const relativePath of relativePaths) {
249
309
  expandedPaths.push(...expandGlob(relativePath));
@@ -257,14 +317,12 @@ function forceOverwriteList(relativePaths) {
257
317
  try {
258
318
  const stats = statSync(srcPath);
259
319
  if (stats.isDirectory()) {
260
- // Force copy directory
261
320
  if (!existsSync(destPath)) {
262
321
  mkdirSync(destPath, { recursive: true });
263
322
  }
264
323
  forceCopyDirRecursive(srcPath, destPath);
265
324
  console.log(`✓ Force overwritten (dir): ${relativePath}`);
266
325
  } else {
267
- // Force copy file
268
326
  const parentDir = dirname(destPath);
269
327
  if (!existsSync(parentDir)) {
270
328
  mkdirSync(parentDir, { recursive: true });
@@ -281,20 +339,17 @@ function forceOverwriteList(relativePaths) {
281
339
  });
282
340
  }
283
341
 
284
- // Copy config/agent contents to ~/.aicode-cli/agent only if they don't exist (preserve user customizations)
342
+ // Copy config/agent contents to ~/.aicode-cli/agent
285
343
  if (existsSync(packageConfigDir)) {
286
344
  console.log(`📁 Syncing default config files to ${agentDir} (preserving user customizations)`);
287
345
 
288
- // Copy directory recursively (only if destination doesn't exist)
289
346
  copyDirRecursive(packageConfigDir, agentDir);
290
347
  console.log('✓ Default config files synced successfully');
291
348
 
292
- // Force overwrite specified files/folders
293
349
  if (FORCE_OVERWRITE_LIST.length > 0) {
294
350
  forceOverwriteList(FORCE_OVERWRITE_LIST);
295
351
  }
296
352
 
297
- // Check for extensions
298
353
  const extensionsDir = join(agentDir, 'extensions');
299
354
  if (existsSync(extensionsDir)) {
300
355
  try {
@@ -311,19 +366,17 @@ if (existsSync(packageConfigDir)) {
311
366
  }
312
367
 
313
368
  // ============================================================================
314
- // Reset User-Agent placeholder in models.json (so CLI can replace it on each run)
369
+ // Reset User-Agent placeholder in models.json
315
370
  // ============================================================================
316
371
  const modelsPath = join(agentDir, 'models.json');
317
372
  if (existsSync(modelsPath)) {
318
373
  try {
319
374
  let content = readFileSync(modelsPath, 'utf-8');
320
- // 只重置 headers.User-Agent 下的版本号为占位符
321
375
  try {
322
376
  const config = JSON.parse(content);
323
377
  for (const provider of Object.values(config.providers || {})) {
324
378
  if (provider.headers && provider.headers['User-Agent']) {
325
379
  const ua = provider.headers['User-Agent'];
326
- // 如果是以 aicode-cli/ 开头,替换为占位符
327
380
  if (ua && ua.startsWith('aicode-cli/')) {
328
381
  provider.headers['User-Agent'] = '${VERSION}';
329
382
  }
@@ -334,6 +387,11 @@ if (existsSync(modelsPath)) {
334
387
  if (newContent !== content) {
335
388
  writeFileSync(modelsPath, newContent, 'utf-8');
336
389
  console.log('✓ Reset User-Agent placeholder in models.json');
390
+
391
+ logSecurity('file_modified', {
392
+ file: modelsPath,
393
+ action: 'Reset User-Agent placeholder',
394
+ });
337
395
  }
338
396
  } catch (e) {
339
397
  console.log(`⚠ Could not reset User-Agent placeholder: ${e.message}`);
@@ -349,24 +407,38 @@ if (existsSync(modelsPath)) {
349
407
  }
350
408
 
351
409
  // 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)');
410
+ (async () => {
411
+ try {
412
+ if (!existsSync(piDir)) {
413
+ await fsSymlink(aicodeCliDir, piDir, 'dir');
414
+ console.log(`✓ Created symbolic link: ${piDir} -> ${aicodeCliDir}`);
415
+
416
+ logSecurity('symlink_create', {
417
+ target: piDir,
418
+ source: aicodeCliDir,
419
+ success: true,
420
+ });
421
+ } else {
422
+ console.log(`✓ ${piDir} already exists`);
423
+ }
424
+ } catch (e) {
425
+ if (e.code === 'EEXIST') {
426
+ console.log(`✓ Symbolic link already exists: ${piDir}`);
427
+ } else {
428
+ console.log(`⚠ Could not create symbolic link: ${e.message}`);
429
+ console.log('\n📌 To fix this, please run manually:');
430
+ console.log(' ln -s ~/.aicode-cli ~/.pi');
431
+ console.log('\n (If ~/.pi exists, run: rm -rf ~/.pi && ln -s ~/.aicode-cli ~/.pi)');
432
+
433
+ logSecurity('symlink_create', {
434
+ target: piDir,
435
+ source: aicodeCliDir,
436
+ success: false,
437
+ error: e.message,
438
+ }, 'warn');
439
+ }
368
440
  }
369
- }
441
+ })();
370
442
 
371
443
  // Check and display models config
372
444
  if (existsSync(modelsPath)) {
@@ -383,8 +455,16 @@ if (existsSync(modelsPath)) {
383
455
  console.log(`⚠ No models.json found at ${modelsPath}`);
384
456
  }
385
457
 
458
+ // Record installation complete
459
+ logSecurity('install_complete', {
460
+ success: true,
461
+ aicodeCliDir,
462
+ agentDir,
463
+ piDir,
464
+ });
465
+
386
466
  console.log('\n✨ Setup complete!');
387
467
  console.log('📝 Configuration files are in ~/.aicode-cli/');
388
468
  console.log('🔗 ~/.pi is linked to ~/.aicode-cli (ensuring model configs work)');
389
469
  console.log('🚀 Run with: aicode-cli');
390
- console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');
470
+ console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');