jinzd-ai-cli 0.4.217 → 0.4.219

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 (30) hide show
  1. package/README.md +5 -2
  2. package/dist/{batch-YCOVKTXD.js → batch-CHAPYRVM.js} +2 -2
  3. package/dist/chunk-3I4RZYYO.js +638 -0
  4. package/dist/{chunk-KOPUCJXM.js → chunk-4ZN6L6K5.js} +3 -3
  5. package/dist/{chunk-VTH7BLXK.js → chunk-64NUW3WL.js} +1 -1
  6. package/dist/{chunk-QAYOI57M.js → chunk-EGMORH5W.js} +1 -1
  7. package/dist/{chunk-MGBMNCHG.js → chunk-GEN4O5QH.js} +94 -270
  8. package/dist/chunk-GGKQHPB3.js +604 -0
  9. package/dist/{chunk-WKOQ5CYC.js → chunk-KIEZBTVD.js} +1 -1
  10. package/dist/{chunk-OUAZOE5U.js → chunk-QUFYBR6Q.js} +47 -206
  11. package/dist/{chunk-XJGEQIYS.js → chunk-U7KVU25H.js} +1 -1
  12. package/dist/{chunk-WZ3VKLF3.js → chunk-VADHBW7T.js} +1 -1
  13. package/dist/{ci-52RZIYWB.js → ci-QPP66T53.js} +4 -4
  14. package/dist/{ci-format-73UXKE65.js → ci-format-5S3EEYRK.js} +2 -2
  15. package/dist/{constants-DIXAD35W.js → constants-FJOLBABC.js} +1 -1
  16. package/dist/doctor-cli-KB2LUDF3.js +17 -0
  17. package/dist/electron-server.js +921 -274
  18. package/dist/{hub-GIGBITZN.js → hub-DIM7SKKY.js} +1 -1
  19. package/dist/index.js +192 -200
  20. package/dist/{pr-KPQ5RPKB.js → pr-YQGH72N6.js} +4 -4
  21. package/dist/{run-tests-ZDSA3QES.js → run-tests-GEZSSNJM.js} +2 -2
  22. package/dist/{run-tests-K7QR5QN4.js → run-tests-O76JIBCW.js} +1 -1
  23. package/dist/{server-QT3SC2KI.js → server-5E2AIXVX.js} +108 -80
  24. package/dist/{server-22YF3U34.js → server-Z6O3G2LY.js} +9 -9
  25. package/dist/{task-orchestrator-BHQQCVTY.js → task-orchestrator-5HBW4O64.js} +9 -9
  26. package/dist/{usage-WZZFSFLM.js → usage-BMK6M5U3.js} +2 -2
  27. package/package.json +1 -1
  28. package/dist/chunk-524WZOKS.js +0 -166
  29. package/dist/chunk-SNJAOXFT.js +0 -129
  30. package/dist/doctor-cli-7GOQPULZ.js +0 -226
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  autoTrimSessionIfNeeded,
7
7
  buildInitPrompt,
8
8
  clearDevState,
9
- loadContextFiles,
10
9
  loadDevState,
11
10
  parseSimpleYaml,
12
11
  persistToolRound,
