@ia-ccun/code-agent-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +211 -0
  2. package/bin/cli.js +83 -0
  3. package/config/agent/APPEND_SYSTEM.md +48 -0
  4. package/config/agent/SYSTEM.md +33 -0
  5. package/config/agent/bin/fd +0 -0
  6. package/config/agent/extensions/context.ts +578 -0
  7. package/config/agent/extensions/custom-footer.ts +170 -0
  8. package/config/agent/extensions/custom.ts +289 -0
  9. package/config/agent/extensions/review.ts +1281 -0
  10. package/config/agent/extensions/working-msg.ts +96 -0
  11. package/config/agent/help.md +364 -0
  12. package/config/agent/models.json +56 -0
  13. package/config/agent/prompts/feat.md +106 -0
  14. package/config/agent/prompts/git-commit.md +159 -0
  15. package/config/agent/prompts/git-rollback.md +91 -0
  16. package/config/agent/prompts/git-worktree.md +277 -0
  17. package/config/agent/prompts/help.md +10 -0
  18. package/config/agent/prompts/init-project.md +53 -0
  19. package/config/agent/prompts/workflow.md +194 -0
  20. package/config/agent/settings.json +7 -0
  21. package/config/agent/skills/code-review/SKILL.md +50 -0
  22. package/config/agent/skills/commit/SKILL.md +51 -0
  23. package/config/agent/skills/csv-data-summarizer/SKILL.md +149 -0
  24. package/config/agent/skills/csv-data-summarizer/analyze.py +182 -0
  25. package/config/agent/skills/csv-data-summarizer/examples/showcase_financial_pl_data.csv +46 -0
  26. package/config/agent/skills/csv-data-summarizer/requirements.txt +4 -0
  27. package/config/agent/skills/csv-data-summarizer/resources/sample.csv +22 -0
  28. package/config/agent/skills/find-skills/SKILL.md +133 -0
  29. package/config/agent/skills/frontend-design/LICENSE.txt +177 -0
  30. package/config/agent/skills/frontend-design/SKILL.md +42 -0
  31. package/config/agent/skills/github/SKILL.md +47 -0
  32. package/config/agent/skills/hello/SKILL.md +23 -0
  33. package/config/agent/skills/librarian/SKILL.md +195 -0
  34. package/config/agent/skills/markdown-to-html/SKILL.md +62 -0
  35. package/config/agent/skills/pr/SKILL.md +56 -0
  36. package/config/agent/skills/refactor/SKILL.md +37 -0
  37. package/config/agent/skills/skill-creator/LICENSE.txt +202 -0
  38. package/config/agent/skills/skill-creator/SKILL.md +356 -0
  39. package/config/agent/skills/skill-creator/references/output-patterns.md +82 -0
  40. package/config/agent/skills/skill-creator/references/workflows.md +28 -0
  41. package/config/agent/skills/skill-creator/scripts/init_skill.py +303 -0
  42. package/config/agent/skills/skill-creator/scripts/package_skill.py +110 -0
  43. package/config/agent/skills/skill-creator/scripts/quick_validate.py +95 -0
  44. package/config/agent/skills/ui-ux-pro-max/SKILL.md +264 -0
  45. package/config/agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  46. package/config/agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  47. package/config/agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  48. package/config/agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  49. package/config/agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  50. package/config/agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  51. package/config/agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  52. package/config/agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  53. package/config/agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  54. package/config/agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  55. package/config/agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  56. package/config/agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  57. package/config/agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  58. package/config/agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  59. package/config/agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  60. package/config/agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
  61. package/config/agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  62. package/config/agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  63. package/config/agent/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  64. package/config/agent/skills/ui-ux-pro-max/scripts/analyze.py +434 -0
  65. package/config/agent/skills/ui-ux-pro-max/scripts/core.py +238 -0
  66. package/config/agent/skills/ui-ux-pro-max/scripts/search.py +61 -0
  67. package/config/agent/skills/unit-test/SKILL.md +115 -0
  68. package/config/agent/themes/catppuccin-mocha.json +99 -0
  69. package/config.json +6 -0
  70. package/dist/banner.d.ts +10 -0
  71. package/dist/banner.d.ts.map +1 -0
  72. package/dist/banner.js +32 -0
  73. package/dist/banner.js.map +1 -0
  74. package/dist/config-loader.d.ts +17 -0
  75. package/dist/config-loader.d.ts.map +1 -0
  76. package/dist/config-loader.js +60 -0
  77. package/dist/config-loader.js.map +1 -0
  78. package/dist/config.d.ts +23 -0
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.js +12 -0
  81. package/dist/config.js.map +1 -0
  82. package/dist/index.d.ts +11 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +14 -0
  85. package/dist/index.js.map +1 -0
  86. package/package.json +69 -0
  87. package/scripts/postinstall.js +197 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Custom Footer Extension — Single-line compact powerline style
