@ia-ccun/code-agent-cli 0.0.17 → 0.0.19

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
@@ -2,13 +2,16 @@
2
2
 
3
3
  /**
4
4
  * aicode-cli
5
- * AI Coding Agent CLI - 基于 @mariozechner/pi-coding-agent
5
+ * AI Coding Agent CLI
6
6
  */
7
7
 
8
- import { spawn, execSync } from 'child_process';
8
+ // 设置终端标题
9
+ process.title = 'aicode-cli';
10
+
11
+ import { spawn } from 'child_process';
9
12
  import { fileURLToPath } from 'url';
10
13
  import { dirname, join } from 'path';
11
- import { existsSync, readFileSync } from 'fs';
14
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
12
15
  import { getConfig, getConfigDir, loadConfig } from '../dist/config-loader.js';
13
16
  import { generateBanner } from '../dist/banner.js';
14
17
 
@@ -19,30 +22,30 @@ const __dirname = dirname(__filename);
19
22
  function parseArgs() {
20
23
  const args = process.argv.slice(2);
21
24
  let configDir;
22
- let webMode = false;
23
- let webPort;
24
-
25
+
26
+ // 处理 --version 参数,显示 CLI 自己的版本
27
+ if (args.includes('--version') || args.includes('-v')) {
28
+ const pkgPath = join(__dirname, '..', 'package.json');
29
+ if (existsSync(pkgPath)) {
30
+ const { version } = JSON.parse(readFileSync(pkgPath, 'utf-8'));
31
+ console.log(version);
32
+ }
33
+ process.exit(0);
34
+ }
35
+
25
36
  for (let i = 0; i < args.length; i++) {
26
37
  const arg = args[i];
27
38
  if (arg === '--config' || arg === '-c') {
28
39
  configDir = args[i + 1];
29
40
  args.splice(i, 2);
30
- i--;
31
- } else if (arg === '--web' || arg === '--webui') {
32
- webMode = true;
33
- args.splice(i, 1);
34
- i--;
35
- } else if (arg === '--port' || arg === '-p') {
36
- webPort = parseInt(args[i + 1], 10);
37
- args.splice(i, 2);
38
- i--;
41
+ break;
39
42
  }
40
43
  }
41
-
42
- return { remainingArgs: args, configDir, webMode, webPort };
44
+
45
+ return { remainingArgs: args, configDir };
43
46
  }
44
47
 
45
- const { remainingArgs, configDir: cliConfigDir, webMode, webPort } = parseArgs();
48
+ const { remainingArgs, configDir: cliConfigDir } = parseArgs();
46
49
 
47
50
  // 优先使用命令行参数,其次使用环境变量,最后使用默认值
48
51
  const effectiveConfigDir = cliConfigDir || process.env.AICODE_CLI_CONFIG_DIR;
@@ -57,91 +60,106 @@ if (effectiveConfigDir) {
57
60
  config = getConfig();
58
61
  }
59
62
 
63
+ // ============================================================================
64
+ // 替换 models.json 中的 ${CLI_VERSION} 占位符
65
+ // ============================================================================
66
+ function replaceCLIVersionPlaceholder() {
67
+ try {
68
+ // 找到项目根目录的 package.json
69
+ let rootDir = __dirname;
70
+ while (rootDir !== dirname(rootDir)) {
71
+ const pkgPath = join(rootDir, 'package.json');
72
+ if (existsSync(pkgPath)) {
73
+ const { version } = JSON.parse(readFileSync(pkgPath, 'utf-8'));
74
+ const versionString = `aicode-cli/${version}`;
75
+
76
+ // 查找配置目录下的 models.json
77
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
78
+ const configDirs = [
79
+ join(homeDir, '.aicode-cli', 'agent'),
80
+ join(homeDir, '.pi', 'agent'),
81
+ ];
82
+
83
+ for (const dir of configDirs) {
84
+ const modelsPath = join(dir, 'models.json');
85
+ if (!existsSync(modelsPath)) continue;
86
+
87
+ try {
88
+ let content = readFileSync(modelsPath, 'utf-8');
89
+ const config = JSON.parse(content);
90
+
91
+ // 遍历所有 provider,替换 headers.User-Agent 中的占位符
92
+ for (const provider of Object.values(config.providers || {})) {
93
+ if (provider.headers && provider.headers['User-Agent']) {
94
+ const ua = provider.headers['User-Agent'];
95
+ if (ua && ua.startsWith('${') && ua.endsWith('}')) {
96
+ provider.headers['User-Agent'] = versionString;
97
+ }
98
+ }
99
+ }
100
+
101
+ const newContent = JSON.stringify(config, null, 2);
102
+ if (newContent !== content) {
103
+ writeFileSync(modelsPath, newContent, 'utf-8');
104
+ }
105
+ } catch (e) {
106
+ // 忽略错误
107
+ }
108
+ }
109
+ break;
110
+ }
111
+ rootDir = dirname(rootDir);
112
+ }
113
+ } catch (e) {
114
+ // 忽略错误
115
+ }
116
+ }
60
117
 