@@ -15,7 +14,7 @@ import {
15
14
  scanProject,
16
15
  sessionHasMeaningfulContent,
17
16
  setupProxy
18
- } from "./chunk-OUAZOE5U.js";
17
+ } from "./chunk-QUFYBR6Q.js";
19
18
  import {
20
19
  buildReviewPrompt,
21
20
  buildSecurityReviewPrompt
@@ -33,14 +32,12 @@ import {
33
32
  exportMemoryEntries,
34
33
  formatMemoryForPrompt,
35
34
  formatPermissionProfileWarning,
36
- getPendingHookTrust,
37
35
  getRecentlyDeniedAutoActions,
38
36
  googleSearchContext,
39
37
  initTheme,
40
38
  isInterrupted,
41
39
  isMemoryExpired,
42
40
  lastResponseStore,
43
- listHooks,
44
41
  listMemoryEntries,
45
42
  memoryMarkdownPath,
46
43
  memoryStorePath,
@@ -48,19 +45,17 @@ import {
48
45
  requestInterrupt,
49
46
  resetInterrupt,
50
47
  rlInternal,
51
- runLifecycleHooks,
52
48
  setContextWindow,
53
49
  setMaxOutputCap,
54
50
  spawnAgentContext,
55
51
  theme,
56
- trustHook,
57
52
  undoStack,
58
- untrustHook,
59
53
  updateMemoryApproval
60
- } from "./chunk-MGBMNCHG.js";
54
+ } from "./chunk-GEN4O5QH.js";
55
+ import "./chunk-K3CF65QH.js";
61
56
  import "./chunk-T2NL5ZIA.js";
62
57
  import "./chunk-BXP6YZ2P.js";
63
- import "./chunk-VTH7BLXK.js";
58
+ import "./chunk-64NUW3WL.js";
64
59
  import {
65
60
  SessionManager,
66
61
  getContentText
@@ -74,19 +69,45 @@ import {
74
69
  getPricing
75
70
  } from "./chunk-E44DTERW.js";
76
71
  import {
77
- getConfigDirUsage,
78
- listRecentCrashes,
72
+ fileCheckpoints
73
+ } from "./chunk-4BKXL7SM.js";
74
+ import {
75
+ buildChatIndex,
76
+ clearChatIndex,
77
+ getChatIndexStatus,
78
+ searchChatMemory
79
+ } from "./chunk-TB4W4Y4T.js";
80
+ import "./chunk-KHYD3WXE.js";
81
+ import {
82
+ DEFAULT_PATTERNS,
83
+ scanString
84
+ } from "./chunk-SLSWPBK3.js";
85
+ import {
86
+ buildDoctorReport,
87
+ formatDoctorReport,
88
+ loadContextFiles,
79
89
  writeCrashLog
80
- } from "./chunk-524WZOKS.js";
90
+ } from "./chunk-3I4RZYYO.js";
81
91
  import {
82
92
  ProviderRegistry
83
93
  } from "./chunk-QMXC327F.js";
84
94
  import {
85
- getStatsSnapshot,
86
- getTopFailingTools,
87
- getTopUsedTools,
88
- installFlushOnExit
89
- } from "./chunk-SNJAOXFT.js";
95
+ describePlugin,
96
+ getActivePluginAssets,
97
+ getInstalledPlugin,
98
+ getPendingHookTrust,
99
+ installFlushOnExit,
100
+ installPlugin,
101
+ listHooks,
102
+ listInstalledPlugins,
103
+ mergePluginHooks,
104
+ pluginRoot,
105
+ runLifecycleHooks,
106
+ setPluginEnabled,
107
+ trustHook,
108
+ trustPlugin,
109
+ untrustHook
110
+ } from "./chunk-GGKQHPB3.js";
90
111
  import {
91
112
  CONTENT_ONLY_STREAM_REMINDER,
92
113
  TEE_FINAL_USER_NUDGE,
@@ -108,7 +129,7 @@ import {
108
129
  } from "./chunk-XPBEJB27.js";
109
130
  import {
110
131
  ConfigManager
111
- } from "./chunk-WKOQ5CYC.js";
132
+ } from "./chunk-KIEZBTVD.js";
112
133
  import {
113
134
  AuthError,
114
135
  ProviderError,
@@ -126,34 +147,18 @@ import {
126
147
  DESCRIPTION,
127
148
  DEV_STATE_FILE_NAME,
128
149
  MCP_PROJECT_CONFIG_NAME,
129
- MEMORY_FILE_NAME,
130
150
  PLAN_MODE_READONLY_TOOLS,
131
151
  PLAN_MODE_SYSTEM_ADDON,
132
152
  REPO_URL,
133
153
  SKILLS_DIR_NAME,
134
154
  VERSION,
135
155
  buildUserIdentityPrompt
136
- } from "./chunk-XJGEQIYS.js";
156
+ } from "./chunk-U7KVU25H.js";
137
157
  import {
138
158
  formatGitContextForPrompt,
139
159
  getGitContext,
140
160
  getGitRoot
141
161
  } from "./chunk-HOSJZMQS.js";
142
- import {
143
- fileCheckpoints
144
- } from "./chunk-4BKXL7SM.js";
145
- import {
146
- buildChatIndex,
147
- clearChatIndex,
148
- getChatIndexStatus,
149
- searchChatMemory
150
- } from "./chunk-TB4W4Y4T.js";
151
- import "./chunk-KHYD3WXE.js";
152
- import {
153
- DEFAULT_PATTERNS,
154
- scanString
155
- } from "./chunk-SLSWPBK3.js";
156
- import "./chunk-K3CF65QH.js";
157
162
  import "./chunk-CKH4KQ4E.js";
