jinzd-ai-cli 0.4.19 → 0.4.21

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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm version](https://img.shields.io/npm/v/jinzd-ai-cli)](https://www.npmjs.com/package/jinzd-ai-cli)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
10
- [![Tests](https://img.shields.io/badge/tests-138%20passing-brightgreen)]()
10
+ [![Tests](https://img.shields.io/badge/tests-282%20passing-brightgreen)]()
11
11
  [![GitHub Release](https://img.shields.io/github/v/release/jinzhengdong/ai-cli)](https://github.com/jinzhengdong/ai-cli/releases)
12
12
  [![CI](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml)
13
13
 
@@ -34,7 +34,7 @@
34
34
  - **PWA Support** — Install Web UI as a desktop/mobile app, accessible over LAN
35
35
  - **Hierarchical Context** — 3-layer context files (global / project / subdirectory) auto-injected
36
36
  - **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
37
- - **37 REPL Commands** — Session management, checkpointing, code review, scaffolding, and more
37
+ - **38 REPL Commands** — Session management, checkpointing, code review, scaffolding, user identity, and more
38
38
  - **GitHub Actions CI/CD** — Automated testing on Node 20/22 + npm publish on release tags
39
39
  - **Cross-Platform** — Windows, macOS, Linux
40
40
 
@@ -75,7 +75,7 @@ Pre-built CLI binaries (no Node.js required, ~56 MB):
75
75
  aicli
76
76
  ```
77
77
 
78
- On first run, an interactive setup wizard guides you through selecting a provider and entering your API key.
78
+ On first run, an interactive setup wizard guides you through setting up your profile and entering your API key. Your identity is persisted and injected into every AI conversation.
79
79
 
80
80
  ```
81
81
  [deepseek] > Hello! Tell me about this project
@@ -167,12 +167,13 @@ AI autonomously invokes these 16 tools during conversations:
167
167
  | `/undo` | Undo last file operation |
168
168
  | `/doctor` | Health check (API keys, MCP, context) |
169
169
  | `/export` | Export session as Markdown or JSON |
170
+ | `/profile` | View/edit your identity (AI knows who you are across all providers) |
170
171
  | `/config` | Open configuration wizard |
171
172
  | `/help` | Show all available commands |
172
173
 
173
174
  **Multi-line input**: Use `\` at end of line for continuation, or paste multi-line content directly (auto-detected and merged).
174
175
 
175
- Type `/help` in the REPL to see all 37 commands.
176
+ Type `/help` in the REPL to see all 38 commands.
176
177
 
177
178
  ## CLI Parameters
178
179
 
@@ -348,11 +349,11 @@ The Web UI (`aicli web`) provides a full-featured browser interface:
348
349
  ## Testing
349
350
 
350
351
  ```bash
351
- npm test # Run all 138 tests
352
+ npm test # Run all 282 tests
352
353
  npm run test:watch # Watch mode
353
354
  ```
354
355
 
355
- 8 test suites covering: authentication, sessions, tool types & danger levels, permissions, output truncation, diff rendering, edit-file similarity, error hierarchy.
356
+ 18 test suites covering: authentication, sessions, tool types & danger levels, permissions, output truncation, diff rendering, edit-file similarity, error hierarchy, config management, env loading, provider registry, web-fetch, grep-files, hub renderer, hub discussion, hub presets, dev-state.
356
357
 
357
358
  ## Documentation
358
359
 
package/README.zh-CN.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm version](https://img.shields.io/npm/v/jinzd-ai-cli)](https://www.npmjs.com/package/jinzd-ai-cli)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
10
- [![Tests](https://img.shields.io/badge/tests-138%20passing-brightgreen)]()
10
+ [![Tests](https://img.shields.io/badge/tests-282%20passing-brightgreen)]()
11
11
  [![GitHub Release](https://img.shields.io/github/v/release/jinzhengdong/ai-cli)](https://github.com/jinzhengdong/ai-cli/releases)
12
12
  [![CI](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/jinzhengdong/ai-cli/actions/workflows/ci.yml)
13
13
 
@@ -26,7 +26,7 @@
26
26
  - **PWA 支持** — Web UI 可安装为桌面/移动应用,支持局域网访问
27
27
  - **三层级上下文** — 全局 / 项目 / 子目录上下文文件自动注入
28
28
  - **无头模式** — `aicli -p "提示词"` 用于 CI/CD 管道和脚本
29
- - **37 个 REPL 命令** — 会话管理、检查点、代码审查、脚手架等
29
+ - **38 个 REPL 命令** — 会话管理、检查点、代码审查、脚手架、用户身份等
30
30
  - **GitHub Actions CI/CD** — Node 20/22 自动测试 + Release tag 自动发布 npm
31
31
  - **跨平台** — Windows、macOS、Linux
32
32
 
@@ -67,7 +67,7 @@ npm install -g jinzd-ai-cli
67
67
  aicli
68
68
  ```
69
69
 
70
- 首次运行会进入交互式配置向导,选择 Provider 并输入 API Key。配置保存在 `~/.aicli/config.json`。
70
+ 首次运行会进入交互式配置向导,先设置你的身份档案,再选择 Provider 并输入 API Key。身份信息会注入每次 AI 对话。
71
71
 
72
72
  ```
73
73
  [deepseek] > 你好!帮我分析一下这个项目
@@ -159,12 +159,13 @@ AI 在对话中可自主调用 16 个工具:
159
159
  | `/undo` | 撤销上次文件操作 |
160
160
  | `/doctor` | 健康检查(API Key、MCP、上下文) |
161
161
  | `/export` | 导出会话为 Markdown 或 JSON |
162
+ | `/profile` | 查看/编辑身份档案(AI 跨 Provider 认识你) |
162
163
  | `/config` | 打开配置向导 |
163
164
  | `/help` | 显示所有命令 |
164
165
 
165
166
  **多行输入**:行末加 `\` 续行,或直接粘贴多行内容(自动检测合并)。
166
167
 
167
- 在 REPL 中输入 `/help` 查看全部 37 个命令。
168
+ 在 REPL 中输入 `/help` 查看全部 38 个命令。
168
169
 
169
170
  ## CLI 参数
170
171
 
@@ -361,11 +362,11 @@ Web UI(`aicli web`)提供功能完备的浏览器界面:
361
362
  ## 测试
362
363
 
363
364
  ```bash
364
- npm test # 运行全部 138 个测试
365
+ npm test # 运行全部 282 个测试
365
366
  npm run test:watch # 监听模式
366
367
  ```
367
368
 
368
- 8 个测试套件覆盖:认证、会话、工具类型与危险级别、权限、输出截断、diff 渲染、edit-file 相似度、错误层级。
369
+ 18 个测试套件覆盖:认证、会话、工具类型与危险级别、权限、输出截断、diff 渲染、edit-file 相似度、错误层级、配置管理、环境变量、Provider 注册、web-fetch、grep-files、Hub 渲染、Hub 讨论、Hub 预设、开发状态。
369
370
 
370
371
  ## 文档
371
372
 
@@ -6,7 +6,7 @@ import {
6
6
  SUBAGENT_DEFAULT_MAX_ROUNDS,
7
7
  SUBAGENT_MAX_ROUNDS_LIMIT,
8
8
  runTestsTool
9
- } from "./chunk-JNAZORS2.js";
9
+ } from "./chunk-AR656G5C.js";
10
10
 
11
11
  // src/tools/builtin/bash.ts
12
12
  import { execSync } from "child_process";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.19";
9
+ var VERSION = "0.4.21";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -85,6 +85,35 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
85
85
  - Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
86
86
  - Project context files (CLAUDE.md, AICLI.md) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
87
87
  - If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.`;
88
+ function buildUserIdentityPrompt(profile) {
89
+ const lines = [];
90
+ const displayName = profile.nickname || profile.name;
91
+ if (displayName) {
92
+ lines.push(`The user's name is **${profile.name || displayName}**${profile.nickname && profile.name ? ` (prefers to be called **${profile.nickname}**)` : ""}.`);
93
+ }
94
+ if (profile.role) {
95
+ lines.push(`Role: ${profile.role}.`);
96
+ }
97
+ if (profile.bio) {
98
+ lines.push(`About: ${profile.bio}`);
99
+ }
100
+ if (profile.interests && profile.interests.length > 0) {
101
+ lines.push(`Interests & expertise: ${profile.interests.join(", ")}.`);
102
+ }
103
+ if (profile.locale) {
104
+ lines.push(`Preferred language: ${profile.locale}. Please respond in this language unless the user explicitly uses another language.`);
105
+ }
106
+ if (profile.extra) {
107
+ lines.push(`
108
+ ${profile.extra}`);
109
+ }
110
+ if (lines.length === 0) return null;
111
+ return `# Who You're Talking To
112
+
113
+ ${lines.join("\n")}
114
+
115
+ Address the user personally and adapt your communication style to their background. This identity persists across all conversations and all AI providers.`;
116
+ }
88
117
  var AUTHOR = "Jin Zhengdong";
89
118
  var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
90
119
  var DESCRIPTION = "Cross-platform REPL-style AI conversation tool with multi-provider and agentic tool calling support";
@@ -468,6 +497,7 @@ export {
468
497
  SUBAGENT_MAX_ROUNDS_LIMIT,
469
498
  SUBAGENT_ALLOWED_TOOLS,
470
499
  AGENTIC_BEHAVIOR_GUIDELINE,
500
+ buildUserIdentityPrompt,
471
501
  AUTHOR,
472
502
  AUTHOR_EMAIL,
473
503
  DESCRIPTION,
@@ -7,7 +7,7 @@ import {
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
9
  schemaToJsonSchema
10
- } from "./chunk-PR5JZVNN.js";
10
+ } from "./chunk-2JOMWVTE.js";
11
11
  import {
12
12
  APP_NAME,
13
13
  CONFIG_DIR_NAME,
@@ -20,7 +20,7 @@ import {
20
20
  MCP_TOOL_PREFIX,
21
21
  PLUGINS_DIR_NAME,
22
22
  VERSION
23
- } from "./chunk-JNAZORS2.js";
23
+ } from "./chunk-AR656G5C.js";
24
24
 
25
25
  // src/config/config-manager.ts
26
26
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -58,8 +58,26 @@ var ModelParamsSchema = z.object({
58
58
  /** thinking 模式的 token 预算(最小 1024,仅 Claude Extended Thinking 使用) */
59
59
  thinkingBudget: z.number().int().min(1024).optional()
60
60
  });
61
+ var UserProfileSchema = z.object({
62
+ /** 真实姓名(如 "Jin Zhengdong") */
63
+ name: z.string().optional(),
64
+ /** 昵称/称呼偏好(如 "东叔"),AI 会以此称呼你 */
65
+ nickname: z.string().optional(),
66
+ /** 职业角色(如 "Full-stack developer"、"Data scientist") */
67
+ role: z.string().optional(),
68
+ /** 个人简介 / 专长描述(1-3 句话) */
69
+ bio: z.string().optional(),
70
+ /** 兴趣领域或技术栈(如 ["TypeScript", "Rust", "AI/ML"]) */
71
+ interests: z.array(z.string()).default([]),
72
+ /** 语言偏好(如 "zh-CN"、"en"),AI 将据此选择交流语言 */
73
+ locale: z.string().optional(),
74
+ /** 自定义人设补充(自由格式,直接注入 system prompt) */
75
+ extra: z.string().optional()
76
+ }).default({});
61
77
  var ConfigSchema = z.object({
62
78
  version: z.string().default("1.0.0"),
79
+ // 用户身份档案 — 跨 Provider 的 "灵魂"
80
+ userProfile: UserProfileSchema,
63
81
  defaultProvider: z.string().default("claude"),
64
82
  // 每个 provider 的默认模型(key 为 provider ID)
65
83
  defaultModels: z.record(z.string()).default({}),
@@ -3441,7 +3459,7 @@ function saveDevState(content) {
3441
3459
  if (trimmed.length > DEV_STATE_MAX_CHARS) {
3442
3460
  trimmed = trimmed.slice(0, DEV_STATE_MAX_CHARS);
3443
3461
  const lastNewline = trimmed.lastIndexOf("\n");
3444
- if (lastNewline > DEV_STATE_MAX_CHARS * 0.8) {
3462
+ if (lastNewline > 0) {
3445
3463
  trimmed = trimmed.slice(0, lastNewline);
3446
3464
  }
3447
3465
  trimmed += "\n\n[...truncated]";
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.4.19";
11
+ var VERSION = "0.4.21";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -88,6 +88,35 @@ var AGENTIC_BEHAVIOR_GUIDELINE = `# Important Behavioral Guidelines
88
88
  - Only begin using write/execute tools when the user **explicitly requests** an action (e.g., "generate", "create", "modify", "run", "start", etc.).
89
89
  - Project context files (CLAUDE.md, AICLI.md) provide background information about the project. They are NOT instructions to start working. Only use them as reference when the user asks a project-related question or task.
90
90
  - If you are unsure about the user's intent, use the ask_user tool to confirm with the user, rather than assuming and executing on your own.`;
91
+ function buildUserIdentityPrompt(profile) {
92
+ const lines = [];
93
+ const displayName = profile.nickname || profile.name;
94
+ if (displayName) {
95
+ lines.push(`The user's name is **${profile.name || displayName}**${profile.nickname && profile.name ? ` (prefers to be called **${profile.nickname}**)` : ""}.`);
96
+ }
97
+ if (profile.role) {
98
+ lines.push(`Role: ${profile.role}.`);
99
+ }
100
+ if (profile.bio) {
101
+ lines.push(`About: ${profile.bio}`);
102
+ }
103
+ if (profile.interests && profile.interests.length > 0) {
104
+ lines.push(`Interests & expertise: ${profile.interests.join(", ")}.`);
105
+ }
106
+ if (profile.locale) {
107
+ lines.push(`Preferred language: ${profile.locale}. Please respond in this language unless the user explicitly uses another language.`);
108
+ }
109
+ if (profile.extra) {
110
+ lines.push(`
111
+ ${profile.extra}`);
112
+ }
113
+ if (lines.length === 0) return null;
114
+ return `# Who You're Talking To
115
+
116
+ ${lines.join("\n")}
117
+
118
+ Address the user personally and adapt your communication style to their background. This identity persists across all conversations and all AI providers.`;
119
+ }
91
120
  var AUTHOR = "Jin Zhengdong";
92
121
  var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
93
122
  var DESCRIPTION = "Cross-platform REPL-style AI conversation tool with multi-provider and agentic tool calling support";
@@ -473,6 +502,7 @@ export {
473
502
  SUBAGENT_ALLOWED_TOOLS,
474
503
  CONTEXT_PRESSURE_THRESHOLD,
475
504
  AGENTIC_BEHAVIOR_GUIDELINE,
505
+ buildUserIdentityPrompt,
476
506
  AUTHOR,
477
507
  AUTHOR_EMAIL,
478
508
  DESCRIPTION,
@@ -387,7 +387,7 @@ ${content}`);
387
387
  }
388
388
  }
389
389
  async function runTaskMode(config, providers, configManager, topic) {
390
- const { TaskOrchestrator } = await import("./task-orchestrator-JKY2T2W3.js");
390
+ const { TaskOrchestrator } = await import("./task-orchestrator-UZQRB7AO.js");
391
391
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
392
392
  let interrupted = false;
393
393
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  saveDevState,
24
24
  sessionHasMeaningfulContent,
25
25
  setupProxy
26
- } from "./chunk-DBEYB76P.js";
26
+ } from "./chunk-7T2W4LN3.js";
27
27
  import {
28
28
  ToolRegistry,
29
29
  askUserContext,
@@ -38,7 +38,7 @@ import {
38
38
  theme,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-PR5JZVNN.js";
41
+ } from "./chunk-2JOMWVTE.js";
42
42
  import {
43
43
  AGENTIC_BEHAVIOR_GUIDELINE,
44
44
  AUTHOR,
@@ -57,8 +57,9 @@ import {
57
57
  PLAN_MODE_SYSTEM_ADDON,
58
58
  REPO_URL,
59
59
  SKILLS_DIR_NAME,
60
- VERSION
61
- } from "./chunk-JNAZORS2.js";
60
+ VERSION,
61
+ buildUserIdentityPrompt
62
+ } from "./chunk-AR656G5C.js";
62
63
 
63
64
  // src/index.ts
64
65
  import { program } from "commander";
@@ -166,14 +167,16 @@ var Renderer = class {
166
167
  const configured = options?.wrapWidth ?? 0;
167
168
  this.wrapWidth = configured > 0 ? configured : 0;
168
169
  }
169
- printWelcome(provider, model, contextWindow) {
170
+ printWelcome(provider, model, contextWindow, userNickname) {
170
171
  const ctxStr = contextWindow ? theme.dim(` (ctx: ${fmtContextWindow(contextWindow)})`) : "";
172
+ const greeting = userNickname ? ` \u{1F44B} Hello, ${userNickname}!` : "";
171
173
  console.log();
172
174
  console.log(theme.heading(" \u{1F916} ai-cli") + theme.dim(` v${VERSION}`) + theme.dim(" \u2014 " + AUTHOR + " <" + AUTHOR_EMAIL + ">"));
175
+ if (greeting) console.log(theme.accent(greeting));
173
176
  console.log(theme.dim(" " + "\u2500".repeat(55)));
174
177
  console.log(theme.dim(` Provider : `) + theme.prompt(provider));
175
178
  console.log(theme.dim(` Model : `) + chalk.white(model) + ctxStr);
176
- console.log(theme.dim(" Commands : ") + theme.dim("/help \xB7 /about \xB7 Ctrl+C to exit"));
179
+ console.log(theme.dim(" Commands : ") + theme.dim("/help \xB7 /about \xB7 /profile \xB7 Ctrl+C to exit"));
177
180
  console.log();
178
181
  }
179
182
  printAbout(pluginCount = 0, mcpInfo) {
@@ -217,12 +220,12 @@ var Renderer = class {
217
220
  console.log(tool("spawn_agent", "Delegate to independent sub-agent (isolated dialog + auto tool-call loop)"));
218
221
  console.log(tool("run_tests", "Run project tests and return structured report (auto-detect Maven/npm/pytest etc.)"));
219
222
  console.log(HR);
220
- console.log(theme.dim(" REPL Commands (37):"));
223
+ console.log(theme.dim(" REPL Commands (38):"));
221
224
  console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
222
225
  console.log(theme.dim(" /system /context /status /search /undo /export /copy /paste"));
223
226
  console.log(theme.dim(" /cost /init /skill /tools /plugins /mcp /config /checkpoint"));
224
- console.log(theme.dim(" /review /commands /test /scaffold /add-dir /memory /doctor"));
225
- console.log(theme.dim(" /bug /think /diff /fork /yolo /exit"));
227
+ console.log(theme.dim(" /review /commands /test /scaffold /add-dir /memory /profile"));
228
+ console.log(theme.dim(" /doctor /bug /think /diff /fork /yolo /exit"));
226
229
  console.log(HR);
227
230
  console.log(theme.dim(" Key Features:"));
228
231
  console.log(feat("Agentic loop (up to 25 tool-call rounds, final answer streamed)"));
@@ -233,6 +236,7 @@ var Renderer = class {
233
236
  console.log(feat("File operation undo (/undo [list|<n>], supports write_file / edit_file / bash-created files/dirs)"));
234
237
  console.log(feat("Thinking mode collapse (<think> blocks auto-collapsed, GLM-5 etc.)"));
235
238
  console.log(feat("Token usage tracking (per-response + session cumulative, Gemini/Claude/DeepSeek etc.)"));
239
+ console.log(feat("User identity (/profile): persistent profile injected into every AI provider \u2014 AI knows who you are"));
236
240
  console.log(feat("MCP protocol support: connect external MCP server tools (config.json mcpServers)"));
237
241
  console.log(feat("Plugin system: ~/.aicli/plugins/*.js custom tools (requires allowPlugins:true, off by default)"));
238
242
  console.log(feat("Plan Mode: /plan enters read-only planning, AI uses safe tools only, /plan execute resumes"));
@@ -350,7 +354,7 @@ var Renderer = class {
350
354
  } catch (err) {
351
355
  if (err?.name === "AbortError") {
352
356
  interrupted = true;
353
- flushBuf();
357
+ if (!inThinking) flushBuf();
354
358
  } else {
355
359
  throw err;
356
360
  }
@@ -423,6 +427,9 @@ var Renderer = class {
423
427
  process.stdout.write(theme.dim("\n\u{1F4AD} Thinking...\n"));
424
428
  }
425
429
  }
430
+ if (this.wrapWidth > 0) {
431
+ displayed = wrapText(displayed, this.wrapWidth);
432
+ }
426
433
  const CHUNK_SIZE = 12;
427
434
  const DELAY_MS = 8;
428
435
  let pos = 0;
@@ -881,6 +888,7 @@ function createDefaultCommands() {
881
888
  " /test [command|filter] - Run project tests and show structured report",
882
889
  " /scaffold <description> - Generate project scaffolding with AI",
883
890
  " /add-dir [path|remove] - Add/remove a directory from AI context",
891
+ " /profile [show|set|clear] - View or edit your identity (AI knows who you are)",
884
892
  " /memory [show|add|clear] - View or edit persistent memory (memory.md)",
885
893
  " /doctor - Health check (API keys, config, MCP status)",
886
894
  " /bug [--copy] - Generate bug report template (--copy to clipboard)",
@@ -1038,11 +1046,18 @@ function createDefaultCommands() {
1038
1046
  return;
1039
1047
  }
1040
1048
  const sessions = ctx.sessions.listSessions();
1041
- const match = sessions.find((s) => s.id.startsWith(id));
1042
- if (!match) {
1049
+ const matches = sessions.filter((s) => s.id.startsWith(id));
1050
+ if (matches.length === 0) {
1043
1051
  ctx.renderer.renderError(`Session '${id}' not found.`);
1044
1052
  return;
1045
1053
  }
1054
+ if (matches.length > 1) {
1055
+ console.log(theme.warning(` \u26A0 Ambiguous prefix '${id}' matches ${matches.length} sessions \u2014 loading most recent:`));
1056
+ for (const m of matches.slice(0, 5)) {
1057
+ console.log(theme.dim(` ${m.id.slice(0, 12)} ${m.title ?? "(untitled)"}`));
1058
+ }
1059
+ }
1060
+ const match = matches[0];
1046
1061
  ctx.sessions.loadSession(match.id);
1047
1062
  ctx.setProvider(match.provider, match.model);
1048
1063
  ctx.resetSessionTokenUsage();
@@ -1155,7 +1170,8 @@ function createDefaultCommands() {
1155
1170
  const estStr = fmtCtx(estimated);
1156
1171
  const winStr = fmtCtx(ctxWindowSize);
1157
1172
  const pctColor = pct >= 80 ? theme.error : pct >= 60 ? theme.warning : theme.success;
1158
- console.log(` Context% : ~${estStr} / ${winStr} tokens (${pctColor(`${pct}%`)})`);
1173
+ const pctDisplay = pct > 100 ? `${pct}% \u26A0 over limit` : `${pct}%`;
1174
+ console.log(` Context% : ~${estStr} / ${winStr} tokens (${pctColor(pctDisplay)})`);
1159
1175
  }
1160
1176
  console.log();
1161
1177
  }
@@ -1821,7 +1837,8 @@ ${hint}` : "")
1821
1837
  }
1822
1838
  const ok = ctx.restoreCheckpoint(name);
1823
1839
  if (ok) {
1824
- console.log(theme.success(` \u2713 Restored to checkpoint "${name}" (${session.messages.length} messages)`));
1840
+ ctx.resetSessionTokenUsage();
1841
+ console.log(theme.success(` \u2713 Restored to checkpoint "${name}" (${session.messages.length} messages, token usage reset)`));
1825
1842
  } else {
1826
1843
  ctx.renderer.renderError(`Checkpoint "${name}" not found.`);
1827
1844
  }
@@ -1925,17 +1942,21 @@ ${hint}` : "")
1925
1942
  name: "test",
1926
1943
  description: "Run project tests and show structured report",
1927
1944
  usage: "/test [command|filter]",
1928
- async execute(args, _ctx) {
1929
- const { executeTests } = await import("./run-tests-HP4MMQAP.js");
1930
- const argStr = args.join(" ").trim();
1931
- let testArgs = {};
1932
- if (argStr) {
1933
- const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
1934
- testArgs = isCommand ? { command: argStr } : { filter: argStr };
1935
- }
1936
- const report = await executeTests(testArgs);
1937
- const firstLines = report.split("\n").slice(0, 3).join("\n");
1938
- console.log(theme.dim(firstLines));
1945
+ async execute(args, ctx) {
1946
+ try {
1947
+ const { executeTests } = await import("./run-tests-ZTVEN2O7.js");
1948
+ const argStr = args.join(" ").trim();
1949
+ let testArgs = {};
1950
+ if (argStr) {
1951
+ const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
1952
+ testArgs = isCommand ? { command: argStr } : { filter: argStr };
1953
+ }
1954
+ const report = await executeTests(testArgs);
1955
+ const firstLines = report.split("\n").slice(0, 3).join("\n");
1956
+ console.log(theme.dim(firstLines));
1957
+ } catch (err) {
1958
+ ctx.renderer.renderError(`Test execution failed: ${err instanceof Error ? err.message : String(err)}`);
1959
+ }
1939
1960
  }
1940
1961
  },
1941
1962
  {
@@ -2017,6 +2038,64 @@ ${hint}` : "")
2017
2038
  }
2018
2039
  }
2019
2040
  },
2041
+ // ── /profile ───────────────────────────────────────────────────────────────
2042
+ {
2043
+ name: "profile",
2044
+ description: 'View or edit your identity profile (the "soul" of AI)',
2045
+ usage: "/profile [show|edit|set <field> <value>|clear]",
2046
+ execute(args, ctx) {
2047
+ const sub = args[0] ?? "show";
2048
+ const profile = ctx.config.get("userProfile") ?? {};
2049
+ if (sub === "show" || sub === "view") {
2050
+ const hasProfile = profile.name || profile.nickname || profile.role;
2051
+ if (!hasProfile) {
2052
+ console.log(theme.dim("\n No profile configured yet. Use /profile edit or /config to set up.\n"));
2053
+ return;
2054
+ }
2055
+ console.log(theme.heading("\n\u{1F464} Your Profile\n") + theme.dim("\u2500".repeat(50)));
2056
+ if (profile.name) console.log(` ${theme.dim("Name:")} ${chalk2.white(profile.name)}`);
2057
+ if (profile.nickname) console.log(` ${theme.dim("Nickname:")} ${theme.accent(profile.nickname)}`);
2058
+ if (profile.role) console.log(` ${theme.dim("Role:")} ${chalk2.white(profile.role)}`);
2059
+ if (profile.bio) console.log(` ${theme.dim("Bio:")} ${chalk2.white(profile.bio)}`);
2060
+ if (profile.interests?.length) console.log(` ${theme.dim("Interests:")} ${chalk2.white(profile.interests.join(", "))}`);
2061
+ if (profile.locale) console.log(` ${theme.dim("Locale:")} ${chalk2.white(profile.locale)}`);
2062
+ if (profile.extra) console.log(` ${theme.dim("Extra:")} ${chalk2.white(profile.extra.slice(0, 80))}${profile.extra.length > 80 ? "\u2026" : ""}`);
2063
+ console.log(theme.dim("\u2500".repeat(50)));
2064
+ console.log(theme.dim(" This identity is injected into every AI conversation.\n"));
2065
+ } else if (sub === "edit") {
2066
+ ctx.renderer.printInfo("Use /config to open the interactive profile editor, or use:\n /profile set <field> <value>\n Fields: name, nickname, role, bio, interests, locale, extra");
2067
+ } else if (sub === "set") {
2068
+ const field = args[1];
2069
+ const value = args.slice(2).join(" ").trim();
2070
+ const validFields = ["name", "nickname", "role", "bio", "interests", "locale", "extra"];
2071
+ if (!field || !validFields.includes(field)) {
2072
+ ctx.renderer.printInfo(`Usage: /profile set <field> <value>
2073
+ Fields: ${validFields.join(", ")}`);
2074
+ return;
2075
+ }
2076
+ if (!value) {
2077
+ ctx.renderer.printInfo(`Usage: /profile set ${field} <value>`);
2078
+ return;
2079
+ }
2080
+ const updated = { ...profile };
2081
+ if (field === "interests") {
2082
+ updated.interests = value.split(",").map((s) => s.trim()).filter(Boolean);
2083
+ } else {
2084
+ updated[field] = value;
2085
+ }
2086
+ ctx.config.set("userProfile", updated);
2087
+ ctx.config.save();
2088
+ const displayVal = field === "interests" ? updated.interests : value;
2089
+ ctx.renderer.printSuccess(`Profile updated: ${field} = ${JSON.stringify(displayVal)}`);
2090
+ } else if (sub === "clear") {
2091
+ ctx.config.set("userProfile", {});
2092
+ ctx.config.save();
2093
+ ctx.renderer.printSuccess("Profile cleared. AI will no longer know who you are.");
2094
+ } else {
2095
+ ctx.renderer.printInfo("Usage: /profile [show|edit|set <field> <value>|clear]");
2096
+ }
2097
+ }
2098
+ },
2020
2099
  // ── /memory ────────────────────────────────────────────────────────────────
2021
2100
  {
2022
2101
  name: "memory",
@@ -2993,8 +3072,10 @@ var SetupWizard = class {
2993
3072
  }
2994
3073
  async runFirstRun() {
2995
3074
  console.log(theme.heading("\nWelcome to ai-cli!\n"));
2996
- console.log("Let's set up your first AI provider.\n");
3075
+ console.log("Let's get to know you first, then set up your AI provider.\n");
2997
3076
  try {
3077
+ await this.setupProfile();
3078
+ console.log();
2998
3079
  const providerId = await select({
2999
3080
  message: "Which AI provider do you want to set up first?",
3000
3081
  choices: PROVIDERS
@@ -3002,7 +3083,11 @@ var SetupWizard = class {
3002
3083
  await this.setupProvider(providerId);
3003
3084
  this.config.set("defaultProvider", providerId);
3004
3085
  this.config.save();
3005
- console.log(theme.success("\nSetup complete! Starting ai-cli...\n"));
3086
+ const nickname = this.config.get("userProfile")?.nickname;
3087
+ const greeting = nickname ? `Setup complete, ${nickname}!` : "Setup complete!";
3088
+ console.log(theme.success(`
3089
+ ${greeting} Starting ai-cli...
3090
+ `));
3006
3091
  return true;
3007
3092
  } catch {
3008
3093
  return false;
@@ -3015,9 +3100,12 @@ var SetupWizard = class {
3015
3100
  const currentProxy = this.config.get("proxy") ?? "";
3016
3101
  const proxyStatus = currentProxy ? theme.success(`[${currentProxy}]`) : theme.dim("[not configured]");
3017
3102
  const googleKeyStatus = this.config.getApiKey("google-search") ? theme.success("[configured]") : theme.dim("[not configured]");
3103
+ const profileName = this.config.get("userProfile")?.nickname || this.config.get("userProfile")?.name;
3104
+ const profileStatus = profileName ? theme.success(`[${profileName}]`) : theme.dim("[not configured]");
3018
3105
  const action = await select({
3019
3106
  message: "What would you like to configure?",
3020
3107
  choices: [
3108
+ { value: "profile", name: `Edit your profile (identity) ${profileStatus}` },
3021
3109
  { value: "apikey", name: "Manage API key for a provider" },
3022
3110
  { value: "default", name: "Change default provider" },
3023
3111
  { value: "proxy", name: `Configure proxy (HTTP/HTTPS) ${proxyStatus}` },
@@ -3025,7 +3113,9 @@ var SetupWizard = class {
3025
3113
  { value: "done", name: "Done" }
3026
3114
  ]
3027
3115
  });
3028
- if (action === "proxy") {
3116
+ if (action === "profile") {
3117
+ await this.setupProfile();
3118
+ } else if (action === "proxy") {
3029
3119
  await this.setupProxy();
3030
3120
  } else if (action === "google") {
3031
3121
  await this.setupGoogleSearch();
@@ -3198,6 +3288,50 @@ Managing ${displayName} API Key`);
3198
3288
  }
3199
3289
  console.log();
3200
3290
  }
3291
+ /** 收集用户身份档案 — aicli 的 "灵魂" 功能 */
3292
+ async setupProfile() {
3293
+ const existing = this.config.get("userProfile") ?? {};
3294
+ console.log(theme.heading("\n\u{1F464} Your Profile"));
3295
+ console.log(theme.dim(" This helps AI know who you are \u2014 across all providers and sessions.\n"));
3296
+ const name = await input({
3297
+ message: "Your name:",
3298
+ default: existing.name || void 0
3299
+ });
3300
+ const nickname = await input({
3301
+ message: "Preferred nickname (AI will call you this):",
3302
+ default: existing.nickname || void 0
3303
+ });
3304
+ const role = await input({
3305
+ message: 'Your role (e.g. "Full-stack developer", "Student"):',
3306
+ default: existing.role || void 0
3307
+ });
3308
+ const bio = await input({
3309
+ message: "Brief intro (1-2 sentences about your background):",
3310
+ default: existing.bio || void 0
3311
+ });
3312
+ const interestsStr = await input({
3313
+ message: "Interests / tech stack (comma-separated):",
3314
+ default: existing.interests?.join(", ") || void 0
3315
+ });
3316
+ const locale = await input({
3317
+ message: 'Preferred language (e.g. "zh-CN", "en", "ja"):',
3318
+ default: existing.locale || void 0
3319
+ });
3320
+ const profile = {};
3321
+ if (name) profile.name = name;
3322
+ if (nickname) profile.nickname = nickname;
3323
+ if (role) profile.role = role;
3324
+ if (bio) profile.bio = bio;
3325
+ if (interestsStr) profile.interests = interestsStr.split(",").map((s) => s.trim()).filter(Boolean);
3326
+ if (locale) profile.locale = locale;
3327
+ if (existing.extra) profile.extra = existing.extra;
3328
+ this.config.set("userProfile", profile);
3329
+ this.config.save();
3330
+ const displayName = nickname || name || "User";
3331
+ console.log(theme.success(`
3332
+ Profile saved! AI will know you as "${displayName}".
3333
+ `));
3334
+ }
3201
3335
  };
3202
3336
 
3203
3337
  // src/repl/custom-commands.ts
@@ -3857,7 +3991,15 @@ You have a maximum of ${MAX_TOOL_ROUNDS} tool call rounds per conversation turn.
3857
3991
  - On Windows, use PowerShell cmdlets (Invoke-RestMethod, Get-ChildItem) instead of Unix commands (curl, grep, find).
3858
3992
  - If starting a long-running server process via bash, use background execution \u2014 do not block the tool round.
3859
3993
  - Prioritize the most critical tasks first in case you run out of rounds.`;
3860
- const parts = [dateTimeInfo + "\n" + envInfo, AGENTIC_BEHAVIOR_GUIDELINE + budgetInfo];
3994
+ const parts = [dateTimeInfo + "\n" + envInfo];
3995
+ const userProfile = this.config.get("userProfile");
3996
+ if (userProfile) {
3997
+ const identityPrompt = buildUserIdentityPrompt(userProfile);
3998
+ if (identityPrompt) {
3999
+ parts.push(identityPrompt);
4000
+ }
4001
+ }
4002
+ parts.push(AGENTIC_BEHAVIOR_GUIDELINE + budgetInfo);
3861
4003
  const memory = this.loadMemoryContent();
3862
4004
  if (memory) {
3863
4005
  parts.push(`# Persistent Memory
@@ -4097,7 +4239,8 @@ Session '${this.resumeSessionId}' not found.
4097
4239
  const pluginCount = await this.toolRegistry.loadPlugins(this.config.getPluginsDir(), allowPlugins);
4098
4240
  const welcomeProvider = this.providers.get(this.currentProvider);
4099
4241
  const welcomeModelInfo = welcomeProvider?.info.models.find((m) => m.id === this.currentModel);
4100
- this.renderer.printWelcome(this.currentProvider, this.currentModel, welcomeModelInfo?.contextWindow);
4242
+ const profileNickname = this.config.get("userProfile")?.nickname || this.config.get("userProfile")?.name;
4243
+ this.renderer.printWelcome(this.currentProvider, this.currentModel, welcomeModelInfo?.contextWindow, profileNickname);
4101
4244
  if (welcomeModelInfo?.contextWindow) setContextWindow(welcomeModelInfo.contextWindow);
4102
4245
  if (this.resumeSessionId) {
4103
4246
  const session = this.sessions.current;
@@ -5548,7 +5691,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5548
5691
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5549
5692
  process.exit(1);
5550
5693
  }
5551
- const { startWebServer } = await import("./server-JFLTWF7Q.js");
5694
+ const { startWebServer } = await import("./server-IDPMZAUF.js");
5552
5695
  await startWebServer({ port, host: options.host });
5553
5696
  });
5554
5697
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5781,7 +5924,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5781
5924
  }),
5782
5925
  config.get("customProviders")
5783
5926
  );
5784
- const { startHub } = await import("./hub-RJRNBB5G.js");
5927
+ const { startHub } = await import("./hub-CNSFX47B.js");
5785
5928
  await startHub(
5786
5929
  {
5787
5930
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-6WLDJKHU.js";
4
+ } from "./chunk-6CJX3RST.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-JNAZORS2.js";
5
+ } from "./chunk-AR656G5C.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -18,7 +18,7 @@ import {
18
18
  renderDiff,
19
19
  runHook,
20
20
  setupProxy
21
- } from "./chunk-DBEYB76P.js";
21
+ } from "./chunk-7T2W4LN3.js";
22
22
  import {
23
23
  AuthManager
24
24
  } from "./chunk-BYNY5JPB.js";
@@ -32,7 +32,7 @@ import {
32
32
  spawnAgentContext,
33
33
  truncateOutput,
34
34
  undoStack
35
- } from "./chunk-PR5JZVNN.js";
35
+ } from "./chunk-2JOMWVTE.js";
36
36
  import {
37
37
  AGENTIC_BEHAVIOR_GUIDELINE,
38
38
  AUTHOR,
@@ -48,8 +48,9 @@ import {
48
48
  PLAN_MODE_SYSTEM_ADDON,
49
49
  PLUGINS_DIR_NAME,
50
50
  SKILLS_DIR_NAME,
51
- VERSION
52
- } from "./chunk-JNAZORS2.js";
51
+ VERSION,
52
+ buildUserIdentityPrompt
53
+ } from "./chunk-AR656G5C.js";
53
54
 
54
55
  // src/web/server.ts
55
56
  import express from "express";
@@ -396,7 +397,14 @@ function buildSystemPrompt(ctx) {
396
397
  const envInfo = `OS: ${osName}
397
398
  ${shellInfo}
398
399
  Working directory: ${process.cwd()}`;
399
- const parts = [dateTimeInfo + "\n" + envInfo, AGENTIC_BEHAVIOR_GUIDELINE];
400
+ const parts = [dateTimeInfo + "\n" + envInfo];
401
+ if (ctx.userProfile) {
402
+ const identityPrompt = buildUserIdentityPrompt(ctx.userProfile);
403
+ if (identityPrompt) {
404
+ parts.push(identityPrompt);
405
+ }
406
+ }
407
+ parts.push(AGENTIC_BEHAVIOR_GUIDELINE);
400
408
  if (ctx.configDir) {
401
409
  const memory = loadMemoryContent(ctx.configDir);
402
410
  if (memory) {
@@ -1490,7 +1498,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1490
1498
  case "test": {
1491
1499
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1492
1500
  try {
1493
- const { executeTests } = await import("./run-tests-HP4MMQAP.js");
1501
+ const { executeTests } = await import("./run-tests-ZTVEN2O7.js");
1494
1502
  const argStr = args.join(" ").trim();
1495
1503
  let testArgs = {};
1496
1504
  if (argStr) {
@@ -2041,7 +2049,8 @@ Add .md files to create commands.` });
2041
2049
  activeSystemPrompt: this.activeSystemPrompt,
2042
2050
  activeSkill,
2043
2051
  planMode: this.planMode,
2044
- configDir: this.config.getConfigDir()
2052
+ configDir: this.config.getConfigDir(),
2053
+ userProfile: this.config.get("userProfile")
2045
2054
  });
2046
2055
  if (this.addedDirs.size > 0) {
2047
2056
  const MAX_DIR_CONTEXT = 4e4;
@@ -4,10 +4,10 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-PR5JZVNN.js";
7
+ } from "./chunk-2JOMWVTE.js";
8
8
  import {
9
9
  SUBAGENT_ALLOWED_TOOLS
10
- } from "./chunk-JNAZORS2.js";
10
+ } from "./chunk-AR656G5C.js";
11
11
 
12
12
  // src/hub/task-orchestrator.ts
13
13
  import { createInterface } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",