118
+ // 执行占位符替换
119
+ replaceCLIVersionPlaceholder();
61
120
 
62
121
  // 显示 Banner
63
122
  if (config.banner === '__default__' || config.banner !== '') {
64
123
  console.log(generateBanner(config.themeColor));
65
124
  }
66
125
 
67
- // 检查新版本(后台异步执行,不阻塞启动)
68
- function checkForUpdates() {
69
- setTimeout(async () => {
70
- try {
71
- // 读取本地版本
72
- const pkgPath = new URL('../package.json', import.meta.url).pathname;
73
- const localVersion = JSON.parse(readFileSync(pkgPath)).version;
74
-
75
- // 获取当前 registry
76
- const registry = execSync('npm config get registry', { encoding: 'utf8', stdio: 'pipe' }).trim();
77
- const latestVersion = execSync(`npm view @nucc/code-agent-cli version --registry=${registry}`, { encoding: 'utf8', stdio: 'pipe' }).trim();
78
-
79
- // 比较版本号
80
- const localParts = localVersion.split('.').map(Number);
81
- const latestParts = latestVersion.split('.').map(Number);
82
- const needsUpdate = latestParts[0] > localParts[0] ||
83
- (latestParts[0] === localParts[0] && latestParts[1] > localParts[1]) ||
84
- (latestParts[0] === localParts[0] && latestParts[1] === localParts[1] && latestParts[2] > localParts[2]);
85
-
86
- if (needsUpdate) {
87
- console.log('\n\x1b[33m⚠️ 发现新版本 v%s → v%s\x1b[0m', localVersion, latestVersion);
88
- console.log('\x1b[36m 运行以下命令更新:\x1b[0m');
89
- console.log(' npm install -g @nucc/code-agent-cli\n');
126
+ // 检查新版本
127
+ async function checkForUpdates() {
128
+ try {
129
+ const { execSync } = await import('child_process');
130
+ // 读取本地版本 - 向上查找 package.json
131
+ let rootDir = __dirname;
132
+ let localVersion = null;
133
+ while (rootDir !== dirname(rootDir)) {
134
+ const pkgPath = join(rootDir, 'package.json');
135
+ if (existsSync(pkgPath)) {
136
+ localVersion = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
137
+ break;
90
138
  }
91
- } catch (e) {
92
- // 忽略检查失败
139
+ rootDir = dirname(rootDir);
140
+ }
141
+ if (!localVersion) return;
142
+
143
+ // 获取当前 registry
144
+ const registry = execSync('npm config get registry', { encoding: 'utf8', stdio: 'pipe' }).trim();
145
+ const latestVersion = execSync(`npm view @nucc/code-agent-cli version --registry=${registry}`, { encoding: 'utf8', stdio: 'pipe' }).trim();
146
+
147
+ // 比较版本号
148
+ const localParts = localVersion.split('.').map(Number);
149
+ const latestParts = latestVersion.split('.').map(Number);
150
+ const needsUpdate = latestParts[0] > localParts[0] ||
151
+ (latestParts[0] === localParts[0] && latestParts[1] > localParts[1]) ||
152
+ (latestParts[0] === localParts[0] && latestParts[1] === localParts[1] && latestParts[2] > localParts[2]);
153
+
154
+ if (needsUpdate) {
155
+ console.log('\n⚠️ 发现新版本 v%s → v%s', localVersion, latestVersion);
156
+ console.log('\n 运行以下命令更新:');
157
+ console.log(' npm install -g @nucc/code-agent-cli\n');
93
158
  }
94
- }, 100); // 延迟 100ms 确保不影响主流程
95
- }
96
-
97
- // WebUI 模式
98
- if (webMode) {
99
- const webuiDir = join(__dirname, '..', 'webui', 'server');
100
-
101
- // 先构建 webui
102
- console.log('🔨 Building WebUI...');
103
- try {
104
- execSync('npm run build', { cwd: webuiDir, stdio: 'inherit' });
105
159
  } catch (e) {
106
- console.error('Failed to build WebUI:', e);
107
- process.exit(1);
160
+ // 忽略检查失败
108
161
  }
109
-
110
- // 启动 WebUI 服务器
111
- const portArgs = webPort ? ['--port', String(webPort)] : [];
112
- const webuiProcess = spawn('node', ['dist/index.js', ...portArgs], {
113
- cwd: webuiDir,
114
- stdio: 'inherit',
115
- env: {
116
- ...process.env,
117
- AICODE_CLI_CONFIG_DIR: config.configDir,
118
- }
119
- });
120
-
121
- // 自动打开浏览器
122
- const port = webPort || 3002;
123
- setTimeout(() => {
124
- try {
125
- execSync(`open http://localhost:${port}`, { stdio: 'ignore' });
126
- } catch (e) {
127
- // 忽略打开浏览器失败
128
- }
129
- }, 2000);
130
-
131
- webuiProcess.on('exit', (code) => {
132
- process.exit(code ?? 0);
133
- });
134
-
135
- webuiProcess.on('error', (err) => {
136
- console.error('Failed to start WebUI:', err);
137
- process.exit(1);
138
- });
139
-
140
- // WebUI 模式下不执行后续的 TUI 逻辑
141
- checkForUpdates();
142
- // 不阻塞,让 webui 进程继续运行
143
162
  }
144
-
145
163
  checkForUpdates();
146
164
 
147
165
  // 获取 node_modules 中 pi-coding-agent 的 CLI 路径
@@ -162,8 +180,6 @@ const fullPath = join(process.env.HOME || process.env.USERPROFILE, config.config
162
180
  const agentDir = join(fullPath, 'agent');
163
181
  env.AICODE_CLI_CODING_AGENT_DIR = agentDir;
164
182
 
165
-
166
-
167
183
  // 启动 pi-coding-agent
168
184
  const child = spawn('node', [piAgentPath.pathname, ...remainingArgs], {
169
185
  stdio: 'inherit',
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Context Monitor Extension — 自动检测上下文大小并在接近阈值时警告
3
+ *
4
+ * 功能:
5
+ * 1. 自动检测每轮对话后的上下文使用情况
6
+ * 2. 在超过阈值时自动发出不同级别的警告
7
+ * 3. 提供清理建议和上下文统计
8
+ *
9
+ * 阈值级别:
10
+ * - 70% : info 级别提示
11
+ * - 80% : warning 级别警告
12
+ * - 90% : error 级别严重警告
13
+ * - 100% : 阻止继续并强制提示压缩
14
+ */
15
+
16
+ import type { ExtensionAPI, ExtensionContext, ToolResultEvent } from "@mariozechner/pi-coding-agent";
17
+
18
+ interface ContextThresholds {
19
+ info: number; // 70%
20
+ warning: number; // 80%
21
+ error: number; // 90%
22
+ critical: number; // 100%
23
+ }
24
+
25
+ const DEFAULT_THRESHOLDS: ContextThresholds = {
26
+ info: 70,
27
+ warning: 80,
28
+ error: 90,
29
+ critical: 100,
30
+ };
31
+
32
+ interface ContextStats {
33
+ contextWindow: number;
34
+ usedTokens: number;
35
+ remainingTokens: number;
36
+ percent: number;
37
+ systemTokens: number;
38
+ toolsTokens: number;
39
+ conversationTokens: number;
40
+ }
41
+
42
+ function estimateTokens(text: string): number {
43
+ return Math.max(0, Math.ceil(text.length / 4));
44
+ }
45
+
46
+ function formatTokens(n: number): string {
47
+ if (n < 1000) return n.toString();
48
+ if (n < 1000000) return `${(n / 1000).toFixed(1)}k`;
49
+ return `${(n / 1000000).toFixed(1)}M`;
50
+ }
51
+
52
+ function getContextLevel(percent: number): "normal" | "info" | "warning" | "error" | "critical" {
53
+ if (percent >= DEFAULT_THRESHOLDS.critical) return "critical";
54
+ if (percent >= DEFAULT_THRESHOLDS.error) return "error";
55
+ if (percent >= DEFAULT_THRESHOLDS.warning) return "warning";
56
+ if (percent >= DEFAULT_THRESHOLDS.info) return "info";
57
+ return "normal";
58
+ }
59
+
60
+ function getContextMessage(stats: ContextStats): { level: "info" | "warning" | "error"; title: string; body: string } {
61
+ const level = getContextLevel(stats.percent);
62
+ const contextBar = renderContextBar(stats.percent);
63
+
64
+ let title: string;
65
+ let body: string;
66
+
67
+ switch (level) {
68
+ case "critical":
69
+ title = "🚨 上下文已满!";
70
+ body = `上下文窗口已完全使用! (${contextBar})\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n⚠️ 请立即使用 /clear 清理会话或开启新会话!`;
71
+ break;
72
+ case "error":
73
+ title = "⚠️ 上下文即将耗尽";
74
+ body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n💡 建议: 使用 /clear 清理会话`;
75
+ break;
76
+ case "warning":
77
+ title = "📊 上下文使用较高";
78
+ body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens\n\n💡 提示: 可以考虑压缩会话`;
79
+ break;
80
+ case "info":
81
+ title = "ℹ️ 上下文使用提醒";
82
+ body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n\n💡 提示: 上下文已使用过半,注意控制对话长度`;
83
+ break;
84
+ default:
85
+ title = "✅ 上下文状态正常";
86
+ body = `上下文使用: ${contextBar}\n已用: ${formatTokens(stats.usedTokens)} / ${formatTokens(stats.contextWindow)}\n剩余: ${formatTokens(stats.remainingTokens)} tokens`;
87
+ }
88
+
89
+ return { level, title, body };
90
+ }
91
+
92
+ function renderContextBar(percent: number, width: number = 20): string {
93
+ const filled = Math.min(width, Math.round((percent / 100) * width));
94
+ const empty = width - filled;
95
+
96
+ const filledChar = "█";
97
+ const emptyChar = "░";
98
+
99
+ return filledChar.repeat(filled) + emptyChar.repeat(empty) + ` ${percent.toFixed(1)}%`;
100
+ }
101
+
102
+ export default function contextMonitorExtension(pi: ExtensionAPI) {
103
+ // 配置:是否启用自动警告
104
+ let autoWarnEnabled = true;
105
+ let lastWarnedLevel: string = "normal";
106
+ let warnCooldown = 0; // 防止连续警告
107
+
108
+ /**
109
+ * 获取当前上下文统计
110
+ */
111
+ function getContextStats(ctx: ExtensionContext): ContextStats | null {
112
+ const usage = ctx.getContextUsage();
113
+ if (!usage) return null;
114
+
115
+ const contextWindow = usage.contextWindow || ctx.model?.contextWindow || 0;
116
+ if (contextWindow === 0) return null;
117
+
118
+ const usedTokens = usage.tokens || 0;
119
+ const remainingTokens = Math.max(0, contextWindow - usedTokens);
120
+ const percent = (usedTokens / contextWindow) * 100;
121
+
122
+ // 估算各部分 token
123
+ const systemPrompt = ctx.getSystemPrompt();
124
+ const systemTokens = systemPrompt ? estimateTokens(systemPrompt) : 0;
125
+ const toolsTokens = Math.round(
126
+ pi.getActiveTools().reduce((sum, name) => {
127
+ const info = pi.getAllTools().find((t) => t.name === name);
128
+ return sum + estimateTokens(`${name}\n${info?.description || ""}`);
129
+ }, 0) * 1.5
130
+ );
131
+ const conversationTokens = Math.max(0, usedTokens - systemTokens - toolsTokens);
132
+
133
+ return {
134
+ contextWindow,
135
+ usedTokens,
136
+ remainingTokens,
137
+ percent,
138
+ systemTokens,
139
+ toolsTokens,
140
+ conversationTokens,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * 处理上下文警告
146
+ */
147
+ async function handleContextWarning(stats: ContextStats, ctx: ExtensionContext) {
148
+ if (!autoWarnEnabled) return;
149
+
150
+ const level = getContextLevel(stats.percent);
151
+ const message = getContextMessage(stats);
152
+
153
+ // 避免重复警告(同级别)
154
+ if (level === lastWarnedLevel && warnCooldown > 0) {
155
+ warnCooldown--;
156
+ return;
157
+ }
158
+
159
+ // 只在有新级别时警告
160
+ const levels = ["normal", "info", "warning", "error", "critical"];
161
+ const currentIdx = levels.indexOf(level);
162
+ const lastIdx = levels.indexOf(lastWarnedLevel);
163
+
164
+ if (currentIdx <= lastIdx && level !== "critical") return;
165
+
166
+ lastWarnedLevel = level;
167
+ warnCooldown = 3; // 设置冷却
168
+
169
+ // 发送通知
170
+ ctx.ui.notify(message.title, message.level as any);
171
+ }
172
+
173
+ // 监听轮次结束事件,检查上下文使用
174
+ pi.on("turn_end", async (_event, ctx: ExtensionContext) => {
175
+ const stats = getContextStats(ctx);
176
+ if (!stats) return;
177
+
178
+ await handleContextWarning(stats, ctx);
179
+ });
180
+
181
+ // 监听会话开始,初始化
182
+ pi.on("session_start", async (_event, ctx: ExtensionContext) => {
183
+ lastWarnedLevel = "normal";
184
+ warnCooldown = 0;
185
+ });
186
+
187
+ /**
188
+ * /context-stats 命令 - 显示详细上下文统计
189
+ */
190
+ pi.registerCommand("context-stats", {
191
+ description: "显示当前上下文详细统计",
192
+ usage: "/context-stats",
193
+ handler: async (_args, ctx: ExtensionContext) => {
194
+ const stats = getContextStats(ctx);
195
+
196
+ if (!stats) {
197
+ ctx.ui.notify("无法获取上下文统计", "warning");
198
+ return;
199
+ }
200
+
201
+ const message = getContextMessage(stats);
202
+ const lines = message.body.split("\n");
203
+
204
+ // 构建详细统计文本
205
+ const modelName = ctx.model?.name || "unknown";
206
+ const modelMaxTokens = ctx.model?.maxTokens || 0;
207
+ const contextWindow = stats.contextWindow;
208
+
209
+ // 根据使用情况给出建议
210
+ let suggestion = "";
211
+ if (stats.percent >= 90) {
212
+ suggestion = `⚠️ 上下文使用过高,建议清理会话\n💡 模型 ${modelName} 最大窗口 ${formatTokens(contextWindow)}`;
213
+ } else if (stats.percent >= 70) {
214
+ suggestion = `💡 上下文已过半,注意控制对话长度`;
215
+ }
216
+
217
+ const detailLines = [
218
+ `${"─".repeat(40)}`,
219
+ `📊 上下文详细统计`,
220
+ `${"─".repeat(40)}`,
221
+ ``,
222
+ `模型: ${modelName}`,
223
+ `总窗口: ${formatTokens(contextWindow)} tokens`,
224
+ `最大输出: ${formatTokens(modelMaxTokens)} tokens`,
225
+ `已使用: ${formatTokens(stats.usedTokens)} tokens (${stats.percent.toFixed(1)}%)`,
226
+ `剩余: ${formatTokens(stats.remainingTokens)} tokens`,
227
+ ``,
228
+ `📝 分布估算:`,
229
+ ` 系统: ${formatTokens(stats.systemTokens)} tokens`,
230
+ ` 工具: ${formatTokens(stats.toolsTokens)} tokens`,
231
+ ` 对话: ${formatTokens(stats.conversationTokens)} tokens`,
232
+ ``,
233
+ suggestion,
234
+ ``,
235
+ `${"─".repeat(40)}`,
236
+ ];
237
+
238
+ // 显示给用户
239
+ const fullText = detailLines.join("\n");
240
+ pi.sendMessage(
241
+ {
242
+ customType: "context-stats",
243
+ content: fullText,
244
+ display: true,
245
+ },
246
+ { triggerTurn: false }
247
+ );
248
+
249
+ // 如果超过阈值也显示通知
250
+ if (stats.percent >= 80) {
251
+ ctx.ui.notify(message.title, message.level as any);
252
+ }
253
+ },
254
+ });
255
+
256
+ /**
257
+ * /context-warn 命令 - 切换自动警告
258
+ */
259
+ pi.registerCommand("context-warn", {
260
+ description: "开启/关闭上下文警告",
261
+ usage: "/context-warn [on|off]",
262
+ handler: async (args, ctx: ExtensionContext) => {
263
+ if (!args || args === "on") {
264
+ autoWarnEnabled = true;
265
+ ctx.ui.notify("✅ 上下文警告已开启", "info");
266
+ } else if (args === "off") {
267
+ autoWarnEnabled = false;
268
+ ctx.ui.notify("🔕 上下文警告已关闭", "info");
269
+ } else {
270
+ ctx.ui.notify(`当前状态: ${autoWarnEnabled ? "开启" : "关闭"}`, "info");
271
+ }
272
+ },
273
+ });
274
+ }
@@ -916,6 +916,9 @@ export default function reviewExtension(pi: ExtensionAPI) {
916
916
  fullPrompt += `\n\nThis project has additional instructions for code reviews:\n\n${projectGuidelines}`;
917
917
  }
918
918
 
919
+ // 要求 AI 使用中文输出
920
+ fullPrompt += `\n\n---\n\n**重要:请使用中文撰写审查反馈。**`;
921
+
919
922
  const modeHint = useFreshSession ? " (fresh session)" : "";
920
923
  ctx.ui.notify(`Starting review: ${hint}${modeHint}`, "info");
921
924
 
@@ -2,52 +2,25 @@
2
2
  "providers": {
3
3
  "openai": {
4
4
  "name": "OpenAI",
5
- "baseUrl": "https://api.openai.com/v1",
5
+ "baseUrl": "https://ai-llm.xxx.com/v1",
6
6
  "api": "openai-completions",
7
- "apiKey": "YOUR_API_KEY_HERE",
7
+ "apiKey": "你自己的APIKEY",
8
+ "headers": {
9
+ "User-Agent": "${VERSION}"
10
+ },
8
11
  "models": [
9
12
  {
10
- "id": "gpt-4o",
11
- "name": "GPT-4o",
12
- "contextWindow": 128000,
13
- "maxTokens": 16384,
14
- "reasoning": false,
15
- "input": ["text"],
16
- "cost": {
17
- "input": 2.5,
18
- "output": 10
19
- }
20
- },
21
- {
22
- "id": "gpt-4o-mini",
23
- "name": "GPT-4o Mini",
24
- "contextWindow": 128000,
25
- "maxTokens": 16384,
26
- "reasoning": false,
27
- "input": ["text"],
28
- "cost": {
29
- "input": 0.075,
30
- "output": 0.3
31
- }
32
- }
33
- ]
34
- },
35
- "anthropic": {
36
- "name": "Anthropic",
37
- "baseUrl": "https://api.anthropic.com",
38
- "api": "anthropic-messages",
39
- "apiKey": "YOUR_API_KEY_HERE",
40
- "models": [
41
- {
42
- "id": "claude-sonnet-4-20250514",
43
- "name": "Claude Sonnet 4",
44
- "contextWindow": 200000,
45
- "maxTokens": 8192,
13
+ "id": "GLM-4.7-W8A8",
14
+ "name": "GLM4.7",
15
+ "contextWindow": 99000,
16
+ "maxTokens": 99000,
46
17
  "reasoning": true,
47
18
  "input": ["text"],
48
19
  "cost": {
49
- "input": 3,
50
- "output": 15
20
+ "input": 5,
21
+ "output": 5,
22
+ "cacheRead": 0,
23
+ "cacheWrite": 0
51
24
  }
52
25
  }
53
26
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-ccun/code-agent-cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "AI Coding Agent CLI - 基于OpenClaw🦞底层Agent原理实现的的编码智能体(供学习使用)。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,16 +32,13 @@
32
32
  "postinstall": "node scripts/postinstall.js",
33
33
  "test": "echo \"Tests skipped\" && exit 0",
34
34
  "release": "npm version patch",
35
- "webui:build": "cd webui/server && npm run build",
36
- "webui:dev": "cd webui/server && npm run dev",
37
- "webui": "npm run webui:build && node webui/server/dist/index.js",
38
35
  "prepare": "husky"
39
36
  },
40
37
  "dependencies": {
41
- "@mariozechner/pi-agent-core": "0.60.0",
42
- "@mariozechner/pi-ai": "0.60.0",
43
- "@mariozechner/pi-coding-agent": "0.60.0",
44
- "@mariozechner/pi-tui": "0.60.0"
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"
45
42
  },
46
43
  "overrides": {
47
44
  "glob": "^11.0.0",
@@ -68,6 +68,21 @@ if (existsSync(piAgentConfigJs)) {
68
68
  }
69
69
  }
70
70
 
71
+ // 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');
73
+ if (existsSync(slashCommandsJs)) {
74
+ try {
75
+ let content = readFileSync(slashCommandsJs, 'utf-8');
76
+ if (content.includes('Quit pi')) {
77
+ content = content.replace(/Quit pi/g, 'Quit aicode-cli');
78
+ writeFileSync(slashCommandsJs, content);
79
+ console.log(`✓ Updated slash command description`);
80
+ }
81
+ } catch (e) {
82
+ console.log(`⚠ Could not update slash commands: ${e.message}`);
83
+ }
84
+ }
85
+
71
86
  // Ensure .aicode-cli directory exists
72
87
  if (!existsSync(aicodeCliDir)) {
73
88
  mkdirSync(aicodeCliDir, { recursive: true });
@@ -226,6 +241,44 @@ if (existsSync(packageConfigDir)) {
226
241
  console.log('⚠ Configuration directory not found in package');
227
242
  }
228
243
 
244
+ // ============================================================================
245
+ // Reset User-Agent placeholder in models.json (so CLI can replace it on each run)
246
+ // ============================================================================
247
+ const modelsPath = join(agentDir, 'models.json');
248
+ if (existsSync(modelsPath)) {
249
+ try {
250
+ let content = readFileSync(modelsPath, 'utf-8');
251
+ // 只重置 headers.User-Agent 下的版本号为占位符
252
+ try {
253
+ const config = JSON.parse(content);
254
+ for (const provider of Object.values(config.providers || {})) {
255
+ if (provider.headers && provider.headers['User-Agent']) {
256
+ const ua = provider.headers['User-Agent'];
257
+ // 如果是以 aicode-cli/ 开头,替换为占位符
258
+ if (ua && ua.startsWith('aicode-cli/')) {
259
+ provider.headers['User-Agent'] = '${VERSION}';
260
+ }
261
+ }
262
+ }
263
+ const newContent = JSON.stringify(config, null, 2);
264
+
265
+ if (newContent !== content) {
266
+ writeFileSync(modelsPath, newContent, 'utf-8');
267
+ console.log('✓ Reset User-Agent placeholder in models.json');
268
+ }
269
+ } catch (e) {
270
+ console.log(`⚠ Could not reset User-Agent placeholder: ${e.message}`);
271
+ }
272
+
273
+ if (newContent !== content) {
274
+ writeFileSync(modelsPath, newContent, 'utf-8');
275
+ console.log('✓ Reset User-Agent placeholder in models.json');
276
+ }
277
+ } catch (e) {
278
+ console.log(`⚠ Could not reset User-Agent placeholder: ${e.message}`);
279
+ }
280
+ }
281
+
229
282
  // Create symlink ~/.pi linking to ~/.aicode-cli (if not exists)
230
283
  try {
231
284
  if (!existsSync(piDir)) {
@@ -246,24 +299,19 @@ try {
246
299
  }
247
300
  }
248
301
 
249
- // Check if models.json exists
250
- const modelsPath = join(agentDir, 'models.json');
302
+ // Check and display models config
251
303
  if (existsSync(modelsPath)) {
252
304
  try {
253
- const models = JSON.parse(readFileSync(modelsPath, 'utf8'));
254
- const modelCount = Object.keys(models).length;
255
- if (modelCount > 0) {
256
- console.log(`✓ Found ${modelCount} configured model(s):`);
257
- console.log(` - ${Object.keys(models).join(', ')}`);
258
- } else {
259
- console.log(`✓ Config file exists but no models configured`);
305
+ const models = JSON.parse(readFileSync(modelsPath, 'utf-8'));
306
+ if (models.providers) {
307
+ const providerCount = Object.keys(models.providers).length;
308
+ console.log(`✓ Found ${providerCount} configured provider(s)`);
260
309
  }
261
310
  } catch (e) {
262
311
  console.log(`⚠ Error reading models.json: ${e.message}`);
263
312
  }
264
313
  } else {
265
314
  console.log(`⚠ No models.json found at ${modelsPath}`);
266
- console.log('\n📌 To configure models, create ~/.aicode-cli/agent/models.json');
267
315
  }
268
316
 
269
317
  console.log('\n✨ Setup complete!');