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

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/cli.js CHANGED
@@ -144,7 +144,7 @@ if (config.banner === '__default__' || config.banner !== '') {
144
144
  // 检查新版本
145
145
  async function checkForUpdates() {
146
146
  try {
147
- const { execSync } = await import('child_process');
147
+ const { execFileSync } = await import('child_process');
148
148
  // 读取本地版本 - 向上查找 package.json
149
149
  let rootDir = __dirname;
150
150
  let localVersion = null;
@@ -158,9 +158,9 @@ async function checkForUpdates() {
158
158
  }
159
159
  if (!localVersion) return;
160
160
 
161
- // 获取当前 registry
162
- const registry = execSync('npm config get registry', { encoding: 'utf8', stdio: 'pipe' }).trim();
163
- const latestVersion = execSync(`npm view @ia-ccun/code-agent-cli version --registry=${registry}`, { encoding: 'utf8', stdio: 'pipe' }).trim();
161
+ // 🔒 Security: Use execFileSync instead of execSync to prevent command injection
162
+ const registry = execFileSync('npm', ['config', 'get', 'registry'], { encoding: 'utf8', stdio: 'pipe' }).trim();
163
+ const latestVersion = execFileSync('npm', ['view', '@ia-ccun/code-agent-cli', 'version', '--registry=' + registry], { encoding: 'utf8', stdio: 'pipe' }).trim();
164
164
 
165
165
  // 比较版本号
166
166
  const localParts = localVersion.split('.').map(Number);
@@ -175,7 +175,8 @@ async function checkForUpdates() {
175
175
  try {
176
176
  const cacheDir = join(process.env.HOME || process.env.USERPROFILE, '.aicode-cli');
177
177
  if (!existsSync(cacheDir)) {
178
- execSync(`mkdir -p "${cacheDir}"`, { stdio: 'pipe' });
178
+ // 🔒 Security: Use execFileSync for mkdir instead of execSync
179
+ execFileSync('mkdir', ['-p', cacheDir], { stdio: 'pipe' });
179
180
  }
180
181
  const cacheData = {
181
182
  localVersion,
@@ -457,3 +457,5 @@ aicode-cli --verbose
457
457
  ---
458
458
 
459
459
  *世界那么哒 · AI 实践落地 @2026*
460
+
461
+ **作者**: xujianjiang
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * 配置加载器
3
+ * 🔒 Security: Validate path to prevent path traversal attacks
3
4
  */
4
5
  import type { AppConfig } from './config.js';
5
6
  /**
6
7
  * 获取配置目录
8
+ * 🔒 Security: Validate path to prevent path traversal attacks
7
9
  */
8
10
  export declare function getConfigDir(): string;
9
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,SAAS,CAyBtC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAErC"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAuErC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,SAAS,CA8CtC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAErC"}
@@ -1,26 +1,80 @@
1
1
  /**
2
2
  * 配置加载器
3
+ * 🔒 Security: Validate path to prevent path traversal attacks
3
4
  */
4
5
  import { homedir } from 'os';
5
- import { join } from 'path';
6
+ import { join, normalize, resolve, isAbsolute } from 'path';
6
7
  import { existsSync, readFileSync } from 'fs';
7
8
  import { defaultConfig } from './config.js';
9
+ import { logSecurity, logError, SecurityEventType, LogLevel } from './security-logger.js';
8
10
  let cachedConfig = null;
9
11
  /**
10
12
  * 获取配置目录
13
+ * 🔒 Security: Validate path to prevent path traversal attacks
11
14
  */
12
15
  export function getConfigDir() {
13
- // 优先级: 环境变量 > 命令行参数(后续实现) > 默认值
16
+ // 优先级:环境变量 > 命令行参数 (后续实现) > 默认值
14
17
  const configDir = process.env.AICODE_CLI_CONFIG_DIR;
15
18
  if (configDir) {
19
+ let resolvedPath;
16
20
  // 展开环境变量中的路径
17
21
  if (configDir === "~") {
18
- return homedir();
22
+ resolvedPath = homedir();
19
23
  }
20
- if (configDir.startsWith("~/")) {
21
- return join(homedir(), configDir.slice(2));
24
+ else if (configDir.startsWith("~/")) {
25
+ resolvedPath = join(homedir(), configDir.slice(2));
22
26
  }
23
- return configDir;
27
+ else if (isAbsolute(configDir)) {
28
+ resolvedPath = configDir;
29
+ }
30
+ else {
31
+ // 相对路径:相对于当前工作目录
32
+ resolvedPath = resolve(configDir);
33
+ }
34
+ // 🔒 规范化路径(消除 .. 和 .)
35
+ const normalized = normalize(resolvedPath);
36
+ // 🔒 安全检查 1: 防止路径遍历
37
+ if (normalized.includes('..')) {
38
+ logSecurity(SecurityEventType.PATH_TRAVERSAL_BLOCKED, {
39
+ attemptedPath: configDir,
40
+ normalizedPath: normalized,
41
+ reason: 'Path contains ..',
42
+ action: 'Rejected and using default',
43
+ }, LogLevel.WARN);
44
+ console.warn('[安全警告] 配置路径包含 ..,使用默认值');
45
+ return join(homedir(), defaultConfig.configDir);
46
+ }
47
+ // 🔒 安全检查 2: 限制在家目录内(防止写入系统目录)
48
+ const homeDir = homedir();
49
+ if (!normalized.startsWith(homeDir)) {
50
+ logSecurity(SecurityEventType.PATH_VALIDATION, {
51
+ attemptedPath: configDir,
52
+ normalizedPath: normalized,
53
+ homeDir: homeDir,
54
+ reason: 'Path outside home directory',
55
+ action: 'Rejected and using default',
56
+ }, LogLevel.WARN);
57
+ console.warn(`[安全警告] 配置目录 (${normalized}) 在家目录外,使用默认值`);
58
+ return join(homedir(), defaultConfig.configDir);
59
+ }
60
+ // 🔒 安全检查 3: 确保是绝对路径
61
+ if (!isAbsolute(normalized)) {
62
+ logSecurity(SecurityEventType.PATH_VALIDATION, {
63
+ attemptedPath: configDir,
64
+ normalizedPath: normalized,
65
+ reason: 'Path is not absolute',
66
+ action: 'Rejected and using default',
67
+ }, LogLevel.WARN);
68
+ console.warn('[安全警告] 配置路径不是绝对路径,使用默认值');
69
+ return join(homedir(), defaultConfig.configDir);
70
+ }
71
+ // ✅ 路径验证通过
72
+ logSecurity(SecurityEventType.PATH_VALIDATION, {
73
+ attemptedPath: configDir,
74
+ normalizedPath: normalized,
75
+ action: 'Accepted',
76
+ });
77
+ return normalized;
24
78
  }
25
79
  return join(homedir(), defaultConfig.configDir);
26
80
  }
@@ -37,12 +91,32 @@ export function loadConfig() {
37
91
  const content = readFileSync(configPath, 'utf-8');
38
92
  const userConfig = JSON.parse(content);
39
93
  cachedConfig = { ...defaultConfig, ...userConfig };
94
+ // ✅ 记录配置加载成功
95
+ logSecurity(SecurityEventType.CONFIG_LOAD, {
96
+ path: configPath,
97
+ success: true,
98
+ hasUserConfig: true,
99
+ });
40
100
  }
41
- catch {
101
+ catch (error) {
102
+ // ❌ 记录配置解析错误
103
+ logError('Failed to parse config.json', error, { path: configPath });
104
+ logSecurity(SecurityEventType.CONFIG_ERROR, {
105
+ path: configPath,
106
+ error: error.message,
107
+ action: 'Using default config',
108
+ }, LogLevel.WARN);
42
109
  cachedConfig = { ...defaultConfig };
43
110
  }
44
111
  }
45
112
  else {
113
+ // ⚠️ 记录配置文件不存在
114
+ logSecurity(SecurityEventType.CONFIG_LOAD, {
115
+ path: configPath,
116
+ success: true,
117
+ exists: false,
118
+ action: 'Using default config',
119
+ });
46
120
  cachedConfig = { ...defaultConfig };
47
121
  }
48
122
  // 设置环境变量,让 pi-coding-agent 使用我们的配置目录
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,IAAI,YAAY,GAAqB,IAAI,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,gCAAgC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAEpD,IAAI,SAAS,EAAE,CAAC;QACd,aAAa;QACb,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,OAAO,OAAO,EAAE,CAAC;QACnB,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;IAEvD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,UAAU,EAAE,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IACtC,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,QAAQ,CAAC;IAEnD,OAAO,YAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,EAAE,CAAC;AACtB,CAAC"}
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAE1F,IAAI,YAAY,GAAqB,IAAI,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,gCAAgC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAEpD,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,YAAoB,CAAC;QAEzB,aAAa;QACb,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,YAAY,GAAG,OAAO,EAAE,CAAC;QAC3B,CAAC;aAAM,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,YAAY,GAAG,SAAS,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,iBAAiB;YACjB,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QAED,sBAAsB;QACtB,MAAM,UAAU,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC;QAE3C,oBAAoB;QACpB,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,CAAC,iBAAiB,CAAC,sBAAsB,EAAE;gBACpD,aAAa,EAAE,SAAS;gBACxB,cAAc,EAAE,UAAU;gBAC1B,MAAM,EAAE,kBAAkB;gBAC1B,MAAM,EAAE,4BAA4B;aACrC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QAClD,CAAC;QAED,+BAA+B;QAC/B,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,WAAW,CAAC,iBAAiB,CAAC,eAAe,EAAE;gBAC7C,aAAa,EAAE,SAAS;gBACxB,cAAc,EAAE,UAAU;gBAC1B,OAAO,EAAE,OAAO;gBAChB,MAAM,EAAE,6BAA6B;gBACrC,MAAM,EAAE,4BAA4B;aACrC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,gBAAgB,UAAU,eAAe,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QAClD,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,WAAW,CAAC,iBAAiB,CAAC,eAAe,EAAE;gBAC7C,aAAa,EAAE,SAAS;gBACxB,cAAc,EAAE,UAAU;gBAC1B,MAAM,EAAE,sBAAsB;gBAC9B,MAAM,EAAE,4BAA4B;aACrC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YACxC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QAClD,CAAC;QAED,WAAW;QACX,WAAW,CAAC,iBAAiB,CAAC,eAAe,EAAE;YAC7C,aAAa,EAAE,SAAS;YACxB,cAAc,EAAE,UAAU;YAC1B,MAAM,EAAE,UAAU;SACnB,CAAC,CAAC;QAEH,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;IAEvD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,UAAU,EAAE,CAAC;YAEnD,aAAa;YACb,WAAW,CAAC,iBAAiB,CAAC,WAAW,EAAE;gBACzC,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,IAAI;aACpB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,aAAa;YACb,QAAQ,CAAC,6BAA6B,EAAE,KAAc,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YAC9E,WAAW,CAAC,iBAAiB,CAAC,YAAY,EAAE;gBAC1C,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAG,KAAe,CAAC,OAAO;gBAC/B,MAAM,EAAE,sBAAsB;aAC/B,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClB,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,eAAe;QACf,WAAW,CAAC,iBAAiB,CAAC,WAAW,EAAE;YACzC,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,sBAAsB;SAC/B,CAAC,CAAC;QACH,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IACtC,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,QAAQ,CAAC;IAEnD,OAAO,YAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,EAAE,CAAC;AACtB,CAAC"}
@@ -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.2",
3
+ "version": "0.0.51-beta.3",
4
4
  "description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent(pi)原理封装的编码智能体(供学习使用)。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,7 +42,16 @@
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": {
48
57
  "@types/node": "^22.10.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 });
@@ -172,19 +242,25 @@ const FORCE_OVERWRITE_LIST = [
172
242
  'dangerous-patterns.json',
173
243
  'settings.json',
174
244
  'help.md',
175
- 'extensions/working-msg.ts',
245
+ // Extensions - all files
246
+ 'extensions/aicode-tips.ts',
247
+ 'extensions/bash-check.ts',
248
+ 'extensions/context-monitor.ts',
249
+ 'extensions/context.ts',
176
250
  'extensions/custom-footer.ts',
177
251
  'extensions/custom.ts',
178
- 'extensions/bash-check.ts',
179
- 'extensions/aicode-tips.ts',
180
- // Force overwrite all prompts .md files
252
+ 'extensions/error-monitor.ts',
253
+ 'extensions/msg-debug.ts',
254
+ 'extensions/review.ts',
255
+ 'extensions/working-msg.ts',
256
+ // Prompts
181
257
  'prompts/*.md',
182
- // Force overwrite all skills SKILL.md files
258
+ // Skills
183
259
  'skills/*/SKILL.md',
184
- 'skills/*/*/SKILL.md',
260
+ 'skills/*/*/SKILL.md',
185
261
  ];
186
262
 
187
- // Function to expand glob patterns like "skills/*/SKILL.md" or "prompts/*.md"
263
+ // Function to expand glob patterns
188
264
  function expandGlob(pattern) {
189
265
  const results = [];
190
266
 
@@ -202,21 +278,17 @@ function expandGlob(pattern) {
202
278
  if (!existsSync(cp)) continue;
203
279
 
204
280
  if (hasWildcard) {
205
- // Handle wildcard patterns like * or *.md
206
281
  const entries = readdirSync(cp);
207
- // Convert glob pattern to regex: *.md -> ^.*\.md$
208
282
  const regex = new RegExp('^' + part.replace(/\*/g, '.*') + '$');
209
283
 
210
284
  for (const entry of entries) {
211
285
  if (regex.test(entry)) {
212
286
  const fullPath = join(cp, entry);
213
287
  if (isLastPart) {
214
- // Last part - match files (not directories)
215
288
  if (statSync(fullPath).isFile()) {
216
289
  nextPaths.push(fullPath);
217
290
  }
218
291
  } else {
219
- // Not the last part - only directories
220
292
  if (statSync(fullPath).isDirectory()) {
221
293
  nextPaths.push(fullPath);
222
294
  }
@@ -244,7 +316,6 @@ function expandGlob(pattern) {
244
316
 
245
317
  // Function to force overwrite specific files/folders
246
318
  function forceOverwriteList(relativePaths) {
247
- // Expand glob patterns first
248
319
  const expandedPaths = [];
249
320
  for (const relativePath of relativePaths) {
250
321
  expandedPaths.push(...expandGlob(relativePath));
@@ -258,14 +329,12 @@ function forceOverwriteList(relativePaths) {
258
329
  try {
259
330
  const stats = statSync(srcPath);
260
331
  if (stats.isDirectory()) {
261
- // Force copy directory
262
332
  if (!existsSync(destPath)) {
263
333
  mkdirSync(destPath, { recursive: true });
264
334
  }
265
335
  forceCopyDirRecursive(srcPath, destPath);
266
336
  console.log(`✓ Force overwritten (dir): ${relativePath}`);
267
337
  } else {
268
- // Force copy file
269
338
  const parentDir = dirname(destPath);
270
339
  if (!existsSync(parentDir)) {
271
340
  mkdirSync(parentDir, { recursive: true });
@@ -282,20 +351,17 @@ function forceOverwriteList(relativePaths) {
282
351
  });
283
352
  }
284
353
 
285
- // Copy config/agent contents to ~/.aicode-cli/agent only if they don't exist (preserve user customizations)
354
+ // Copy config/agent contents to ~/.aicode-cli/agent
286
355
  if (existsSync(packageConfigDir)) {
287
356
  console.log(`📁 Syncing default config files to ${agentDir} (preserving user customizations)`);
288
357
 
289
- // Copy directory recursively (only if destination doesn't exist)
290
358
  copyDirRecursive(packageConfigDir, agentDir);
291
359
  console.log('✓ Default config files synced successfully');
292
360
 
293
- // Force overwrite specified files/folders
294
361
  if (FORCE_OVERWRITE_LIST.length > 0) {
295
362
  forceOverwriteList(FORCE_OVERWRITE_LIST);
296
363
  }
297
364
 
298
- // Check for extensions
299
365
  const extensionsDir = join(agentDir, 'extensions');
300
366
  if (existsSync(extensionsDir)) {
301
367
  try {
@@ -312,19 +378,17 @@ if (existsSync(packageConfigDir)) {
312
378
  }
313
379
 
314
380
  // ============================================================================
315
- // Reset User-Agent placeholder in models.json (so CLI can replace it on each run)
381
+ // Reset User-Agent placeholder in models.json
316
382
  // ============================================================================
317
383
  const modelsPath = join(agentDir, 'models.json');
318
384
  if (existsSync(modelsPath)) {
319
385
  try {
320
386
  let content = readFileSync(modelsPath, 'utf-8');
321
- // 只重置 headers.User-Agent 下的版本号为占位符
322
387
  try {
323
388
  const config = JSON.parse(content);
324
389
  for (const provider of Object.values(config.providers || {})) {
325
390
  if (provider.headers && provider.headers['User-Agent']) {
326
391
  const ua = provider.headers['User-Agent'];
327
- // 如果是以 aicode-cli/ 开头,替换为占位符
328
392
  if (ua && ua.startsWith('aicode-cli/')) {
329
393
  provider.headers['User-Agent'] = '${VERSION}';
330
394
  }
@@ -335,6 +399,11 @@ if (existsSync(modelsPath)) {
335
399
  if (newContent !== content) {
336
400
  writeFileSync(modelsPath, newContent, 'utf-8');
337
401
  console.log('✓ Reset User-Agent placeholder in models.json');
402
+
403
+ logSecurity('file_modified', {
404
+ file: modelsPath,
405
+ action: 'Reset User-Agent placeholder',
406
+ });
338
407
  }
339
408
  } catch (e) {
340
409
  console.log(`⚠ Could not reset User-Agent placeholder: ${e.message}`);
@@ -350,24 +419,38 @@ if (existsSync(modelsPath)) {
350
419
  }
351
420
 
352
421
  // Create symlink ~/.pi linking to ~/.aicode-cli (if not exists)
353
- try {
354
- if (!existsSync(piDir)) {
355
- // Create symbolic link using shell command (works on all platforms)
356
- execSync(`ln -s "${aicodeCliDir}" "${piDir}"`, { stdio: 'pipe' });
357
- console.log(`✓ Created symbolic link: ${piDir} -> ${aicodeCliDir}`);
358
- } else {
359
- console.log(`✓ ${piDir} already exists`);
360
- }
361
- } catch (e) {
362
- if (e.message.includes('File exists')) {
363
- console.log(`✓ Symbolic link already exists: ${piDir}`);
364
- } else {
365
- console.log(`⚠ Could not create symbolic link: ${e.message}`);
366
- console.log('\n📌 To fix this, please run manually:');
367
- console.log(' ln -s ~/.aicode-cli ~/.pi');
368
- console.log('\n (If ~/.pi exists, run: rm -rf ~/.pi && ln -s ~/.aicode-cli ~/.pi)');
422
+ (async () => {
423
+ try {
424
+ if (!existsSync(piDir)) {
425
+ await fsSymlink(aicodeCliDir, piDir, 'dir');
426
+ console.log(`✓ Created symbolic link: ${piDir} -> ${aicodeCliDir}`);
427
+
428
+ logSecurity('symlink_create', {
429
+ target: piDir,
430
+ source: aicodeCliDir,
431
+ success: true,
432
+ });
433
+ } else {
434
+ console.log(`✓ ${piDir} already exists`);
435
+ }
436
+ } catch (e) {
437
+ if (e.code === 'EEXIST') {
438
+ console.log(`✓ Symbolic link already exists: ${piDir}`);
439
+ } else {
440
+ console.log(`⚠ Could not create symbolic link: ${e.message}`);
441
+ console.log('\n📌 To fix this, please run manually:');
442
+ console.log(' ln -s ~/.aicode-cli ~/.pi');
443
+ console.log('\n (If ~/.pi exists, run: rm -rf ~/.pi && ln -s ~/.aicode-cli ~/.pi)');
444
+
445
+ logSecurity('symlink_create', {
446
+ target: piDir,
447
+ source: aicodeCliDir,
448
+ success: false,
449
+ error: e.message,
450
+ }, 'warn');
451
+ }
369
452
  }
370
- }
453
+ })();
371
454
 
372
455
  // Check and display models config
373
456
  if (existsSync(modelsPath)) {
@@ -384,8 +467,16 @@ if (existsSync(modelsPath)) {
384
467
  console.log(`⚠ No models.json found at ${modelsPath}`);
385
468
  }
386
469
 
470
+ // Record installation complete
471
+ logSecurity('install_complete', {
472
+ success: true,
473
+ aicodeCliDir,
474
+ agentDir,
475
+ piDir,
476
+ });
477
+
387
478
  console.log('\n✨ Setup complete!');
388
479
  console.log('📝 Configuration files are in ~/.aicode-cli/');
389
480
  console.log('🔗 ~/.pi is linked to ~/.aicode-cli (ensuring model configs work)');
390
481
  console.log('🚀 Run with: aicode-cli');
391
- console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');
482
+ console.log('\n📖 For more information, see: https://www.npmjs.com/package/@ia-ccun/code-agent-cli');