158
163
  import {
159
164
  atomicWriteFileSync
@@ -164,7 +169,7 @@ import { program } from "commander";
164
169
 
165
170
  // src/repl/repl.ts
166
171
  import * as readline from "readline";
167
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync3, mkdirSync as mkdirSync4 } from "fs";
172
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, mkdirSync as mkdirSync4 } from "fs";
168
173
  import { join as join4, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
169
174
  import chalk4 from "chalk";
170
175
 
@@ -289,7 +294,7 @@ var ABOUT_FEATURES = [
289
294
  "Sub-agents: spawn_agent delegates complex subtasks to an isolated tool-call loop",
290
295
  "Agent Skills: ~/.aicli/skills/ reusable packs (system prompt + tool whitelist)",
291
296
  "MCP protocol: connect external MCP server tools; aicli mcp-serve reverses aicli into a server",
292
- "Plugin system: ~/.aicli/plugins/*.js custom tools (allowPlugins, off by default)",
297
+ "Plugin packages: skills/hooks/commands/MCP/agents via trusted .aicli-plugin/plugin.json",
293
298
  "Permission rules + hooks: auto-approve/deny/confirm + pre/post tool execution hooks",
294
299
  "User identity (/profile): persistent profile injected into every provider",
295
300
  "Symbol + semantic code index: find_symbol/get_outline/find_references/search_code (8 languages)",
@@ -657,7 +662,7 @@ Error${typeName}: ${lines.join("\n")}
657
662
  };
658
663
 
659
664
  // src/repl/commands/index.ts
660
- import { writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync2, readFileSync, statSync as statSync2 } from "fs";
665
+ import { writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync2, readFileSync } from "fs";
661
666
  import { execSync as execSync2 } from "child_process";
662
667
  import { platform } from "os";
663
668
  import { resolve, dirname as dirname2, join as join2, basename } from "path";
@@ -916,6 +921,7 @@ function createDefaultCommands() {
916
921
  rows: [
917
922
  ["/tools", "List all AI tools available"],
918
923
  ["/plugins", "Show plugin directory and loaded plugins"],
924
+ ["/plugin [list|install|enable|disable|inspect|trust]", "Manage package plugins"],
919
925
  ["/mcp [reconnect [id]]", "MCP servers / reconnect disconnected"],
920
926
  ["/skill [name|off|list]", "Manage agent skills (reusable prompt packs)"],
921
927
  ["/context", "Show or reload hierarchical context layers"],
@@ -1376,7 +1382,7 @@ No commands match "${filter}".
1376
1382
  console.log(" PermProf : " + (ctx.config.get("defaultPermissionProfile") ?? "legacy"));
1377
1383
  console.log(" Network : " + (ctx.config.get("networkPolicy")?.enabled ?? false ? "on" : "off"));
1378
1384
  console.log(" AutoMode : " + (ctx.getToolExecutor().sessionAutoMode ? "on" : "off"));
1379
- console.log(" Hooks : " + getPendingHookTrust(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()).length + " pending trust");
1385
+ console.log(" Hooks : " + getPendingHookTrust(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir()).length + " pending trust");
1380
1386
  console.log(` Messages : ${session?.messages.length ?? 0}`);
1381
1387
  console.log(` Session : ${session?.id.slice(0, 8) ?? "none"}`);
1382
1388
  if (layers.length > 0) {
@@ -1661,6 +1667,91 @@ No tools match "${filter}".
1661
1667
  console.log();
1662
1668
  }
1663
1669
  },
1670
+ {
1671
+ name: "plugin",
1672
+ description: "Manage package plugins (.aicli-plugin/plugin.json)",
1673
+ usage: "/plugin [list|install <path>|enable <name>|disable <name>|inspect <name>|trust <name>]",
1674
+ async execute(args, ctx) {
1675
+ const sub = args[0] ?? "list";
1676
+ const configDir = ctx.config.getConfigDir();
1677
+ try {
1678
+ if (sub === "list") {
1679
+ const plugins = listInstalledPlugins(configDir);
1680
+ console.log(theme.heading("\n\u{1F50C} Package Plugins\n"));
1681
+ console.log(theme.dim(` Dir: ${pluginRoot(configDir)}`));
1682
+ if (plugins.length === 0) {
1683
+ console.log(theme.dim(" No package plugins installed."));
1684
+ console.log(theme.dim(" Use: /plugin install <path-to-plugin-dir>"));
1685
+ } else {
1686
+ for (const p of plugins) {
1687
+ const status = `${p.enabled ? theme.success("enabled") : theme.dim("disabled")} / ${p.trusted ? theme.success("trusted") : theme.warning("untrusted")}`;
1688
+ console.log(` ${theme.accent(p.name.padEnd(22))} ${p.manifest.version.padEnd(10)} ${status}`);
1689
+ if (p.manifest.description) console.log(theme.dim(` ${p.manifest.description}`));
1690
+ if (!p.valid && p.error) console.log(theme.warning(` invalid: ${p.error}`));
1691
+ }
1692
+ }
1693
+ console.log();
1694
+ return;
1695
+ }
1696
+ if (sub === "install") {
1697
+ const input2 = args[1];
1698
+ if (!input2) {
1699
+ ctx.renderer.printInfo("Usage: /plugin install <path-to-plugin-dir>");
1700
+ return;
1701
+ }
1702
+ const plugin = installPlugin(configDir, input2);
1703
+ ctx.renderer.printSuccess(`Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.`);
1704
+ return;
1705
+ }
1706
+ if (sub === "trust") {
1707
+ const name = args[1];
1708
+ if (!name) {
1709
+ ctx.renderer.printInfo("Usage: /plugin trust <name>");
1710
+ return;
1711
+ }
1712
+ const state = trustPlugin(configDir, name);
1713
+ ctx.renderer.printSuccess(`Trusted plugin ${state.name}.`);
1714
+ return;
1715
+ }
1716
+ if (sub === "enable" || sub === "disable") {
1717
+ const name = args[1];
1718
+ if (!name) {
1719
+ ctx.renderer.printInfo(`Usage: /plugin ${sub} <name>`);
1720
+ return;
1721
+ }
1722
+ const state = setPluginEnabled(configDir, name, sub === "enable");
1723
+ ctx.renderer.printSuccess(`${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state.name}. Restart aicli or reload affected managers for changes to take effect.`);
1724
+ return;
1725
+ }
1726
+ if (sub === "inspect") {
1727
+ const name = args[1];
1728
+ if (!name) {
1729
+ ctx.renderer.printInfo("Usage: /plugin inspect <name>");
1730
+ return;
1731
+ }
1732
+ const plugin = getInstalledPlugin(configDir, name);
1733
+ console.log();
1734
+ for (const line of describePlugin(plugin)) console.log(line);
1735
+ console.log();
1736
+ return;
1737
+ }
1738
+ if (sub === "active") {
1739
+ const assets = getActivePluginAssets(configDir);
1740
+ console.log(JSON.stringify({
1741
+ plugins: assets.plugins.map((p) => p.name),
1742
+ skillDirs: assets.skillDirs,
1743
+ commandDirs: assets.commandDirs,
1744
+ agentDirs: assets.agentDirs,
1745
+ mcpServers: Object.keys(assets.mcpServers)
1746
+ }, null, 2));
1747
+ return;
1748
+ }
1749
+ ctx.renderer.printInfo("Usage: /plugin [list|install|enable|disable|inspect|trust]");
1750
+ } catch (err) {
1751
+ ctx.renderer.renderError(err instanceof Error ? err.message : String(err));
1752
+ }
1753
+ }
1754
+ },
1664
1755
  {
1665
1756
  name: "mcp",
1666
1757
  description: "Show MCP server connections and tools",
@@ -1670,7 +1761,7 @@ No tools match "${filter}".
1670
1761
  const { join: join5 } = await import("path");
1671
1762
  const { existsSync: existsSync5 } = await import("fs");
1672
1763
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1673
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-DIXAD35W.js");
1764
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-FJOLBABC.js");
1674
1765
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1675
1766
  const cwd = process.cwd();
1676
1767
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -1979,7 +2070,7 @@ No tools match "${filter}".
1979
2070
  return;
1980
2071
  }
1981
2072
  if (sub === "reload") {
1982
- const count = manager.loadSkills();
2073
+ const count = manager.loadSkills(getActivePluginAssets(ctx.config.getConfigDir()).skillDirs);
1983
2074
  ctx.renderer.printSuccess(`Reloaded: ${count} skill(s) found.`);
1984
2075
  return;
1985
2076
  }
@@ -2706,7 +2797,7 @@ ${hint}` : "")
2706
2797
  }
2707
2798
  const sub = args[0]?.toLowerCase();
2708
2799
  if (sub === "reload") {
2709
- const count = mgr.loadCommands();
2800
+ const count = mgr.loadCommands(getActivePluginAssets(ctx.config.getConfigDir()).commandDirs);
2710
2801
  console.log(theme.success(` \u2713 Reloaded ${count} custom command(s).`));
2711
2802
  return;
2712
2803
  }
@@ -2729,7 +2820,7 @@ ${hint}` : "")
2729
2820
  usage: "/test [command|filter]",
2730
2821
  async execute(args, ctx) {
2731
2822
  try {
2732
- const { executeTests } = await import("./run-tests-ZDSA3QES.js");
2823
+ const { executeTests } = await import("./run-tests-GEZSSNJM.js");
2733
2824
  const argStr = args.join(" ").trim();
2734
2825
  let testArgs = {};
2735
2826
  if (argStr) {
@@ -3090,126 +3181,16 @@ ${hint}` : "")
3090
3181
  // ── /doctor ────────────────────────────────────────────────────────────────
3091
3182
  {
3092
3183
  name: "doctor",
3093
- description: "Health check: API keys, config, MCP connections",
3184
+ description: "Health check: context, permissions, hooks, plugins, MCP, config",
3094
3185
  usage: "/doctor",
3095
3186
  execute(_args, ctx) {
3096
- console.log(theme.heading("\n\u{1FA7A} AI-CLI Health Check\n"));
3097
- const providers = ctx.providers.listAll();
3098
- console.log(theme.heading("API Keys:"));
3099
- for (const p of providers) {
3100
- const icon = p.configured ? theme.success(" \u2713") : theme.dim(" \u25CB");
3101
- const status = p.configured ? theme.success("configured") : theme.dim("not configured");
3102
- console.log(`${icon} ${p.id.padEnd(14)} ${status}`);
3103
- }
3104
- console.log();
3105
- const configDir = ctx.config.getConfigDir();
3106
- console.log(theme.heading("Config Files:"));
3107
- console.log(` Dir: ${theme.accent(configDir)}`);
3108
- const checkFile = (label, filePath) => {
3109
- const exists = existsSync2(filePath);
3110
- const icon = exists ? theme.success("\u2713") : theme.dim("\u2013");
3111
- let extra = "";
3112
- if (exists) {
3113
- try {
3114
- extra = theme.dim(` (${statSync2(filePath).size} bytes)`);
3115
- } catch {
3116
- }
3117
- }
3118
- console.log(` ${icon} ${label.padEnd(14)} ${exists ? filePath : theme.dim("(not found)")}${extra}`);
3119
- };
3120
- checkFile("config.json", join2(configDir, "config.json"));
3121
- checkFile("memory.md", join2(configDir, MEMORY_FILE_NAME));
3122
- checkFile("dev-state.md", join2(configDir, "dev-state.md"));
3123
- console.log();
3124
- const mcpManager = ctx.getMcpManager();
3125
- if (mcpManager) {
3126
- console.log(theme.heading("MCP Servers:"));
3127
- const statuses = mcpManager.getStatus();
3128
- if (statuses.length === 0) {
3129
- console.log(theme.dim(" (no servers configured)"));
3130
- } else {
3131
- for (const s of statuses) {
3132
- const icon = s.connected ? theme.success(" \u2713") : theme.error(" \u2717");
3133
- const state = s.connected ? theme.success(`connected`) + theme.dim(` \xB7 ${s.serverName} \xB7 ${s.toolCount} tools`) : theme.error("disconnected") + (s.error ? theme.dim(` \xB7 ${s.error}`) : "");
3134
- console.log(`${icon} ${s.serverId.padEnd(16)} ${state}`);
3135
- }
3136
- }
3137
- console.log();
3138
- }
3139
- const provider = ctx.getCurrentProvider();
3140
- const model = ctx.getCurrentModel();
3141
- const ctxTokens = ctx.estimateConversationTokens();
3142
- const ctxWindow = ctx.getContextWindowSize();
3143
- const pct = ctxWindow > 0 ? Math.round(ctxTokens / ctxWindow * 100) : 0;
3144
- const pctColor = pct > 80 ? theme.error : pct > 60 ? theme.warning : theme.success;
3145
- const dirs = ctx.listContextDirs();
3146
- console.log(theme.heading("Current Session:"));
3147
- console.log(` Provider: ${theme.accent(provider)}`);
3148
- console.log(` Model: ${theme.accent(model)}`);
3149
- console.log(` Context: ${pctColor(`${pct}%`)} (est. ${ctxTokens.toLocaleString()} / ${ctxWindow.toLocaleString()} tokens)`);
3150
- if (dirs.length > 0) {
3151
- console.log(` Extra dirs: ${dirs.map((d) => d.dir).join(", ")}`);
3152
- }
3153
- console.log();
3154
- const crashes = listRecentCrashes(5, configDir);
3155
- console.log(theme.heading("Recent Crashes (last 5):"));
3156
- if (crashes.length === 0) {
3157
- console.log(theme.dim(" (none \u2014 clean!)"));
3158
- } else {
3159
- for (const c of crashes) {
3160
- console.log(` ${theme.error("\u2717")} ${c.timestamp} ${theme.warning(c.kind)}`);
3161
- console.log(` ${theme.dim(c.message)}`);
3162
- console.log(` ${theme.dim(c.file)}`);
3163
- }
3164
- }
3165
- console.log();
3166
- const allStats = getStatsSnapshot();
3167
- const totalCalls = allStats.reduce((a, b) => a + b.calls, 0);
3168
- const totalFails = allStats.reduce((a, b) => a + b.failures, 0);
3169
- const topUsed = getTopUsedTools(5);
3170
- const topFailing = getTopFailingTools(5);
3171
- console.log(theme.heading("Tool Usage:"));
3172
- const pctFail = totalCalls ? (totalFails * 100 / totalCalls).toFixed(1) : "0.0";
3173
- console.log(` total: ${totalCalls} calls \xB7 ${totalFails} failures (${pctFail}%)`);
3174
- if (topUsed.length > 0) {
3175
- console.log(theme.dim(" top used:"));
3176
- for (const t of topUsed) {
3177
- const avg = t.calls ? (t.totalDurationMs / t.calls).toFixed(0) : "0";
3178
- console.log(` ${t.name.padEnd(20)} ${String(t.calls).padStart(6)} calls \xB7 avg ${avg}ms`);
3179
- }
3180
- }
3181
- if (topFailing.length > 0) {
3182
- console.log(theme.dim(" top failing:"));
3183
- for (const t of topFailing) {
3184
- const rate = (t.failures * 100 / Math.max(1, t.calls)).toFixed(1);
3185
- const msg = t.lastFailureMessage ? ` \xB7 ${theme.dim(t.lastFailureMessage.slice(0, 80))}` : "";
3186
- console.log(` ${theme.error(t.name.padEnd(20))} ${t.failures}/${t.calls} (${rate}%)${msg}`);
3187
- }
3188
- }
3189
- console.log();
3190
- const usage = getConfigDirUsage(configDir);
3191
- const fmt = (n) => {
3192
- if (n < 1024) return `${n} B`;
3193
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
3194
- if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
3195
- return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
3196
- };
3197
- const dirAnnotations = {
3198
- models: "semantic search model \u2192 /index semantic-clear to free",
3199
- index: "symbol index \u2192 /index clear to rebuild",
3200
- history: "conversation history \u2192 auto-pruned after 30 days",
3201
- logs: "crash logs",
3202
- "memory-index": "chat memory index \u2192 /memory index-clear to free"
3203
- };
3204
- console.log(theme.heading("Disk Usage (~/.aicli):"));
3205
- console.log(` total: ${theme.accent(fmt(usage.totalBytes))}`);
3206
- for (const e of usage.entries.slice(0, 10)) {
3207
- const note = dirAnnotations[e.name];
3208
- const noteStr = note ? ` ${theme.dim("\xB7")} ${theme.dim(note)}` : "";
3209
- console.log(` ${e.name.padEnd(18)} ${theme.dim(fmt(e.bytes))}${noteStr}`);
3210
- }
3211
- console.log();
3212
- console.log(theme.success("\u2713 Health check complete\n"));
3187
+ const report = buildDoctorReport({
3188
+ config: ctx.config,
3189
+ providers: ctx.providers,
3190
+ cwd: process.cwd(),
3191
+ mcpStatuses: ctx.getMcpManager()?.getStatus() ?? []
3192
+ });
3193
+ console.log(formatDoctorReport(report, true));
3213
3194
  }
3214
3195
  },
3215
3196
  {
@@ -3377,7 +3358,7 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3377
3358
  usage: "/hooks [list|inspect <id>|trust <id>|untrust <id>|disable]",
3378
3359
  execute(args, ctx) {
3379
3360
  const sub = (args[0] || "list").toLowerCase();
3380
- const hooks = listHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir());
3361
+ const hooks = listHooks(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir());
3381
3362
  const findHook = (id) => hooks.find((h) => h.id === id || h.hash.startsWith(id ?? ""));
3382
3363
  if (sub === "list") {
3383
3364
  console.log(theme.heading(" Hooks"));
@@ -3456,7 +3437,7 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3456
3437
  console.log(" profile : " + (ctx.config.get("defaultPermissionProfile") ?? "legacy"));
3457
3438
  console.log(" network : " + (ctx.config.get("networkPolicy")?.enabled ?? false ? "on" : "off"));
3458
3439
  console.log(" autoMode : " + (ctx.getToolExecutor().sessionAutoMode ? "on" : "off"));
3459
- console.log(" hooks : " + getPendingHookTrust(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()).length + " pending trust");
3440
+ console.log(" hooks : " + getPendingHookTrust(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir()).length + " pending trust");
3460
3441
  console.log(` patterns : ${DEFAULT_PATTERNS.length} built-in + ${security.customPatterns?.length ?? 0} custom`);
3461
3442
  console.log(` last save : ${ctx.sessions.lastRedactionHits} hit(s) redacted`);
3462
3443
  console.log();
@@ -4277,22 +4258,22 @@ var CustomCommandManager = class {
4277
4258
  this.commandsDir = commandsDir;
4278
4259
  }
4279
4260
  commands = /* @__PURE__ */ new Map();
4280
- loadCommands() {
4261
+ loadCommands(extraDirs = []) {
4281
4262
  this.commands.clear();
4282
- if (!existsSync3(this.commandsDir)) {
4283
- mkdirSync3(this.commandsDir, { recursive: true });
4284
- return 0;
4263
+ this.loadCommandDir(this.commandsDir, true);
4264
+ for (const dir of extraDirs) this.loadCommandDir(dir, false);
4265
+ return this.commands.size;
4266
+ }
4267
+ loadCommandDir(dir, createIfMissing) {
4268
+ if (!existsSync3(dir)) {
4269
+ if (createIfMissing) mkdirSync3(dir, { recursive: true });
4270
+ return;
4285
4271
  }
4286
- let count = 0;
4287
- for (const file of readdirSync(this.commandsDir)) {
4272
+ for (const file of readdirSync(dir)) {
4288
4273
  if (extname(file) !== ".md") continue;
4289
- const cmd = parseCommandFile(join3(this.commandsDir, file));
4290
- if (cmd) {
4291
- this.commands.set(cmd.meta.name, cmd);
4292
- count++;
4293
- }
4274
+ const cmd = parseCommandFile(join3(dir, file));
4275
+ if (cmd && !this.commands.has(cmd.meta.name)) this.commands.set(cmd.meta.name, cmd);
4294
4276
  }
4295
- return count;
4296
4277
  }
4297
4278
  listCommands() {
4298
4279
  return [...this.commands.values()];
@@ -4596,7 +4577,7 @@ function parseAtReferences(input2, cwd) {
4596
4577
  continue;
4597
4578
  }
4598
4579
  if (mime) {
4599
- const fileSize = statSync3(absPath).size;
4580
+ const fileSize = statSync2(absPath).size;
4600
4581
  if (fileSize > MAX_IMAGE_BYTES) {
4601
4582
  refs.push({ path: rawPath, type: "toolarge" });
4602
4583
  continue;
@@ -4670,7 +4651,7 @@ var Repl = class {
4670
4651
  const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
4671
4652
  const permissionProfiles = this.config.get("permissionProfiles") ?? {};
4672
4653
  this.toolExecutor.setConfig({
4673
- hookConfig: this.config.get("hooks") ?? void 0,
4654
+ hookConfig: mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
4674
4655
  hookConfigDir: this.config.getConfigDir(),
4675
4656
  permissionRules: this.config.get("permissionRules") ?? [],
4676
4657
  defaultPermission: this.config.get("defaultPermission") ?? "confirm",
@@ -4680,11 +4661,12 @@ var Repl = class {
4680
4661
  workspaceRoot: process.cwd(),
4681
4662
  networkPolicy: this.config.get("networkPolicy")
4682
4663
  });
4683
- const pendingHooks = getPendingHookTrust(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
4664
+ const effectiveHooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
4665
+ const pendingHooks = getPendingHookTrust(effectiveHooks, this.config.getConfigDir());
4684
4666
  if (pendingHooks.length > 0) {
4685
4667
  process.stderr.write(theme.warning(" \u26A0 " + pendingHooks.length + " project hook(s) require trust. Run /hooks list and /hooks trust <id>.\n"));
4686
4668
  }
4687
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "SessionStart", { source: "repl" }, { configDir: this.config.getConfigDir() });
4669
+ runLifecycleHooks(effectiveHooks, "SessionStart", { source: "repl" }, { configDir: this.config.getConfigDir() });
4688
4670
  if (this.config.get("alwaysYolo") === true) {
4689
4671
  this.toolExecutor.sessionAutoApprove = true;
4690
4672
  }
@@ -4853,7 +4835,7 @@ var Repl = class {
4853
4835
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
4854
4836
  let isDir;
4855
4837
  try {
4856
- isDir = statSync3(fullPath).isDirectory();
4838
+ isDir = statSync2(fullPath).isDirectory();
4857
4839
  } catch {
4858
4840
  continue;
4859
4841
  }
@@ -4885,7 +4867,7 @@ ${treeLines.join("\n")}`
4885
4867
  const fullPath = join4(dir, name);
4886
4868
  let st;
4887
4869
  try {
4888
- st = statSync3(fullPath);
4870
+ st = statSync2(fullPath);
4889
4871
  } catch {
4890
4872
  continue;
4891
4873
  }
@@ -4932,7 +4914,7 @@ ${content}
4932
4914
  }
4933
4915
  let isDir;
4934
4916
  try {
4935
- isDir = statSync3(absPath).isDirectory();
4917
+ isDir = statSync2(absPath).isDirectory();
4936
4918
  } catch {
4937
4919
  return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
4938
4920
  }
@@ -5145,7 +5127,7 @@ ${skillContent}`);
5145
5127
  return false;
5146
5128
  }
5147
5129
  const preDecisions = runLifecycleHooks(
5148
- this.config.get("hooks") ?? void 0,
5130
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5149
5131
  "PreCompact",
5150
5132
  { sessionId: session.id, messageCount: session.messages.length, instruction },
5151
5133
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -5221,7 +5203,7 @@ ${response.content.trim()}
5221
5203
  const removedCount = session.compact(summaryMsg, ackMsg, COMPACT_KEEP_LAST);
5222
5204
  const afterCount = session.messages.length;
5223
5205
  runLifecycleHooks(
5224
- this.config.get("hooks") ?? void 0,
5206
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5225
5207
  "PostCompact",
5226
5208
  { sessionId: session.id, beforeCount, afterCount, removedCount, keepLast: COMPACT_KEEP_LAST, instruction },
5227
5209
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -5423,14 +5405,15 @@ Session '${this.resumeSessionId}' not found.
5423
5405
  }
5424
5406
  const skillsDir = join4(this.config.getConfigDir(), SKILLS_DIR_NAME);
5425
5407
  this.skillManager = new SkillManager(skillsDir, this.config.get("ui").skillSizeWarn);
5426
- const skillCount = this.skillManager.loadSkills();
5408
+ const pluginAssets = getActivePluginAssets(this.config.getConfigDir());
5409
+ const skillCount = this.skillManager.loadSkills(pluginAssets.skillDirs);
5427
5410
  if (skillCount > 0) {
5428
5411
  process.stdout.write(theme.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
5429
5412
  `));
5430
5413
  }
