jinzd-ai-cli 0.4.182 → 0.4.184

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/dist/index.js CHANGED
@@ -9,16 +9,13 @@ import {
9
9
  SkillManager,
10
10
  autoTrimSessionIfNeeded,
11
11
  clearDevState,
12
- computeCost,
13
- formatCost,
14
- getPricing,
15
12
  loadDevState,
16
13
  parseSimpleYaml,
17
14
  persistToolRound,
18
15
  saveDevState,
19
16
  sessionHasMeaningfulContent,
20
17
  setupProxy
21
- } from "./chunk-C6UJBTZO.js";
18
+ } from "./chunk-QLHGIWTT.js";
22
19
  import {
23
20
  ToolExecutor,
24
21
  ToolRegistry,
@@ -37,19 +34,27 @@ import {
37
34
  spawnAgentContext,
38
35
  theme,
39
36
  undoStack
40
- } from "./chunk-XI7EUUL7.js";
37
+ } from "./chunk-4JWMT7XQ.js";
41
38
  import "./chunk-HDSKW7Q3.js";
42
39
  import "./chunk-ZWVIDFGY.js";
43
- import "./chunk-QUYLXQRU.js";
40
+ import "./chunk-NVCB6SFZ.js";
44
41
  import {
45
42
  SessionManager,
46
43
  getContentText
47
44
  } from "./chunk-RIVZNS3K.js";
45
+ import {
46
+ CostTracker
47
+ } from "./chunk-7D67AR56.js";
48
+ import {
49
+ computeCost,
50
+ formatCost,
51
+ getPricing
52
+ } from "./chunk-BLRPRWZX.js";
48
53
  import {
49
54
  getConfigDirUsage,
50
55
  listRecentCrashes,
51
56
  writeCrashLog
52
- } from "./chunk-4NPR3MFZ.js";
57
+ } from "./chunk-WGJ7LE6T.js";
53
58
  import {
54
59
  BudgetWarner,
55
60
  CONTENT_ONLY_STREAM_REMINDER,
@@ -85,11 +90,11 @@ import {
85
90
  getTopFailingTools,
86
91
  getTopUsedTools,
87
92
  installFlushOnExit
88
- } from "./chunk-LWZ6P73G.js";
93
+ } from "./chunk-PS6S4IIW.js";
89
94
  import "./chunk-HIU2SH4V.js";
