@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.
- package/bin/cli.js +11 -10
- package/config/agent/extensions/aicode-advisor.ts +707 -0
- package/config/agent/extensions/aicode-btw.ts +2273 -0
- package/config/agent/extensions/aicode-plan.ts +903 -0
- package/config/agent/extensions/aicode-tips.ts +44 -8
- package/config/agent/extensions/bash-check.ts +1 -1
- package/config/agent/extensions/context-monitor.ts +1 -1
- package/config/agent/extensions/context.ts +3 -3
- package/config/agent/extensions/custom-footer.ts +22 -4
- package/config/agent/extensions/custom.ts +118 -84
- package/config/agent/extensions/error-monitor.ts +1 -1
- package/config/agent/extensions/goal.ts +450 -0
- package/config/agent/extensions/msg-debug.ts +49 -10
- package/config/agent/extensions/review.ts +3 -3
- package/config/agent/extensions/working-msg.ts +44 -23
- package/config/agent/help.md +25 -7
- package/config/agent/models.json +7 -7
- package/config/agent/settings.json +0 -2
- package/dist/banner.d.ts +5 -1
- package/dist/banner.d.ts.map +1 -1
- package/dist/banner.js +106 -7
- package/dist/banner.js.map +1 -1
- package/dist/config-loader.d.ts +2 -0
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +81 -7
- package/dist/config-loader.js.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/security-logger.d.ts +69 -0
- package/dist/security-logger.d.ts.map +1 -0
- package/dist/security-logger.js +178 -0
- package/dist/security-logger.js.map +1 -0
- package/package.json +18 -7
- 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.
|
|
4
|
-
"description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent
|
|
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
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
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"
|
package/scripts/postinstall.js
CHANGED
|
@@ -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 {
|
|
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', '@
|
|
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', '@
|
|
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', '@
|
|
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', '@
|
|
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
|
-
'
|
|
175
|
-
'extensions
|
|
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
|
-
|
|
249
|
+
'skills/*/*/SKILL.md',
|
|
184
250
|
];
|
|
185
251
|
|
|
186
|
-
// Function to expand glob patterns
|
|
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
|
|
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
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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');
|