5431
5414
  const commandsDir = join4(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5432
5415
  this.customCommandManager = new CustomCommandManager(commandsDir);
5433
- const customCmdCount = this.customCommandManager.loadCommands();
5416
+ const customCmdCount = this.customCommandManager.loadCommands(pluginAssets.commandDirs);
5434
5417
  if (customCmdCount > 0) {
5435
5418
  process.stdout.write(theme.dim(` \u{1F4CB} Custom commands: ${customCmdCount} loaded (use /commands to list)
5436
5419
  `));
@@ -5486,6 +5469,7 @@ Session '${this.resumeSessionId}' not found.
5486
5469
  })();
5487
5470
  const mcpEnabled = this.config.get("mcpEnabled") !== false;
5488
5471
  const globalMcpServers = mcpEnabled ? this.config.get("mcpServers") ?? {} : {};
5472
+ const pluginMcpServers = mcpEnabled ? getActivePluginAssets(this.config.getConfigDir()).mcpServers : {};
5489
5473
  const projectMcpResult = mcpEnabled ? this.loadProjectMcpConfig() : null;
5490
5474
  if (!mcpEnabled) {
5491
5475
  process.stdout.write(theme.dim(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)\n"));
@@ -5507,10 +5491,13 @@ Session '${this.resumeSessionId}' not found.
5507
5491
  );
5508
5492
  }