90
95
  import {
91
96
  ConfigManager
92
- } from "./chunk-YDIR3MXD.js";
97
+ } from "./chunk-WLMBMRIA.js";
93
98
  import {
94
99
  AuthError,
95
100
  ProviderError,
@@ -116,7 +121,7 @@ import {
116
121
  SKILLS_DIR_NAME,
117
122
  VERSION,
118
123
  buildUserIdentityPrompt
119
- } from "./chunk-ISO5KVEJ.js";
124
+ } from "./chunk-2DVGTYZG.js";
120
125
  import {
121
126
  formatGitContextForPrompt,
122
127
  getGitContext,
@@ -145,8 +150,8 @@ import { program } from "commander";
145
150
 
146
151
  // src/repl/repl.ts
147
152
  import * as readline from "readline";
148
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
149
- import { join as join5, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
153
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
154
+ import { join as join4, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
150
155
  import chalk4 from "chalk";
151
156
 
152
157
  // src/session/title-generator.ts
@@ -224,6 +229,68 @@ function fmtContextWindow(tokens) {
224
229
  if (tokens >= 1e3) return `${Math.round(tokens / 1024)}K`;
225
230
  return `${tokens}`;
226
231
  }
232
+ var ABOUT_TOOL_DESCRIPTIONS = {
233
+ bash: "Execute shell commands (PowerShell/bash, UTF-8 on Windows)",
234
+ read_file: "Read file contents",
235
+ write_file: "Write to file (danger level: write, requires confirmation)",
236
+ edit_file: "Edit file via exact string replacement / unified-diff patch",
237
+ list_dir: "List directory contents",
238
+ grep_files: "Search file contents with regex",
239
+ glob_files: "Match file paths by glob pattern",
240
+ run_interactive: "Run programs requiring stdin interaction (direct spawn)",
241
+ web_fetch: "Fetch web content (converted to Markdown, 2MB cap)",
242
+ web_search: "Keyless web search (Bing/Google scrape, structured results)",
243
+ google_search: "Google search (requires API Key + CX config)",
244
+ save_last_response: "Save AI response to file (tee streaming to disk)",
245
+ save_memory: "Persist info to ~/.aicli/memory.md, auto-injected across sessions",
246
+ ask_user: "Ask the user a question and wait for an answer mid-loop",
247
+ write_todos: "Break tasks into a subtask list with live progress display",
248
+ spawn_agent: "Delegate to an independent sub-agent (isolated tool-call loop)",
249
+ run_tests: "Run project tests, structured report (Maven/npm/pytest auto-detect)",
250
+ task_create: "Start a command running in the background",
251
+ task_list: "List background tasks and their status/output",
252
+ task_stop: "Stop a running background task",
253
+ git_status: "Show working tree status (branch, staged, modified, untracked)",
254
+ git_diff: "Show file diffs (staged/unstaged, stat summary)",
255
+ git_log: "Show commit history (oneline/full, filter by file/author)",
256
+ git_commit: "Create a git commit (stage files, message, stage_all)",
257
+ notebook_edit: "Edit Jupyter notebook cells (add/edit/delete/move)",
258
+ find_symbol: "Locate symbol definitions via tree-sitter index (8 languages)",
259
+ get_outline: "Enumerate all top-level declarations in one source file",
260
+ find_references: "Search indexed files for references to a symbol name",
261
+ search_code: "Semantic (meaning-based) code search via local embeddings",
262
+ recall_memory: 'Semantic recall over past chat sessions ("remember when\u2026")'
263
+ };
264
+ var ABOUT_FEATURES = [
265
+ "Agentic loop: up to 200 tool-call rounds (configurable), final answer streamed",
266
+ "Multimodal input: @filepath inlines images (base64) or text into messages",
267
+ "Git context awareness: branch + file-change status auto-injected on startup",
268
+ "Project context files: auto-load AICLI.md / CLAUDE.md (global/project/subdir)",
269
+ "Plan Mode: /plan read-only planning with safe tools, /plan execute resumes",
270
+ 'Headless mode: aicli -p "\u2026" non-interactive, stdin pipe + --json / NDJSON output',
271
+ "Context auto-management: token estimate, auto-compact above 80%, /compact + smart trim",
272
+ "Prompt caching: system prompt split stable/volatile \u2014 Claude caches the stable half",
273
+ "Provider retry + fallback chain: backoff on transient errors, walk config.fallback",
274
+ "Smart model routing: /route per-message model override by tag/keyword/length rules",
275
+ "Sub-agents: spawn_agent delegates complex subtasks to an isolated tool-call loop",
276
+ "Agent Skills: ~/.aicli/skills/ reusable packs (system prompt + tool whitelist)",
277
+ "MCP protocol: connect external MCP server tools; aicli mcp-serve reverses aicli into a server",
278
+ "Plugin system: ~/.aicli/plugins/*.js custom tools (allowPlugins, off by default)",
279
+ "Permission rules + hooks: auto-approve/deny/confirm + pre/post tool execution hooks",
280
+ "User identity (/profile): persistent profile injected into every provider",
281
+ "Symbol + semantic code index: find_symbol/get_outline/find_references/search_code (8 languages)",
282
+ "Chat memory recall: semantic index over past sessions + recall_memory tool",
283
+ "Session tooling: /checkpoint, /fork, /branch (diff/cherry-pick), /rewind, /session show|page|remove",
284
+ "Session Replay \u{1F3AC} + Web UI: timeline of every message, tool call, reasoning, token usage",
285
+ "Cross-session search (/search) + sensitive-data redaction before anything hits disk",
286
+ "Cost dashboard: /cost (session) + aicli usage (cross-session by provider/model) + monthly budget",
287
+ "File operation undo (/undo) for write_file / edit_file / bash-created files",
288
+ "Multi-Agent Hub: aicli hub \u2014 mixed-model debate, --steer, --vote, Web UI room",
289
+ "aicli ci: headless PR review for GitHub Actions (posts/updates one comment, CI gate)",
290
+ "Anthropic Batches API: aicli batch submit/list/status/results/cancel (50% off, 24h)",
291
+ "Theme customization + full semantic-color coverage (dark/light/custom)",
292
+ "Standalone executable packaging (no Node.js required), Electron desktop app"
293
+ ];
227
294
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
228
295
  function createSpinner(text) {
229
296
  if (!process.stdout.isTTY) {
@@ -324,9 +391,18 @@ var Renderer = class {
324
391
  console.log(theme.dim(" Commands : ") + theme.dim("/help \xB7 /about \xB7 /profile \xB7 Ctrl+C to exit"));
325
392
  console.log();
326
393
  }
327
- printAbout(pluginCount = 0, mcpInfo) {
394
+ /**
395
+ * /about — 项目门面。
396
+ *
397
+ * v0.4.183: 计数与清单一律从 live registry 派生(曾硬编码 "30 工具 / 45 命令"
398
+ * + 手敲命令名块,每次发版都漂)。功能墙砍掉逐版历史尾巴,只留常青能力描述,
399
+ * 末尾指向 CHANGELOG.md(与 v0.4.177 给 CLAUDE.md 瘦身同一思路——/about 是
400
+ * 展示台,CHANGELOG 才是版本史的权威记录)。
401
+ */
402
+ printAbout(opts) {
403
+ const { pluginCount = 0, mcpInfo, builtinTools, commandNames } = opts;
328
404
  const HR = theme.dim(" " + "\u2500".repeat(56));
329
- const label = (s) => theme.dim(` ${s.padEnd(6)}`);
405
+ const label = (s) => theme.dim(` ${s.padEnd(7)}`);
330
406
  const tool = (name, desc) => theme.accent(` ${name.padEnd(22)}`) + theme.dim(desc);
331
407
  const feat = (s) => theme.dim(" \u2726 ") + chalk.white(s);
332
408
  console.log();
@@ -342,149 +418,29 @@ var Renderer = class {
342
418
  console.log(theme.dim(" Gemini (Google) \xB7 Zhipu (GLM) \xB7 OpenRouter \xB7 Ollama (Local, no API key)"));
343
419
  console.log(HR);
344
420
  const mcpToolCount = mcpInfo?.tools ?? 0;
345
- const toolTotal = 30 + pluginCount + mcpToolCount;
421
+ const toolTotal = builtinTools.length + pluginCount + mcpToolCount;
346
422
  const extras = [];
347
423
  if (pluginCount > 0) extras.push(`${pluginCount} plugin(s)`);
348
424
  if (mcpToolCount > 0) extras.push(`${mcpToolCount} MCP`);
349
425
  const toolLabel = extras.length > 0 ? `, incl. ${extras.join(" + ")}` : ", extensible via plugins/MCP";
350
426
  console.log(theme.dim(` Agentic Tools (${toolTotal}${toolLabel}):`));
351
- console.log(tool("bash", "Execute shell commands (PowerShell/bash, UTF-8 on Windows)"));
352
- console.log(tool("read_file", "Read file contents"));
353
- console.log(tool("write_file", "Write to file (danger level: write, requires confirmation)"));
354
- console.log(tool("edit_file", "Edit file via exact string replacement (requires confirmation)"));
355
- console.log(tool("list_dir", "List directory contents"));
356
- console.log(tool("grep_files", "Search file contents with regex"));
357
- console.log(tool("glob_files", "Match file paths by glob pattern"));
358
- console.log(tool("run_interactive", "Run programs requiring stdin interaction (direct spawn)"));
359
- console.log(tool("web_fetch", "Fetch web content (converted to Markdown)"));
360
- console.log(tool("web_search", "Search web via Bing/Google scraping \u2014 no API key, structured {title,url,snippet} results (v0.4.135+)"));
361
- console.log(tool("google_search", "Google search (requires API Key + CX config)"));
362
- console.log(tool("save_last_response", "Save AI response to file (tee streaming to disk)"));
363
- console.log(tool("save_memory", "Persist important info to ~/.aicli/memory.md, auto-injected across sessions"));
364
- console.log(tool("ask_user", "Ask user a question and wait for answer (clarification in agentic loop)"));
365
- console.log(tool("write_todos", "Break tasks into subtask list with real-time progress display"));
366
- console.log(tool("spawn_agent", "Delegate to independent sub-agent (isolated dialog + auto tool-call loop)"));
367
- console.log(tool("run_tests", "Run project tests and return structured report (auto-detect Maven/npm/pytest etc.)"));
368
- console.log(tool("task_create", "Start a command running in the background"));
369
- console.log(tool("task_list", "List background tasks and their status/output"));
370
- console.log(tool("task_stop", "Stop a running background task"));
371
- console.log(tool("git_status", "Show working tree status (branch, staged, modified, untracked)"));
372
- console.log(tool("git_diff", "Show file diffs (staged/unstaged, stat summary)"));
373
- console.log(tool("git_log", "Show commit history (oneline/full, filter by file/author)"));
374
- console.log(tool("git_commit", "Create a git commit (stage files, message, stage_all)"));
375
- console.log(tool("notebook_edit", "Edit Jupyter notebook cells (add/edit/delete/move)"));
376
- console.log(tool("find_symbol", "Locate symbol definitions via persistent tree-sitter index (TS/JS/TSX/Python/Go/Rust/Java/C++)"));
377
- console.log(tool("get_outline", "Enumerate all top-level declarations in one source file"));
378
- console.log(tool("find_references", "Search indexed files for references to a symbol name"));
379
- console.log(tool("search_code", 'Semantic (meaning-based) code search via local embeddings \u2014 "grep by meaning", bilingual'));
380
- console.log(tool("recall_memory", 'Semantic recall over past chat sessions \u2014 "remember when we discussed X" across history (v0.4.89+)'));
427
+ for (const { name, description } of builtinTools) {
428
+ const curated = ABOUT_TOOL_DESCRIPTIONS[name];
429
+ const desc = curated ?? (description.length > 90 ? description.slice(0, 89) + "\u2026" : description);
430
+ console.log(tool(name, desc));
431
+ }
381
432
  console.log(HR);
382
- console.log(theme.dim(" REPL Commands (45):"));
383
- console.log(theme.dim(" /help /about /provider /model /route /clear /compact /plan"));
384
- console.log(theme.dim(" /session /system /context /status /search /undo /export /copy"));
385
- console.log(theme.dim(" /paste /cost /init /skill /tools /plugins /mcp /config"));
386
- console.log(theme.dim(" /checkpoint /review /security-review /security /rewind /commands"));
387
- console.log(theme.dim(" /test /scaffold /add-dir /memory /profile /doctor /bug /think"));
388
- console.log(theme.dim(" /diff /fork /branch /index /yolo /snapshot /exit"));
433
+ console.log(theme.dim(` REPL Commands (${commandNames.length}):`));
434
+ const withSlash = commandNames.map((n) => `/${n}`);
435
+ const perRow = 8;
436
+ for (let i = 0; i < withSlash.length; i += perRow) {
437
+ console.log(theme.dim(" " + withSlash.slice(i, i + perRow).join(" ")));
438
+ }
389
439
  console.log(HR);
390
440
  console.log(theme.dim(" Key Features:"));
391
- console.log(feat("Agentic loop (up to 200 tool-call rounds, configurable via config/CLI, final answer streamed)"));
392
- console.log(feat("Multimodal input: @filepath to inline images (base64) or text into messages"));
393
- console.log(feat("Git context awareness: auto-inject branch name and file change status on startup"));
394
- console.log(feat("Project context files: auto-load AICLI.md / CLAUDE.md (3 levels: global/project/subdir)"));
395
- console.log(feat("Cross-session full-text history search (/search <keyword>)"));
396
- console.log(feat("File operation undo (/undo [list|<n>], supports write_file / edit_file / bash-created files/dirs)"));
397
- console.log(feat("Thinking mode collapse (<think> blocks auto-collapsed, GLM-5 etc.)"));
398
- console.log(feat("Token usage tracking (per-response + session cumulative, Gemini/Claude/DeepSeek etc.)"));
399
- console.log(feat("User identity (/profile): persistent profile injected into every AI provider \u2014 AI knows who you are"));
400
- console.log(feat("MCP protocol support: connect external MCP server tools (config.json mcpServers)"));
401
- console.log(feat("Plugin system: ~/.aicli/plugins/*.js custom tools (requires allowPlugins:true, off by default)"));
402
- console.log(feat("Plan Mode: /plan enters read-only planning, AI uses safe tools only, /plan execute resumes"));
403
- console.log(feat('Headless mode: aicli -p "..." single-round non-interactive, supports stdin pipe and --json output'));
404
- console.log(feat("/compact context compression: generate summary replacing old messages, keep last 4, solve context overflow"));
405
- console.log(feat("Kimi tool-call reliability: XML pseudo-calls auto-detected and converted to real API calls (v0.1.19)"));
406
- console.log(feat("Sub-Agent: spawn_agent delegates to independent sub-agent with isolated loop for complex subtasks"));
407
- console.log(feat("Agent Skills: ~/.aicli/skills/ reusable skill packs, inject system prompt + tool whitelist"));
408
- console.log(feat("/init project initialization: scan project structure, AI generates AICLI.md context file"));
409
- console.log(feat("Multi-file edit preview: batch diff preview + selective approve/reject"));
410
- console.log(feat("Hooks system: pre/post tool execution hooks (shell commands, template variable substitution)"));
411
- console.log(feat("Permission Rules: rule-based tool permission control (auto-approve/deny/confirm)"));
412
- console.log(feat("Checkpointing: /checkpoint save/restore/list/delete session checkpoints"));
413
- console.log(feat("/review code review: read git diff, AI generates structured review comments"));
414
- console.log(feat("Custom Commands: ~/.aicli/commands/*.md user-defined REPL commands"));
415
- console.log(feat("Tab auto-completion: command names/subcommand args/@file paths, press Tab to trigger"));
416
- console.log(feat("Streaming token count: inline display of exact/estimated token count after stream ends"));
417
- console.log(feat("Context auto-management: estimate token usage, auto-compact above 80%, /status shows percentage"));
418
- console.log(feat("run_tests tool: auto-detect project type, run tests, JUnit XML parsing, structured report"));
419
- console.log(feat("/scaffold: describe project requirements, AI uses tools to auto-create full project skeleton"));
420
- console.log(feat("Standalone executable packaging (~56MB, no Node.js required)"));
421
- console.log(feat("Parallel tool calls: safe-level tools run via Promise.all(), improving batch efficiency"));
422
- console.log(feat("/add-dir dynamic directory context: inject dir tree + file contents into AI context at runtime (40K char limit)"));
423
- console.log(feat("/memory command: view/manually append/clear memory.md, supplement AI long-term memory"));
424
- console.log(feat("/doctor health check: diagnose API keys, config file, MCP connections and context usage"));
425
- console.log(feat("--allowed-tools / --blocked-tools: whitelist/blacklist AI available tools at startup"));
426
- console.log(feat("/bug feedback: generate bug report template with system info, --copy to clipboard"));
427
- console.log(feat("--output-format streaming-json: headless NDJSON per-chunk output for scripts/CI"));
428
- console.log(feat("Desktop notifications: system notification when AI task exceeds threshold (default 10s, macOS/Win/Linux)"));
429
- console.log(feat("Project-level .mcp.json: place MCP config in project root, auto-merge with global, /mcp shows source"));
430
- console.log(feat("--resume <id>: restore a specific session on CLI startup, supports prefix matching"));
431
- console.log(feat("Word wrap config: config.ui.wordWrap sets terminal output wrap width (0=auto)"));
432
- console.log(feat("Theme customization: config.ui.theme dark/light/custom + colors custom color slots"));
433
- console.log(feat("Extended Thinking: Claude deep reasoning mode, /think runtime toggle, thinking block collapse"));
434
- console.log(feat("Full theme coverage: all terminal output uses semantic color slots, dark/light/custom one-click switch"));
435
- console.log(feat('edit_file smart hints: show "did you mean" similar lines on mismatch + ignore_whitespace tolerance'));
436
- console.log(feat("/config set|get|show: quick config read/write in REPL, no wizard needed (dot-path + auto type conversion)"));
437
- console.log(feat("Streaming final answer in tool calls: typewriter-style chunked output, zero extra API calls, Escape to interrupt"));
438
- console.log(feat("/diff command: show aggregated diff of all file modifications in current session (merge multi-edit per file)"));
439
- console.log(feat("/fork conversation branch: fork from current position or checkpoint into new session, explore alternatives"));
440
- console.log(feat("Streaming Tool Use: real-time text streaming + instant tool name display in agentic loop (OpenAI/Claude)"));
441
- console.log(feat("User interjection: type a message + Enter during agentic loop to redirect AI mid-execution"));
442
- console.log(feat("Multi-Agent Hub: aicli hub \u2014 multiple AI roles discuss/brainstorm with round-robin turns"));
443
- console.log(feat("Hub distributed mode: aicli hub --distributed + aicli join \u2014 multi-process via WebSocket"));
444
- console.log(feat("Human participation: aicli join --human \u2014 real person joins multi-agent discussion"));
445
- console.log(feat("Context injection: aicli hub -c doc.md \u2014 inject external documents for all agents"));
446
- console.log(feat("Task Mode: aicli hub --task \u2014 agents plan, write code, and execute with tools (plan\u2192approve\u2192execute\u2192review)"));
447
- console.log(feat("Ollama local models: built-in provider, no API key, auto-discovers installed models via /v1/models"));
448
- console.log(feat("MCP tool budget: auto-trim MCP tool definitions when exceeding 20% context window, prioritize used tools"));
449
- console.log(feat("Smart compact: tool-history-aware compression preserves full tool call rounds (no mid-round splits)"));
450
- console.log(feat("Session size control: auto-trim old tool output when session exceeds 2MB, keep recent rounds intact"));
451
- console.log(feat("Crash recovery: detect incomplete agentic loops on /resume, warn and offer continuation"));
452
- console.log(feat("Cost dashboard: /cost history shows cross-session daily/weekly/monthly spend with budget progress bar"));
453
- console.log(feat("Prompt caching (A1, v0.4.70+): system prompt split into stable/volatile \u2014 Claude caches stable half, 10% cost on hits"));
454
- console.log(feat("edit_file patch mode (A2, v0.4.72+): accepts unified diff (@@ hunks) \u2014 most compact for scattered small changes in large files"));
455
- console.log(feat("Anthropic Batches API (A3, v0.4.73+): aicli batch submit/list/status/results/cancel \u2014 50% off, 24h window"));
456
- console.log(feat("Session Replay (B1, v0.4.71+): Web UI \u{1F3AC} button \u2014 timeline view of every message, tool call, reasoning, and cache-aware token usage"));
457
- console.log(feat("Conversation Branching (B2, v0.4.74+): /branch list/new/switch/delete/rename \u2014 fork the conversation at any message; Web UI replay \u{1F33F} fork-here button"));
458
- console.log(feat("Branch tree sidebar (B2 polish, v0.4.75+): Web UI \u{1F33F} Branches tab with tree-indented picker, click to switch, hover to rename/delete, + Fork button"));
459
- console.log(feat("Cross-branch ops (B3, v0.4.80+): /branch diff and /branch cherry-pick \u2014 compare branches and pick messages across forks; v0.4.81 accepts id/title/prefix"));
460
- console.log(feat("MCP Server mode (E1, v0.4.84+): aicli mcp-serve \u2014 reverse aicli into an MCP server so Claude Desktop/Cursor/any MCP client can use its 28 built-in tools (excluding ask_user/spawn_agent which need interactive stdin)"));
461
- console.log(feat("Session sensitive-data redaction (v0.4.88+): unified redactor scrubs passwords/tokens/keys from every message before it hits disk; /security status and scan to audit"));
462
- console.log(feat('Chat memory recall (B4, v0.4.89+): "human-like long-term memory" \u2014 semantic index over all past sessions + recall_memory AI tool + /memory rebuild|refresh|status|recall, AI auto-recalls on "last time"/"\u4E0A\u6B21"'));
463
- console.log(feat("Web UI Memory panel (B4, v0.4.90+): \u{1F9E0} Memory sidebar tab \u2014 cross-session semantic search with \u2795 Inject-to-input (quotes hit into chat box for user-reviewed recall) and \u2197 jump-to-session"));
464
- console.log(feat("Ctrl+C tool interrupt (v0.4.92+): pressing Ctrl+C during a long-running tool call (bash/run_tests/etc.) actually aborts the child process and returns control to the REPL"));
465
- console.log(feat("Skill size warning threshold (v0.4.93+): config.skills.maxSize (default 10000 chars) \u2014 warn when a loaded skill pack exceeds budget, configurable per project"));
466
- console.log(feat("DeepSeek V4 family (v0.4.94+): deepseek-pro / deepseek-flash with reasoning_content thinking mode \u2014 full roundtrip persisted across multi-turn (v0.4.95\u20130.4.97)"));
467
- console.log(feat("Smart model routing (/route, config.routing): per-message model override by tag/keyword/length rules \u2014 e.g. route short msgs to Haiku, code-heavy to Opus; /route test <msg> previews decision"));
468
- console.log(feat("Write-task auto-test suppression (v0.4.98+): AI no longer chains run_tests after write_file/edit_file unless explicitly asked \u2014 keeps single-file edits from ballooning into test runs"));
469
- console.log(feat("Tool history ordering (v0.4.100+): preserve original tool-call order across multi-turn rounds \u2014 fixes reasoning drift on long agentic loops"));
470
- console.log(feat("save_last_response Web mode (v0.4.101\u20130.4.102+): hidden from CLI-only contexts and tee-streams chunks to disk in Web UI as the response arrives"));
471
- console.log(feat("write_file long-content guidance (v0.4.103+): tool description no longer encourages AI to chunk long files \u2014 single-shot writes prevented from being split into truncated parts"));
472
- console.log(feat("Provider retry + fallback chain (v0.4.144+): transient network / 5xx / 429 errors retry on the same provider with exponential backoff; persistent failures walk config.fallback.chain (per-entry provider+model). Opt-in via config.fallback.enabled. Stream-safe: never retries after first chunk yielded"));
473
- console.log(feat("aicli ci \u2014 headless PR review for GitHub Actions (v0.4.145+): `aicli ci --pr <num> --post` reads diff via gh CLI, runs code + security review, posts/updates a single PR comment via sentinel marker. Drop-in workflow YAML at docs/github-actions-example.yml. Critical/high findings \u2192 exit 1 (CI gate)"));
474
- console.log(feat("Kimi K2.6 family + temperature fix (v0.4.146\u20130.4.149): kimi-k2.6 / k2.5 / k2-thinking default models; auto-migrate retired k2-0711/k2-turbo ids in config + history; K2.x models force temperature=1 (API requires it)"));
475
- console.log(feat("bash error hints (v0.4.146\u20130.4.165): on common Windows/PowerShell failures the bash tool appends actionable hints \u2014 Test-Path over -EA SilentlyContinue probes, python (not python3) incl. the WindowsApps stub that exits 1 silently, write_file over hallucinated Write-Content cmdlets"));
476
- console.log(feat("6th security + perf audit (v0.4.151): SSRF hardening (IPv4 int/hex/octal + IPv6 + DNS-rebinding pinned via undici lookup), newline command-chain bypass in permissions, timing-safe token compare, interpreter inline-code (-c/-e) danger elevation, vector-store top-k + redactor perf"));
477
- console.log(feat("C1/C2 index extended to Go/Rust/Java/C++ (v0.4.143): find_symbol/get_outline/find_references/search_code now cover 8 languages via bundled tree-sitter grammars"));
478
- console.log(feat("Provider streaming tool-call fixes (v0.4.142): Zhipu/GLM repeated id+name delta aggregation no longer drops args; conformance tests across OpenAI-standard vs repeated-chunk shapes"));
479
- console.log(feat("Hub P1 \u2014 mixed multi-model brainstorm (v0.4.152): aicli hub --mix spreads configured providers across roles (Claude vs GPT vs DeepSeek really debate); fixes per-role model bug via resolveRoleProviders"));
480
- console.log(feat("Hub P2 \u2014 human-in-the-loop + convergence (v0.4.154): --steer pauses each round for guidance/stop; --vote lets agents append [CONVERGED], 2/3 majority ends early (zero extra API calls); structured Decision/Action-Items summary"));
481
- console.log(feat("Hub P3 \u2014 discussion persistence (v0.4.155): finished discussions auto-save to ~/.aicli/history as a normal session, listed by `aicli sessions`, replayable via Web UI \u{1F3AC} \u2014 reuses Session Replay, zero new machinery"));
482
- console.log(feat("Hub P4 \u2014 Web UI multi-agent room (v0.4.156\u20130.4.158): \u{1F3DB} Hub sidebar tab runs discussions live in the browser (streaming per-role blocks, vote rows, summary card); P4b adds in-browser --steer and side-by-side agent lanes. Hub roadmap complete"));
483
- console.log(feat("aicli web fixes (v0.4.153): honor config.mcpEnabled in web mode; vendor Tailwind/DaisyUI/marked/highlight.js locally (GFW-blocked CDNs broke styling + login overlay)"));
484
- console.log(feat("Self-updating service worker (v0.4.159): network-only HTML/JS + activate-time reload broadcast \u2014 upgrades take effect without manual SW unregister; fixes stale-cache ghost"));
485
- console.log(feat("Web UI CSP fix (v0.4.160\u20130.4.162): restore missing \u{1F3DB} Hub tab button; loosen script-src to unsafe-inline so inline onclick handlers fire (strict CSP had silently blocked every button)"));
486
- console.log(feat("Windows process-tree kill (v0.4.163): killChild uses taskkill /T /F so Ctrl+C actually interrupts ssh/psql grandchildren (child.kill only hit the powershell parent)"));
487
- console.log(feat("Hallucination detector bash-aware (v0.4.165): files written via bash (python open().write(), Out-File, scp, redirects) no longer false-flagged as phantom claims; coarse check suppressed when bash ran this turn"));
441
+ for (const f of ABOUT_FEATURES) console.log(feat(f));
442
+ console.log();
443
+ console.log(theme.dim(" \u2726 ") + theme.accent("Full per-version history \u2192 CHANGELOG.md"));
488
444
  console.log();
489
445
  }
490
446
  printPrompt(provider, _model) {
@@ -1205,7 +1161,13 @@ No commands match "${filter}".
1205
1161
  execute(_args, ctx) {
1206
1162
  const manager = ctx.getMcpManager();
1207
1163
  const mcpInfo = manager ? { servers: manager.getConnectedCount(), tools: manager.getTotalToolCount() } : void 0;
1208
- ctx.renderer.printAbout(ctx.tools.listPluginTools().length, mcpInfo);
1164
+ const builtinTools = ctx.tools.listBuiltinTools().map((t) => ({ name: t.definition.name, description: t.definition.description }));
1165
+ ctx.renderer.printAbout({
1166
+ pluginCount: ctx.tools.listPluginTools().length,
1167
+ mcpInfo,
1168
+ builtinTools,
1169
+ commandNames: ctx.getCommandNames()
1170
+ });
1209
1171
  }
1210
1172
  },
1211
1173
  {
@@ -1871,16 +1833,16 @@ No tools match "${filter}".
1871
1833
  usage: "/mcp [reconnect [serverId] | trust-project]",
1872
1834
  async execute(args, ctx) {
1873
1835
  if (args[0] === "trust-project") {
1874
- const { join: join6 } = await import("path");
1875
- const { existsSync: existsSync6 } = await import("fs");
1836
+ const { join: join5 } = await import("path");
1837
+ const { existsSync: existsSync5 } = await import("fs");
1876
1838
  const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
1877
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-JI7VPTMJ.js");
1839
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-UGNL2FJR.js");
1878
1840
  const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
1879
1841
  const cwd = process.cwd();
1880
1842
  const projectRoot = getGitRoot2(cwd) ?? cwd;
1881
- const mcpPath = join6(projectRoot, MCP_PROJECT_CONFIG_NAME2);
1843
+ const mcpPath = join5(projectRoot, MCP_PROJECT_CONFIG_NAME2);
1882
1844
  console.log();
1883
- if (!existsSync6(mcpPath)) {
1845
+ if (!existsSync5(mcpPath)) {
1884
1846
  console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
1885
1847
  console.log();
1886
1848
  return;
@@ -2935,7 +2897,7 @@ ${hint}` : "")
2935
2897
  usage: "/test [command|filter]",
2936
2898
  async execute(args, ctx) {
2937
2899
  try {
2938
- const { executeTests } = await import("./run-tests-GFOHEIWM.js");
2900
+ const { executeTests } = await import("./run-tests-JHICSWKH.js");
2939
2901
  const argStr = args.join(" ").trim();
2940
2902
  let testArgs = {};
2941
2903
  if (argStr) {
@@ -3553,12 +3515,12 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3553
3515
  if (scanAll) {
3554
3516
  const metas = ctx.sessions.listSessions();
3555
3517
  console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
3556
- const { readFileSync: readFileSync5 } = await import("fs");
3557
- const { join: join6 } = await import("path");
3518
+ const { readFileSync: readFileSync4 } = await import("fs");
3519
+ const { join: join5 } = await import("path");
3558
3520
  const historyDir = ctx.config.getHistoryDir();
3559
3521
  for (const m of metas) {
3560
3522
  try {
3561
- const content = readFileSync5(join6(historyDir, `${m.id}.json`), "utf-8");
3523
+ const content = readFileSync4(join5(historyDir, `${m.id}.json`), "utf-8");
3562
3524
  const hits2 = scanString(content, opts);
3563
3525
  if (hits2.length) {
3564
3526
  filesWithHits++;
@@ -4321,142 +4283,6 @@ var CustomCommandManager = class {
4321
4283
  }
4322
4284
  };
4323
4285
 
4324
- // src/core/cost-tracker.ts
4325
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "fs";
4326
- import { join as join4 } from "path";
4327
- var CostTracker = class {
4328
- filePath;
4329
- records = [];
4330
- dirty = false;
4331
- constructor(configDir) {
4332
- this.filePath = join4(configDir, "cost-history.json");
4333
- this.load();
4334
- }
4335
- load() {
4336
- try {
4337
- if (existsSync4(this.filePath)) {
4338
- const data = JSON.parse(readFileSync3(this.filePath, "utf-8"));
4339
- if (data.version === 1 && Array.isArray(data.records)) {
4340
- this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
4341
- }
4342
- }
4343
- } catch {
4344
- this.records = [];
4345
- }
4346
- }
4347
- /**
4348
- * Save to disk (atomic write).
4349
- *
4350
- * H2: Snapshot records before writing and only clear the dirty flag after
4351
- * the write succeeds. If writeFileSync/renameSync throws, dirty remains
4352
- * true so the next save() retries — prevents silent data loss on transient
4353
- * disk errors. writeFileSync is sync in Node.js so there's no interleaving
4354
- * risk within a single save() call; the snapshot mainly protects against
4355
- * future refactors to async I/O.
4356
- */
4357
- save() {
4358
- if (!this.dirty) return;
4359
- const snapshot = {
4360
- version: 1,
4361
- records: [...this.records]
4362
- // shallow copy — records are plain data
4363
- };
4364
- try {
4365
- const tmp = this.filePath + ".tmp";
4366
- writeFileSync2(tmp, JSON.stringify(snapshot, null, 2), "utf-8");
4367
- renameSync(tmp, this.filePath);
4368
- this.dirty = false;
4369
- } catch (err) {
4370
- console.error("[cost-tracker] Failed to persist cost history:", err);
4371
- }
4372
- }
4373
- /**
4374
- * Record cost from a completed session/interaction.
4375
- */
4376
- addCost(provider, model, usage) {
4377
- const cost = computeCost(provider, model, usage);
4378
- if (cost === null || cost === 0) return;
4379
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4380
- let record = this.records.find((r) => r.date === today);
4381
- if (!record) {
4382
- record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
4383
- this.records.push(record);
4384
- }
4385
- record.cost += cost;
4386
- record.sessions += 1;
4387
- record.inputTokens += usage.inputTokens;
4388
- record.outputTokens += usage.outputTokens;
4389
- this.dirty = true;
4390
- if (this.records.length > 90) {
4391
- this.records = this.records.slice(-90);
4392
- }
4393
- }
4394
- /** Get total cost for a given month ("2026-04"). */
4395
- getMonthlyCost(yearMonth) {
4396
- const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
4397
- return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
4398
- }
4399
- /** Get today's cost. */
4400
- getTodayCost() {
4401
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4402
- return this.records.find((r) => r.date === today)?.cost ?? 0;
4403
- }
4404
- /** Get total cost for last N days. */
4405
- getRecentCost(days) {
4406
- const cutoff = /* @__PURE__ */ new Date();
4407
- cutoff.setDate(cutoff.getDate() - days);
4408
- const cutoffStr = cutoff.toISOString().slice(0, 10);
4409
- return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
4410
- }
4411
- /** Get all records for display. */
4412
- getRecords() {
4413
- return [...this.records];
4414
- }
4415
- /**
4416
- * Check if monthly cost exceeds budget, return warning message or null.
4417
- */
4418
- checkBudget(monthlyBudget) {
4419
- if (!monthlyBudget || monthlyBudget <= 0) return null;
4420
- const monthlyCost = this.getMonthlyCost();
4421
- const ratio = monthlyCost / monthlyBudget;
4422
- if (ratio >= 1) {
4423
- return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
4424
- }
4425
- if (ratio >= 0.8) {
4426
- return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
4427
- }
4428
- return null;
4429
- }
4430
- /**
4431
- * Format a cost summary for display.
4432
- */
4433
- formatSummary(monthlyBudget) {
4434
- const today = this.getTodayCost();
4435
- const monthly = this.getMonthlyCost();
4436
- const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
4437
- const lines = [
4438
- `Today: ${formatCost(today)}`,
4439
- `This month: ${formatCost(monthly)} (${yearMonth})`
4440
- ];
4441
- if (monthlyBudget && monthlyBudget > 0) {
4442
- const ratio = monthly / monthlyBudget;
4443
- const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
4444
- lines.push(`Budget: ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
4445
- }
4446
- const last7 = [];
4447
- for (let i = 6; i >= 0; i--) {
4448
- const d = /* @__PURE__ */ new Date();
4449
- d.setDate(d.getDate() - i);
4450
- const dateStr = d.toISOString().slice(0, 10);
4451
- const record = this.records.find((r) => r.date === dateStr);
4452
- const dayLabel = dateStr.slice(5);
4453
- last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
4454
- }
4455
- lines.push(`Last 7 days: ${last7.join(" ")}`);
4456
- return lines.join("\n");
4457
- }
4458
- };
4459
-
4460
4286
  // src/core/model-router.ts
4461
4287
  var TAG_REGEX = /(?:^|\s)#([a-zA-Z][\w-]{0,31})\b/g;
4462
4288
  function extractTags(message) {
@@ -4746,7 +4572,7 @@ function parseAtReferences(input2, cwd) {
4746
4572
  const absPath = resolve2(cwd, rawPath);
4747
4573
  const ext = extname2(rawPath).toLowerCase();
4748
4574
  const mime = IMAGE_MIME[ext];
4749
- if (!existsSync5(absPath)) {
4575
+ if (!existsSync4(absPath)) {
4750
4576
  refs.push({ path: rawPath, type: "notfound" });
4751
4577
  continue;
4752
4578
  }
@@ -4756,7 +4582,7 @@ function parseAtReferences(input2, cwd) {
4756
4582
  refs.push({ path: rawPath, type: "toolarge" });
4757
4583
  continue;
4758
4584
  }
4759
- const data = readFileSync4(absPath).toString("base64");
4585
+ const data = readFileSync3(absPath).toString("base64");
4760
4586
  imageParts.push({
4761
4587
  type: "image_url",
4762
4588
  image_url: { url: `data:${mime};base64,${data}` }
@@ -4764,7 +4590,7 @@ function parseAtReferences(input2, cwd) {
4764
4590
  refs.push({ path: rawPath, type: "image" });
4765
4591
  textBody = textBody.replace(match[0], "").trim();
4766
4592
  } else {
4767
- const content = readFileSync4(absPath, "utf-8");
4593
+ const content = readFileSync3(absPath, "utf-8");
4768
4594
  const inlined = `
4769
4595
 
4770
4596
  [File: ${rawPath}]
@@ -4989,7 +4815,7 @@ var Repl = class {
4989
4815
  const filtered = entries.filter((e) => !SKIP_DIRS_SET.has(e));
4990
4816
  for (let i = 0; i < filtered.length && entryCount < MAX_TREE_ENTRIES; i++) {
4991
4817
  const name = filtered[i];
4992
- const fullPath = join5(dir, name);
4818
+ const fullPath = join4(dir, name);
4993
4819
  const isLast = i === filtered.length - 1;
4994
4820
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4995
4821
  let isDir;
@@ -5023,7 +4849,7 @@ ${treeLines.join("\n")}`
5023
4849
  for (const name of entries) {
5024
4850
  if (totalChars >= MAX_TOTAL_CHARS) break;
5025
4851
  if (SKIP_DIRS_SET.has(name)) continue;
5026
- const fullPath = join5(dir, name);
4852
+ const fullPath = join4(dir, name);
5027
4853
  let st;
5028
4854
  try {
5029
4855
  st = statSync3(fullPath);
@@ -5038,7 +4864,7 @@ ${treeLines.join("\n")}`
5038
4864
  if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
5039
4865
  if (st.size > MAX_FILE_CHARS * 3) continue;
5040
4866
  try {
5041
- let content = readFileSync4(fullPath, "utf-8");
4867
+ let content = readFileSync3(fullPath, "utf-8");
5042
4868
  if (content.length > MAX_FILE_CHARS) {
5043
4869
  content = content.slice(0, MAX_FILE_CHARS) + `
5044
4870
  ... (truncated, ${content.length} chars total)`;
@@ -5068,7 +4894,7 @@ ${content}
5068
4894
  */
5069
4895
  addExtraContextDir(dirPath) {
5070
4896
  const absPath = resolve2(dirPath);
5071
- if (!existsSync5(absPath)) {
4897
+ if (!existsSync4(absPath)) {
5072
4898
  return { success: false, charCount: 0, added: false, error: `Directory not found: ${dirPath}` };
5073
4899
  }
5074
4900
  let isDir;
@@ -5102,9 +4928,9 @@ ${content}
5102
4928
  */
5103
4929
  findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
5104
4930
  for (const candidate of candidates) {
5105
- const fullPath = join5(dir, candidate);
5106
- if (existsSync5(fullPath)) {
5107
- const content = readFileSync4(fullPath, "utf-8").trim();
4931
+ const fullPath = join4(dir, candidate);
4932
+ if (existsSync4(fullPath)) {
4933
+ const content = readFileSync3(fullPath, "utf-8").trim();
5108
4934
  if (content) return { filePath: fullPath, content };
5109
4935
  }
5110
4936
  }
@@ -5132,10 +4958,10 @@ ${content}
5132
4958
  const cwd = process.cwd();
5133
4959
  const gitRoot = getGitRoot(cwd);
5134
4960
  const projectRoot = gitRoot ?? cwd;
5135
- const mcpPath = join5(projectRoot, MCP_PROJECT_CONFIG_NAME);
5136
- if (!existsSync5(mcpPath)) return null;
4961
+ const mcpPath = join4(projectRoot, MCP_PROJECT_CONFIG_NAME);
4962
+ if (!existsSync4(mcpPath)) return null;
5137
4963
  try {
5138
- const raw = JSON.parse(readFileSync4(mcpPath, "utf-8"));
4964
+ const raw = JSON.parse(readFileSync3(mcpPath, "utf-8"));
5139
4965
  const servers = raw?.mcpServers;
5140
4966
  if (!servers || typeof servers !== "object") {
5141
4967
  process.stderr.write(
@@ -5181,8 +5007,8 @@ ${content}
5181
5007
  );
5182
5008
  return { layers: [], mergedContent: "" };
5183
5009
  }
5184
- if (existsSync5(fullPath)) {
5185
- const content = readFileSync4(fullPath, "utf-8").trim();
5010
+ if (existsSync4(fullPath)) {
5011
+ const content = readFileSync3(fullPath, "utf-8").trim();
5186
5012
  if (content) {
5187
5013
  const layer = {
5188
5014
  level: "project",
@@ -5239,9 +5065,9 @@ ${content}
5239
5065
  * 超过 MEMORY_MAX_CHARS 时只取末尾最新部分。
5240
5066
  */
5241
5067
  loadMemoryContent() {
5242
- const memoryPath = join5(this.config.getConfigDir(), MEMORY_FILE_NAME);
5243
- if (!existsSync5(memoryPath)) return null;
5244
- let content = readFileSync4(memoryPath, "utf-8").trim();
5068
+ const memoryPath = join4(this.config.getConfigDir(), MEMORY_FILE_NAME);
5069
+ if (!existsSync4(memoryPath)) return null;
5070
+ let content = readFileSync3(memoryPath, "utf-8").trim();
5245
5071
  if (!content) return null;
5246
5072
  if (content.length > MEMORY_MAX_CHARS) {
5247
5073
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -5629,14 +5455,14 @@ Session '${this.resumeSessionId}' not found.
5629
5455
  process.stdout.write(theme.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
5630
5456
  `));
5631
5457
  }
5632
- const skillsDir = join5(this.config.getConfigDir(), SKILLS_DIR_NAME);
5458
+ const skillsDir = join4(this.config.getConfigDir(), SKILLS_DIR_NAME);
5633
5459
  this.skillManager = new SkillManager(skillsDir, this.config.get("ui").skillSizeWarn);
5634
5460
  const skillCount = this.skillManager.loadSkills();
5635
5461
  if (skillCount > 0) {
5636
5462
  process.stdout.write(theme.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
5637
5463
  `));
5638
5464
  }
5639
- const commandsDir = join5(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5465
+ const commandsDir = join4(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5640
5466
  this.customCommandManager = new CustomCommandManager(commandsDir);
5641
5467
  const customCmdCount = this.customCommandManager.loadCommands();
5642
5468
  if (customCmdCount > 0) {
@@ -6271,14 +6097,14 @@ Session '${this.resumeSessionId}' not found.
6271
6097
  const dir = normalized.includes("/") ? dirname3(normalized) : ".";
6272
6098
  const prefix = normalized.includes("/") ? basename2(normalized) : normalized;
6273
6099
  const absDir = resolve2(process.cwd(), dir);
6274
- if (!existsSync5(absDir)) return [];
6100
+ if (!existsSync4(absDir)) return [];
6275
6101
  const entries = readdirSync3(absDir);
6276
6102
  const results = [];
6277
6103
  for (const entry of entries) {
6278
6104
  if (entry.startsWith(".")) continue;
6279
6105
  if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
6280
6106
  try {
6281
- const fullPath = join5(absDir, entry);
6107
+ const fullPath = join4(absDir, entry);
6282
6108
  const stat = statSync3(fullPath);
6283
6109
  const rel = dir === "." ? entry : `${dir}/${entry}`;
6284
6110
  results.push(stat.isDirectory() ? `${rel}/` : rel);
@@ -6924,7 +6750,7 @@ This fresh stream has NO tools. Produce ONLY the document body: start with a mar
6924
6750
  const cleaned = stripPseudoToolCalls(genContent);
6925
6751
  if (looksLikeDocumentBody(cleaned)) {
6926
6752
  try {
6927
- writeFileSync3(saveToFile, cleaned, "utf-8");
6753
+ writeFileSync2(saveToFile, cleaned, "utf-8");
6928
6754
  process.stdout.write(theme.warning(
6929
6755
  `
6930
6756
  \u26A0 Salvaged save: stripped pseudo-tool-call markup (matched: ${pseudoMatch})
@@ -7411,6 +7237,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
7411
7237
  getToolExecutor: () => this.toolExecutor,
7412
7238
  getCostTracker: () => this.costTracker,
7413
7239
  computeRoutingDecision: (userInput) => this.computeRoutingDecision(userInput),
7240
+ getCommandNames: () => this.commands.listAll().map((c) => c.name),
7414
7241
  exit: () => this.handleExit()
7415
7242
  };
7416
7243
  await cmd.execute(args, ctx);
@@ -7533,7 +7360,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7533
7360
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7534
7361
  process.exit(1);
7535
7362
  }
7536
- const { startWebServer } = await import("./server-2CBNRT2W.js");
7363
+ const { startWebServer } = await import("./server-D2NQLJBH.js");
7537
7364
  await startWebServer({ port, host: options.host });
7538
7365
  });
7539
7366
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7699,13 +7526,17 @@ program.command("sessions").description("List recent conversation sessions").opt
7699
7526
  ${total} session${total !== 1 ? "s" : ""} total`);
7700
7527
  console.log(footer + "\n");
7701
7528
  });
7529
+ program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7530
+ const { runUsageCli } = await import("./usage-4724JLXN.js");
7531
+ await runUsageCli(options);
7532
+ });
7702
7533
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7703
- const { runDoctorCli } = await import("./doctor-cli-KSB3MLFV.js");
7534
+ const { runDoctorCli } = await import("./doctor-cli-6OZTPWTL.js");
7704
7535
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7705
7536
  });
7706
7537
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7707
7538
  try {
7708
- const batch = await import("./batch-ZBKVDSMZ.js");
7539
+ const batch = await import("./batch-ROTBHZ56.js");
7709
7540
  switch (action) {
7710
7541
  case "submit":
7711
7542
  if (!arg) {
@@ -7748,7 +7579,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7748
7579
  }
7749
7580
  });
7750
7581
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7751
- const { startMcpServer } = await import("./server-TXA5VTOS.js");
7582
+ const { startMcpServer } = await import("./server-KUJV27ZT.js");
7752
7583
  await startMcpServer({
7753
7584
  allowDestructive: !!options.allowDestructive,
7754
7585
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7757,7 +7588,7 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7757
7588
  });
7758
7589
  });
7759
7590
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7760
- const { runCi } = await import("./ci-6WGF6ID6.js");
7591
+ const { runCi } = await import("./ci-Y5JKHJPG.js");
7761
7592
  const result = await runCi({
7762
7593
  pr: options.pr,
7763
7594
  base: options.base,
@@ -7803,6 +7634,7 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
7803
7634
  ` ${G}aicli config${R} Setup wizard (API keys, preferences)`,
7804
7635
  ` ${G}aicli providers${R} List all providers and status`,
7805
7636
  ` ${G}aicli sessions${R} List recent conversation sessions`,
7637
+ ` ${G}aicli usage${R} Token + cost usage by provider/model (--days/--month/--json)`,
7806
7638
  ` ${G}aicli user <action>${R} User management (list/create/delete)`,
7807
7639
  ` ${G}aicli batch <action>${R} Anthropic Batches API (50% off, 24h): submit/list/status/results/cancel`,
7808
7640
  ` ${G}aicli mcp-serve${R} MCP server over STDIO (expose tools to Claude Desktop / Cursor)`,
@@ -7902,7 +7734,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7902
7734
  }),
7903
7735
  config.get("customProviders")
7904
7736
  );
7905
- const { startHub } = await import("./hub-DGEYFJPP.js");
7737
+ const { startHub } = await import("./hub-UMVOJF26.js");
7906
7738
  await startHub(
7907
7739
  {
7908
7740
  topic: topic ?? "",