3
+ *
4
+ * ~/path (branch) │ ↑in ↓out $cost │ 42%/200k │ ⚡ model • thinking
5
+ * [gold] [dim] [dim] [color ctx] [accent]
6
+ */
7
+
8
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
9
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
10
+ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
11
+
12
+ function fmtTokens(n: number): string {
13
+ if (n < 1000) return n.toString();
14
+ if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
15
+ if (n < 1000000) return `${Math.round(n / 1000)}k`;
16
+ return `${(n / 1000000).toFixed(1)}M`;
17
+ }
18
+
19
+ // Catppuccin Mocha Yellow — #f9e2af
20
+ const gold = (s: string) => `\x1b[38;2;166;227;161m${s}\x1b[0m`;
21
+
22
+ export default function (pi: ExtensionAPI) {
23
+ pi.on("session_start", async (_event, ctx) => {
24
+ ctx.ui.setFooter((tui, theme, footerData) => {
25
+ const unsub = footerData.onBranchChange(() => tui.requestRender());
26
+
27
+ return {
28
+ dispose: unsub,
29
+ invalidate() {},
30
+ render(width: number): string[] {
31
+ const sep = theme.fg("dim", " │ ");
32
+ const sepWidth = 3; // " │ "
33
+
34
+ // ── Segment 1: project name (branch) ─────────────────────
35
+ const pwd = process.cwd();
36
+ const projectName = pwd.split('/').pop() || "project";
37
+ const branch = footerData.getGitBranch();
38
+ const pathRaw = projectName + (branch ? ` (${branch})` : "");
39
+
40
+ // ── Segment 2: token stats ──────────────────────────────
41
+ let totalIn = 0,
42
+ totalOut = 0,
43
+ totalCost = 0;
44
+ for (const e of ctx.sessionManager.getBranch()) {
45
+ if (e.type === "message" && e.message.role === "assistant") {
46
+ const m = e.message as AssistantMessage;
47
+ totalIn += m.usage.input;
48
+ totalOut += m.usage.output;
49
+ totalCost += m.usage.cost.total;
50
+ }
51
+ }
52
+ const statsParts: string[] = [];
53
+ if (totalIn || totalOut) {
54
+ statsParts.push(`↑${fmtTokens(totalIn)}`);
55
+ statsParts.push(`↓${fmtTokens(totalOut)}`);
56
+ }
57
+ if (totalCost) statsParts.push(`$${totalCost.toFixed(2)}`);
58
+ const statsRaw = statsParts.join(" ");
59
+ const totalTokens = totalIn + totalOut;
60
+
61
+ // ── Segment 3: context % ────────────────────────────────
62
+ const usage = ctx.getContextUsage();
63
+ const pct = usage?.percent ?? 0;
64
+ const win = usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
65
+ const ctxRaw = `${pct.toFixed(0)}%/${fmtTokens(win)}`;
66
+
67
+ // ── Segment 4: model + thinking ─────────────────────────────
68
+ const modelName = ctx.model?.id || "no-model";
69
+ const thinking = pi.getThinkingLevel();
70
+ const thinkSuffix = thinking !== "off" ? ` • ${thinking}` : "";
71
+ const modelRaw = `⚡ ${modelName}${thinkSuffix}`;
72
+
73
+ // ── Assemble: figure out what fits ──────────────────────
74
+ // Always show: path | ctx | model. Stats only if room.
75
+ const fixedRight = ctxRaw + modelRaw;
76
+ const fixedRightWidth = visibleWidth(fixedRight);
77
+ const sepsNeeded = statsRaw ? 3 : 2; // number of separators
78
+ const sepsWidth = sepsNeeded * sepWidth;
79
+
80
+ // Available width for path after reserving right segments
81
+ const rightBlockWidth = (statsRaw ? visibleWidth(statsRaw) + sepWidth : 0)
82
+ + visibleWidth(ctxRaw) + sepWidth + visibleWidth(modelRaw);
83
+ const pathBudget = width - rightBlockWidth - sepWidth; // -sepWidth for first separator
84
+
85
+ let pathDisplay: string;
86
+ if (pathBudget >= 10) {
87
+ // Enough room for path
88
+ if (visibleWidth(pathRaw) <= pathBudget) {
89
+ pathDisplay = gold(pathRaw);
90
+ } else {
91
+ // Truncate from the left: …ail/path (branch)
92
+ const truncated = "…" + pathRaw.slice(-(pathBudget - 1));
93
+ pathDisplay = gold(truncated);
94
+ }
95
+ } else {
96
+ // Very narrow — skip path entirely
97
+ pathDisplay = "";
98
+ }
99
+
100
+ // Context coloring
101
+ let ctxColored: string;
102
+ if (pct > 70) ctxColored = theme.fg("error", ctxRaw);
103
+ else if (pct > 60) ctxColored = theme.fg("warning", ctxRaw);
104
+ else ctxColored = theme.fg("success", ctxRaw);
105
+
106
+ // Thinking level coloring — semaphore, hardcoded to decouple from border
107
+ const thinkingColors: Record<string, string> = {
108
+ off: "\x1b[38;2;51;45;62m", // surface1 — invisible
109
+ minimal: "\x1b[38;2;67;61;78m", // surface2 — barely there
110
+ low: "\x1b[38;2;166;227;161m", // green
111
+ medium: "\x1b[38;2;137;180;250m", // blue
112
+ high: "\x1b[38;2;250;179;135m", // peach
113
+ xhigh: "\x1b[38;2;243;139;168m", // red
114
+ };
115
+ const thinkAnsi = thinkingColors[thinking] ?? thinkingColors.off;
116
+ let modelColored = theme.fg("accent", `⚡ ${modelName}`);
117
+ if (thinking !== "off") {
118
+ modelColored += theme.fg("dim", " • ") + `${thinkAnsi}${thinking}\x1b[0m`;
119
+ }
120
+
121
+ // Blue (#89B4FA) for token stats
122
+ const blue = (s: string) => `\x1b[38;2;137;180;250m${s}\x1b[0m`;
123
+ const statsColored = statsRaw ? blue(statsRaw) : "";
124
+
125
+ // quots
126
+ const quotes = [
127
+ "代码是有灵魂的",
128
+ "敬畏生产,双人复核,稳妥变更",
129
+ "让代码更有温度",
130
+ "民生所系,支付所向",
131
+ "智慧风控,安全同行",
132
+ "绿色支付,低碳生活",
133
+ "保持热爱,奔赴山海",
134
+ "每一行代码都是故事",
135
+ "技术只是手段,创意才是灵魂",
136
+ "安全为基,稳健致远",
137
+ "科技赋能,智慧支付",
138
+ "普惠金融,温暖万家",
139
+ "客户为本,体验至上",
140
+ "责任在肩,守护每笔",
141
+ "创新驱动,价值共生",
142
+ "诚信立本,信誉致远",
143
+ "高效清算,畅通无忧",
144
+ "支付协同,生态共赢",
145
+ "支付有温度,服务有深度",
146
+ "匠心运维,精益求精",
147
+ "数字纽带,连接美好",
148
+ "信任所系,使命必达",
149
+ "稳中求进,变中求效",
150
+ "跨境无界,体验无痕",
151
+ "数据守护,隐私无忧",
152
+ "同心筑梦,支付同行",
153
+ ];
154
+ const quote = quotes[Math.floor(totalTokens / 10000) % quotes.length];
155
+
156
+ // Build the line
157
+ const segments: string[] = [];
158
+ if (pathDisplay) segments.push(pathDisplay);
159
+ if (statsColored) segments.push(statsColored);
160
+ segments.push(ctxColored);
161
+ segments.push(modelColored);
162
+ segments.push(theme.fg("muted", quote));
163
+
164
+ const joined = segments.join(sep);
165
+ return [truncateToWidth(joined, width)];
166
+ },
167
+ };
168
+ });
169
+ });
170
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * 自定义扩展示例
3
+ *
4
+ * 放置位置: config/agent/extensions/
5
+ * 安装后会复制到 ~/.pi/agent/extensions/
6
+ *
7
+ * 可以拦截内置工具、注册自定义工具和命令
8
+ */
9
+
10
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
11
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
12
+ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
13
+
14
+ export default function (pi: ExtensionAPI) {
15
+ // === 记录开始时间 ===
16
+ let agentStartTime = 0;
17
+
18
+ pi.on("agent_start", async () => {
19
+ agentStartTime = Date.now();
20
+ });
21
+
22
+ // === 消息完成通知(只在最后一条显示)===
23
+
24
+ pi.on("agent_end", async (event, ctx) => {
25
+ // 计算耗时
26
+ const elapsed = Date.now() - agentStartTime;
27
+ let timeStr: string;
28
+ if (elapsed < 1000) {
29
+ timeStr = `${elapsed}ms`;
30
+ } else if (elapsed < 60000) {
31
+ timeStr = `${(elapsed / 1000).toFixed(1)}s`;
32
+ } else {
33
+ const mins = Math.floor(elapsed / 60000);
34
+ const secs = Math.floor((elapsed % 60000) / 1000);
35
+ timeStr = `${mins}m ${secs}s`;
36
+ }
37
+
38
+ // 格式化时间
39
+ const now = new Date();
40
+ const timeStr2 = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
41
+
42
+ // agent_end 表示一轮对话结束,显示完成提示
43
+ ctx.ui.notify(`${timeStr2} | ✨ 消息处理完成 (${timeStr})`, "info");
44
+ });
45
+
46
+ // === 拦截工具调用 ===
47
+
48
+ pi.on("tool_call", async (event, ctx) => {
49
+ // 拦截 login 工具 - 阻止执行
50
+ if (event.toolName === "login") {
51
+ await ctx.ui.notify("⚠️ login 功能已禁用", "warning");
52
+ return { block: true, reason: "login 功能已禁用" };
53
+ }
54
+
55
+ // 拦截 logout 工具 - 阻止执行
56
+ if (event.toolName === "logout") {
57
+ await ctx.ui.notify("⚠️ logout 功能已禁用", "warning");
58
+ return { block: true, reason: "logout 功能已禁用" };
59
+ }
60
+
61
+ // 拦截其他工具
62
+ if (event.toolName === "bash") {
63
+ const command = event.input.command;
64
+ if (command.includes("rm -rf")) {
65
+ // 解析要删除的路径
66
+ const match = command.match(/rm\s+-rf\s+('[^']+'|"[^"]+"|\S+)/g);
67
+ if (match) {
68
+ const paths = match.map(m => m.replace(/^rm\s+-rf\s+/, '').replace(/^['"]|['"]$/g, ''));
69
+
70
+ // 尝试列出文件
71
+ const { readdirSync, statSync, existsSync } = await import("fs");
72
+ let fileList: string[] = [];
73
+ for (const p of paths) {
74
+ try {
75
+ if (existsSync(p)) {
76
+ const stat = statSync(p);
77
+ if (stat.isDirectory()) {
78
+ const files = readdirSync(p);
79
+ fileList.push(`📁 ${p}/ (${files.length} 个文件)`);
80
+ } else {
81
+ fileList.push(`📄 ${p}`);
82
+ }
83
+ }
84
+ } catch (e) { /* 忽略读取错误 */ }
85
+ }
86
+
87
+ const msg = fileList.length > 0
88
+ ? `将删除以下文件/目录:\n${fileList.join('\n')}`
89
+ : '确定要执行此删除命令吗?';
90
+
91
+ // 第一次确认
92
+ const firstConfirm = await ctx.ui.confirm("🔴 危险操作 - rm -rf", msg);
93
+ if (!firstConfirm) {
94
+ return { block: true, reason: "被用户拒绝" };
95
+ }
96
+
97
+ // 二次确认
98
+ const secondConfirm = await ctx.ui.confirm("🔴 二次确认 - 不可恢复", "确定要继续删除吗?此操作不可恢复!");
99
+ if (!secondConfirm) {
100
+ return { block: true, reason: "被用户二次拒绝" };
101
+ }
102
+ }
103
+ }
104
+ }
105
+ });
106
+
107
+ // === 注册自定义命令 ===
108
+
109
+ pi.registerCommand("hello", {
110
+ description: "打招呼命令",
111
+ usage: "/hello [名字]",
112
+ handler: async (args, ctx) => {
113
+ const name = args || "大佬,您吃了吗";
114
+ ctx.ui.notify(`你好,${name}!🤝`, "info");
115
+ },
116
+ });
117
+
118
+ pi.registerCommand("status", {
119
+ description: "显示当前会话状态",
120
+ handler: async (_args, ctx) => {
121
+ const info = `
122
+ 📊 会话状态
123
+
124
+ - 当前模型: ${ctx.session.model?.name || "未知"}
125
+ - 项目目录: ${ctx.session.workspace || "未知"}
126
+ - 消息数量: ${ctx.session.messages?.length || 0}
127
+ `.trim();
128
+ ctx.ui.notify(info, "info");
129
+ },
130
+ });
131
+
132
+ pi.registerCommand("clear", {
133
+ description: "清理当前会话上下文",
134
+ usage: "/clear",
135
+ handler: async (_args, ctx) => {
136
+ // 获取当前会话条目数
137
+ const entries = ctx.sessionManager.getBranch();
138
+ const msgCount = entries.filter(e => e.type === "message").length;
139
+
140
+ if (msgCount === 0) {
141
+ ctx.ui.notify("会话已经是空的", "info");
142
+ return;
143
+ }
144
+
145
+ // 确认清理
146
+ const ok = await ctx.ui.confirm("清理会话", `确定要清理当前会话的所有 ${msgCount} 条消息吗?`);
147
+ if (!ok) {
148
+ ctx.ui.notify("已取消", "info");
149
+ return;
150
+ }
151
+
152
+ // 创建新会话来清空上下文
153
+ const result = await ctx.newSession({
154
+ setup: async (_sm) => {
155
+ // 新会话默认为空
156
+ },
157
+ });
158
+
159
+ if (result.cancelled) {
160
+ ctx.ui.notify("已取消", "info");
161
+ return;
162
+ }
163
+
164
+ ctx.ui.notify(`✅ 已清理 ${msgCount} 条消息,进入新会话`, "success");
165
+ },
166
+ });
167
+
168
+ // === 注册 /exit 命令 ===
169
+
170
+ pi.registerCommand("exit", {
171
+ description: "退出当前会话",
172
+ usage: "/exit",
173
+ handler: async (_args, ctx) => {
174
+ ctx.ui.notify("👋 再见!", "info");
175
+ ctx.shutdown();
176
+ },
177
+ });
178
+
179
+ // === 注册 /update 命令 ===
180
+
181
+ pi.registerCommand("update", {
182
+ description: "更新 CLI 工具",
183
+ usage: "/update",
184
+ handler: async (_args, ctx) => {
185
+ const options = [
186
+ "更新自己 (aicode-cli)",
187
+ "更新依赖 agent",
188
+ ];
189
+
190
+ // 让用户选择
191
+ const selected = await ctx.ui.select("选择更新内容", options);
192
+ if (!selected) {
193
+ ctx.ui.notify("已取消", "info");
194
+ return;
195
+ }
196
+
197
+ if (selected.includes("aicode-cli")) {
198
+ const ok = await ctx.ui.confirm("更新确认", "确定要更新 aicode-cli 吗?");
199
+ if (!ok) {
200
+ ctx.ui.notify("已取消", "info");
201
+ return;
202
+ }
203
+ ctx.ui.notify("🔄 正在更新 aicode-cli...", "info");
204
+ const { exec } = await import("child_process");
205
+ exec("npm install -g @ia-ccun/aicode-cli", (error, stdout, stderr) => {
206
+ if (error) {
207
+ ctx.ui.notify(`❌ 更新失败: ${error.message},请重试`, "error");
208
+ } else {
209
+ ctx.ui.notify("✅ aicode-cli 更新成功!", "success");
210
+ }
211
+ });
212
+ } else if (selected.includes("agent")) {
213
+ const ok = await ctx.ui.confirm("更新确认", "确定要更新依赖的agent 吗?");
214
+ if (!ok) {
215
+ ctx.ui.notify("已取消", "info");
216
+ return;
217
+ }
218
+ ctx.ui.notify("🔄 正在更新 ...", "info");
219
+ const { exec } = await import("child_process");
220
+ exec("npm install -g @mariozechner/pi-coding-agent", (error, stdout, stderr) => {
221
+ if (error) {
222
+ ctx.ui.notify(`❌ 更新失败: ${error.message},请重试`, "error");
223
+ } else {
224
+ ctx.ui.notify("✅ 依赖的 agent 更新成功!", "success");
225
+ }
226
+ });
227
+ }
228
+ },
229
+ });
230
+
231
+ // === 注册自定义工具 ===
232
+
233
+ // 注意: 注册自定义工具需要更复杂的参数定义
234
+ // 详情请参考: https://github.com/mariozechner/pi-coding-agent/docs/extensions.md
235
+
236
+ // === 注册 /version 命令 ===
237
+
238
+ pi.registerCommand("version", {
239
+ description: "查看当前版本和最新版本",
240
+ usage: "/version",
241
+ handler: async (_args, ctx) => {
242
+ const { execSync } = await import("child_process");
243
+ const pkgPath = "./package.json";
244
+ const { readFileSync } = await import("fs");
245
+
246
+ // 读取本地版本
247
+ let localVersion = "未知";
248
+ try {
249
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
250
+ localVersion = pkg.version;
251
+ } catch (e) {
252
+ // 忽略
253
+ }
254
+
255
+ // 获取最新版本
256
+ let latestVersion = "未知";
257
+ let updateMsg = "";
258
+ try {
259
+ const registry = execSync("npm config get registry", { encoding: "utf8" }).trim();
260
+ latestVersion = execSync(`npm view @ia-ccun/code-agent-cli version --registry=${registry}`, { encoding: "utf8" }).trim();
261
+
262
+ // 比较版本
263
+ const localParts = localVersion.split(".").map(Number);
264
+ const latestParts = latestVersion.split(".").map(Number);
265
+ const needsUpdate = latestParts[0] > localParts[0] ||
266
+ (latestParts[0] === localParts[0] && latestParts[1] > localParts[1]) ||
267
+ (latestParts[0] === localParts[0] && latestParts[1] === localParts[1] && latestParts[2] > localParts[2]);
268
+
269
+ if (needsUpdate) {
270
+ updateMsg = `\n\n⚠️ 发现新版本 v${localVersion} → v${latestVersion}\n 运行: npm install -g @ia-ccun/code-agent-cli`;
271
+ } else {
272
+ updateMsg = "\n\n✅ 已是最新版本";
273
+ }
274
+ } catch (e) {
275
+ updateMsg = "\n\n❌ 最新版本检查异常,请稍后重试";
276
+ }
277
+
278
+ const info = `
279
+ 📦 版本信息
280
+
281
+ 当前版本: v${localVersion}
282
+ 最新版本: v${latestVersion}
283
+ ${updateMsg}
284
+ `.trim();
285
+
286
+ ctx.ui.notify(info, "info");
287
+ },
288
+ });
289
+ }