5509
5493
  }
5510
- const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
5494
+ const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
5511
5495
  for (const id of Object.keys(globalMcpServers)) {
5512
5496
  this.mcpServerSources.set(id, "global");
5513
5497
  }
5498
+ for (const id of Object.keys(pluginMcpServers)) {
5499
+ this.mcpServerSources.set(id, "plugin");
5500
+ }
5514
5501
  for (const id of Object.keys(projectMcpServers)) {
5515
5502
  this.mcpServerSources.set(id, "project");
5516
5503
  }
@@ -5518,6 +5505,7 @@ Session '${this.resumeSessionId}' not found.
5518
5505
  if (Object.keys(mergedMcpServers).length > 0) {
5519
5506
  this.mcpManager = new McpManager();
5520
5507
  const mcpProjectCount = Object.keys(projectMcpServers).length;
5508
+ const mcpPluginCount = Object.keys(pluginMcpServers).length;
5521
5509
  let bannerEmitted = false;
5522
5510
  const emitBanner = (afterDeadline) => {
5523
5511
  if (bannerEmitted) return;
@@ -5663,7 +5651,7 @@ Session '${this.resumeSessionId}' not found.
5663
5651
  }
5664
5652
  runUserPromptHooks(sessionId, userInput) {
5665
5653
  const promptDecisions = runLifecycleHooks(
5666
- this.config.get("hooks") ?? void 0,
5654
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5667
5655
  "UserPromptSubmit",
5668
5656
  { sessionId, prompt: userInput },
5669
5657
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -6096,7 +6084,7 @@ Session '${this.resumeSessionId}' not found.
6096
6084
  if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
6097
6085
  try {
6098
6086
  const fullPath = join4(absDir, entry);
6099
- const stat = statSync3(fullPath);
6087
+ const stat = statSync2(fullPath);
6100
6088
  const rel = dir === "." ? entry : `${dir}/${entry}`;
6101
6089
  results.push(stat.isDirectory() ? `${rel}/` : rel);
6102
6090
  } catch {
@@ -7152,7 +7140,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
7152
7140
  void this.shutdownAsync();
7153
7141
  }
7154
7142
  async shutdownAsync() {
7155
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
7143
+ runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
7156
7144
  this.running = false;
7157
7145
  const sessionId = this.sessions.current?.id;
7158
7146
  if (sessionId) {
@@ -7267,7 +7255,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7267
7255
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7268
7256
  process.exit(1);
7269
7257
  }
7270
- const { startWebServer } = await import("./server-QT3SC2KI.js");
7258
+ const { startWebServer } = await import("./server-5E2AIXVX.js");
7271
7259
  await startWebServer({ port, host: options.host });
7272
7260
  });
7273
7261
  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) => {
@@ -7434,16 +7422,20 @@ program.command("sessions").description("List recent conversation sessions").opt
7434
7422
  console.log(footer + "\n");
7435
7423
  });
7436
7424
  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) => {
7437
- const { runUsageCli } = await import("./usage-WZZFSFLM.js");
7425
+ const { runUsageCli } = await import("./usage-BMK6M5U3.js");
7438
7426
  await runUsageCli(options);
7439
7427
  });
7440
7428
  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) => {
7441
- const { runDoctorCli } = await import("./doctor-cli-7GOQPULZ.js");
7442
- await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7429
+ const { runDoctorCli } = await import("./doctor-cli-KB2LUDF3.js");
7430
+ const argv = process.argv.slice(2);
7431
+ await runDoctorCli({
7432
+ json: !!options.json || argv.includes("--json"),
7433
+ resetStats: !!options.resetStats || argv.includes("--reset-stats")
7434
+ });
7443
7435
  });
7444
7436
  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) => {
7445
7437
  try {
7446
- const batch = await import("./batch-YCOVKTXD.js");
7438
+ const batch = await import("./batch-CHAPYRVM.js");
7447
7439
  switch (action) {
7448
7440
  case "submit":
7449
7441
  if (!arg) {
@@ -7486,7 +7478,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7486
7478
  }
7487
7479
  });
7488
7480
  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) => {
7489
- const { startMcpServer } = await import("./server-22YF3U34.js");
7481
+ const { startMcpServer } = await import("./server-Z6O3G2LY.js");
7490
7482
  await startMcpServer({
7491
7483
  allowDestructive: !!options.allowDestructive,
7492
7484
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7499,7 +7491,7 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7499
7491
  console.error(`Unknown pr action: ${action}. Use review | security-review | summarize.`);
7500
7492
  process.exit(1);
7501
7493
  }
7502
- const { runPrCli } = await import("./pr-KPQ5RPKB.js");
7494
+ const { runPrCli } = await import("./pr-YQGH72N6.js");
7503
7495
  const result = await runPrCli({
7504
7496
  action,
7505
7497
  target,
@@ -7519,8 +7511,8 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7519
7511
  process.exit(result.exitCode);
7520
7512
  });
7521
7513
  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 or URL; diff fetched via gh pr diff <value>", (v) => /^#?\d+$/.test(v) ? parseInt(v.replace(/^#/, ""), 10) : v).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)").option("--json", "Output a structured JSON report").option("--markdown", "Output Markdown report (default)").option("--sarif", "Output SARIF 2.1.0 for code scanning artifacts").option("--fail-on-security-high", "Fail when the security review contains HIGH/CRITICAL findings (default)").option("--no-fail-on-security-high", "Do not fail on security HIGH/CRITICAL findings").option("--fail-on-test-failure", "Fail when the report mentions test failures (default)").option("--no-fail-on-test-failure", "Do not fail on test failure findings").option("--fail-on-lint-failure", "Fail when the report mentions lint/typecheck failures (default)").option("--no-fail-on-lint-failure", "Do not fail on lint/typecheck failure findings").action(async (options) => {
7522
- const { runCi } = await import("./ci-52RZIYWB.js");
7523
- const { formatCiResult } = await import("./ci-format-73UXKE65.js");
7514
+ const { runCi } = await import("./ci-QPP66T53.js");
7515
+ const { formatCiResult } = await import("./ci-format-5S3EEYRK.js");
7524
7516
  const formats = [options.json, options.markdown, options.sarif].filter(Boolean).length;
7525
7517
  if (formats > 1) {
7526
7518
  console.error("Choose only one output format: --json, --markdown, or --sarif.");
@@ -7677,7 +7669,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7677
7669
  }),
7678
7670
  config.get("customProviders")
7679
7671
  );
7680
- const { startHub } = await import("./hub-GIGBITZN.js");
7672
+ const { startHub } = await import("./hub-DIM7SKKY.js");
7681
7673
  await startHub(
7682
7674
  {
7683
7675
  topic: topic ?? "",