jinzd-ai-cli 0.4.216 → 0.4.218

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 (31) hide show
  1. package/README.md +6 -3
  2. package/dist/{batch-C6HCXZIG.js → batch-7V7OTMUP.js} +2 -2
  3. package/dist/{chat-index-R2E27VXN.js → chat-index-PS274XM7.js} +1 -1
  4. package/dist/{chunk-TJ6GGN4B.js → chunk-5CA2TJ5F.js} +1 -1
  5. package/dist/{chunk-OLN7VUZA.js → chunk-C2Z42DI5.js} +3 -1
  6. package/dist/{chunk-TCOC4AUI.js → chunk-GX3HSGJX.js} +690 -169
  7. package/dist/{chunk-PNTLA3MN.js → chunk-H2UIHGHH.js} +23 -22
  8. package/dist/{chunk-GY3C3C2Y.js → chunk-L4UREAID.js} +3 -3
  9. package/dist/{chunk-77HDCGTN.js → chunk-MWKE2TNS.js} +1 -1
  10. package/dist/{chunk-7YWS3JLU.js → chunk-NTCB7CMT.js} +3 -1
  11. package/dist/{chunk-6HLKCSB3.js → chunk-OUC75QCF.js} +1 -1
  12. package/dist/{chunk-WAI3WPV2.js → chunk-P4VBLXKS.js} +1 -1
  13. package/dist/{chunk-VW7Y27WW.js → chunk-VGFTM3XT.js} +1 -1
  14. package/dist/{chunk-OQGVGPEK.js → chunk-VHY6NVMQ.js} +2 -0
  15. package/dist/{ci-6ZTFO3LX.js → ci-L6GH2WVC.js} +4 -4
  16. package/dist/{ci-format-CLZ6QJRL.js → ci-format-WW7454AY.js} +2 -2
  17. package/dist/{constants-APSORFOH.js → constants-NCTFSHDU.js} +3 -1
  18. package/dist/{doctor-cli-GUIX4X5F.js → doctor-cli-EWMFBP5Q.js} +4 -4
  19. package/dist/electron-server.js +965 -377
  20. package/dist/{hub-ZGHQWNWE.js → hub-CDL6T7CP.js} +1 -1
  21. package/dist/index.js +259 -97
  22. package/dist/{pr-JOL3IAGV.js → pr-D6PEKEGK.js} +4 -4
  23. package/dist/{run-tests-4PKSIVK5.js → run-tests-NXVVKAK2.js} +1 -1
  24. package/dist/{run-tests-ZRK4TQUN.js → run-tests-SWU2XEV7.js} +2 -2
  25. package/dist/{server-FXUF5P64.js → server-LHYSS6CK.js} +207 -100
  26. package/dist/{server-O3XHT56X.js → server-WUT7VYTD.js} +5 -4
  27. package/dist/{task-orchestrator-TUMDTOAX.js → task-orchestrator-C5AA2BI5.js} +5 -4
  28. package/dist/{usage-X7MJX4YD.js → usage-6ZUUJBI2.js} +2 -2
  29. package/dist/web/client/app.js +53 -2
  30. package/dist/web/client/index.html +11 -1
  31. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  scanProject,
16
16
  sessionHasMeaningfulContent,
17
17
  setupProxy
18
- } from "./chunk-PNTLA3MN.js";
18
+ } from "./chunk-H2UIHGHH.js";
19
19
  import {
20
20
  buildReviewPrompt,
21
21
  buildSecurityReviewPrompt
@@ -23,18 +23,34 @@ import {
23
23
  import {
24
24
  ToolExecutor,
25
25
  ToolRegistry,
26
+ addMemoryEntry,
26
27
  askUserContext,
27
28
  cleanupRejectedTeeFile,
28
29
  clearRecentlyDeniedAutoActions,
30
+ deleteMemoryEntry,
31
+ describePlugin,
29
32
  estimateTokens,
33
+ expireMemoryEntry,
34
+ exportMemoryEntries,
35
+ formatMemoryForPrompt,
30
36
  formatPermissionProfileWarning,
37
+ getActivePluginAssets,
38
+ getInstalledPlugin,
31
39
  getPendingHookTrust,
32
40
  getRecentlyDeniedAutoActions,
33
41
  googleSearchContext,
34
42
  initTheme,
43
+ installPlugin,
35
44
  isInterrupted,
45
+ isMemoryExpired,
36
46
  lastResponseStore,
37
47
  listHooks,
48
+ listInstalledPlugins,
49
+ listMemoryEntries,
50
+ memoryMarkdownPath,
51
+ memoryStorePath,
52
+ mergePluginHooks,
53
+ pluginRoot,
38
54
  renderDiff,
39
55
  requestInterrupt,
40
56
  resetInterrupt,
@@ -42,15 +58,18 @@ import {
42
58
  runLifecycleHooks,
43
59
  setContextWindow,
44
60
  setMaxOutputCap,
61
+ setPluginEnabled,
45
62
  spawnAgentContext,
46
63
  theme,
47
64
  trustHook,
65
+ trustPlugin,
48
66
  undoStack,
49
- untrustHook
50
- } from "./chunk-TCOC4AUI.js";
67
+ untrustHook,
68
+ updateMemoryApproval
69
+ } from "./chunk-GX3HSGJX.js";
51
70
  import "./chunk-T2NL5ZIA.js";
52
71
  import "./chunk-BXP6YZ2P.js";
53
- import "./chunk-VW7Y27WW.js";
72
+ import "./chunk-VGFTM3XT.js";
54
73
  import {
55
74
  SessionManager,
56
75
  getContentText
@@ -67,7 +86,7 @@ import {
67
86
  getConfigDirUsage,
68
87
  listRecentCrashes,
69
88
  writeCrashLog
70
- } from "./chunk-6HLKCSB3.js";
89
+ } from "./chunk-OUC75QCF.js";
71
90
  import {
72
91
  ProviderRegistry
73
92
  } from "./chunk-QMXC327F.js";
@@ -76,7 +95,7 @@ import {
76
95
  getTopFailingTools,
77
96
  getTopUsedTools,
78
97
  installFlushOnExit
79
- } from "./chunk-77HDCGTN.js";
98
+ } from "./chunk-MWKE2TNS.js";
80
99
  import {
81
100
  CONTENT_ONLY_STREAM_REMINDER,
82
101
  TEE_FINAL_USER_NUDGE,
@@ -98,7 +117,7 @@ import {
98
117
  } from "./chunk-XPBEJB27.js";
99
118
  import {
100
119
  ConfigManager
101
- } from "./chunk-WAI3WPV2.js";
120
+ } from "./chunk-P4VBLXKS.js";
102
121
  import {
103
122
  AuthError,
104
123
  ProviderError,
@@ -117,14 +136,13 @@ import {
117
136
  DEV_STATE_FILE_NAME,
118
137
  MCP_PROJECT_CONFIG_NAME,
119
138
  MEMORY_FILE_NAME,
120
- MEMORY_MAX_CHARS,
121
139
  PLAN_MODE_READONLY_TOOLS,
122
140
  PLAN_MODE_SYSTEM_ADDON,
123
141
  REPO_URL,
124
142
  SKILLS_DIR_NAME,
125
143
  VERSION,
126
144
  buildUserIdentityPrompt
127
- } from "./chunk-7YWS3JLU.js";
145
+ } from "./chunk-NTCB7CMT.js";
128
146
  import {
129
147
  formatGitContextForPrompt,
130
148
  getGitContext,
@@ -280,7 +298,7 @@ var ABOUT_FEATURES = [
280
298
  "Sub-agents: spawn_agent delegates complex subtasks to an isolated tool-call loop",
281
299
  "Agent Skills: ~/.aicli/skills/ reusable packs (system prompt + tool whitelist)",
282
300
  "MCP protocol: connect external MCP server tools; aicli mcp-serve reverses aicli into a server",
283
- "Plugin system: ~/.aicli/plugins/*.js custom tools (allowPlugins, off by default)",
301
+ "Plugin packages: skills/hooks/commands/MCP/agents via trusted .aicli-plugin/plugin.json",
284
302
  "Permission rules + hooks: auto-approve/deny/confirm + pre/post tool execution hooks",
285
303
  "User identity (/profile): persistent profile injected into every provider",
286
304
  "Symbol + semantic code index: find_symbol/get_outline/find_references/search_code (8 languages)",
@@ -907,6 +925,7 @@ function createDefaultCommands() {
907
925
  rows: [
908
926
  ["/tools", "List all AI tools available"],
909
927
  ["/plugins", "Show plugin directory and loaded plugins"],
928
+ ["/plugin [list|install|enable|disable|inspect|trust]", "Manage package plugins"],
910
929
  ["/mcp [reconnect [id]]", "MCP servers / reconnect disconnected"],
911
930
  ["/skill [name|off|list]", "Manage agent skills (reusable prompt packs)"],
912
931
  ["/context", "Show or reload hierarchical context layers"],
@@ -1367,7 +1386,7 @@ No commands match "${filter}".
1367
1386
  console.log(" PermProf : " + (ctx.config.get("defaultPermissionProfile") ?? "legacy"));
1368
1387
  console.log(" Network : " + (ctx.config.get("networkPolicy")?.enabled ?? false ? "on" : "off"));
1369
1388
  console.log(" AutoMode : " + (ctx.getToolExecutor().sessionAutoMode ? "on" : "off"));
1370
- console.log(" Hooks : " + getPendingHookTrust(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()).length + " pending trust");
1389
+ console.log(" Hooks : " + getPendingHookTrust(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir()).length + " pending trust");
1371
1390
  console.log(` Messages : ${session?.messages.length ?? 0}`);
1372
1391
  console.log(` Session : ${session?.id.slice(0, 8) ?? "none"}`);
1373
1392
  if (layers.length > 0) {
@@ -1652,6 +1671,91 @@ No tools match "${filter}".
1652
1671
  console.log();
1653
1672
  }
1654
1673
  },
1674
+ {
1675
+ name: "plugin",
1676
+ description: "Manage package plugins (.aicli-plugin/plugin.json)",
1677
+ usage: "/plugin [list|install <path>|enable <name>|disable <name>|inspect <name>|trust <name>]",
1678
+ async execute(args, ctx) {
1679
+ const sub = args[0] ?? "list";
1680
+ const configDir = ctx.config.getConfigDir();
1681
+ try {
1682
+ if (sub === "list") {
1683
+ const plugins = listInstalledPlugins(configDir);
1684
+ console.log(theme.heading("\n\u{1F50C} Package Plugins\n"));
1685
+ console.log(theme.dim(` Dir: ${pluginRoot(configDir)}`));
1686
+ if (plugins.length === 0) {
1687
+ console.log(theme.dim(" No package plugins installed."));
1688
+ console.log(theme.dim(" Use: /plugin install <path-to-plugin-dir>"));
1689
+ } else {
1690
+ for (const p of plugins) {
1691
+ const status = `${p.enabled ? theme.success("enabled") : theme.dim("disabled")} / ${p.trusted ? theme.success("trusted") : theme.warning("untrusted")}`;
1692
+ console.log(` ${theme.accent(p.name.padEnd(22))} ${p.manifest.version.padEnd(10)} ${status}`);
1693
+ if (p.manifest.description) console.log(theme.dim(` ${p.manifest.description}`));
1694
+ if (!p.valid && p.error) console.log(theme.warning(` invalid: ${p.error}`));
1695
+ }
1696
+ }
1697
+ console.log();
1698
+ return;
1699
+ }
1700
+ if (sub === "install") {
1701
+ const input2 = args[1];
1702
+ if (!input2) {
1703
+ ctx.renderer.printInfo("Usage: /plugin install <path-to-plugin-dir>");
1704
+ return;
1705
+ }
1706
+ const plugin = installPlugin(configDir, input2);
1707
+ ctx.renderer.printSuccess(`Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.`);
1708
+ return;
1709
+ }
1710
+ if (sub === "trust") {
1711
+ const name = args[1];
1712
+ if (!name) {
1713
+ ctx.renderer.printInfo("Usage: /plugin trust <name>");
1714
+ return;
1715
+ }
1716
+ const state = trustPlugin(configDir, name);
1717
+ ctx.renderer.printSuccess(`Trusted plugin ${state.name}.`);
1718
+ return;
1719
+ }
1720
+ if (sub === "enable" || sub === "disable") {
1721
+ const name = args[1];
1722
+ if (!name) {
1723
+ ctx.renderer.printInfo(`Usage: /plugin ${sub} <name>`);
1724
+ return;
1725
+ }
1726
+ const state = setPluginEnabled(configDir, name, sub === "enable");
1727
+ ctx.renderer.printSuccess(`${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state.name}. Restart aicli or reload affected managers for changes to take effect.`);
1728
+ return;
1729
+ }
1730
+ if (sub === "inspect") {
1731
+ const name = args[1];
1732
+ if (!name) {
1733
+ ctx.renderer.printInfo("Usage: /plugin inspect <name>");
1734
+ return;
1735
+ }
1736
+ const plugin = getInstalledPlugin(configDir, name);
1737
+ console.log();
1738
+ for (const line of describePlugin(plugin)) console.log(line);
1739
+ console.log();
1740
+ return;
1741
+ }
1742
+ if (sub === "active") {
1743
+ const assets = getActivePluginAssets(configDir);
1744
+ console.log(JSON.stringify({
1745
+ plugins: assets.plugins.map((p) => p.name),
1746
+ skillDirs: assets.skillDirs,
1747
+ commandDirs: assets.commandDirs,
1748
+ agentDirs: assets.agentDirs,
1749
+ mcpServers: Object.keys(assets.mcpServers)
1750
+ }, null, 2));
1751
+ return;
1752
+ }
1753
+ ctx.renderer.printInfo("Usage: /plugin [list|install|enable|disable|inspect|trust]");
1754
+ } catch (err) {
1755
+ ctx.renderer.renderError(err instanceof Error ? err.message : String(err));
1756
+ }
1757
+ }
1758
+ },
1655
1759
  {
1656
1760
  name: "mcp",
1657
1761
  description: "Show MCP server connections and tools",
@@ -1661,7 +1765,7 @@ No tools match "${filter}".
1661
1765
  const { join: join5 } = await import("path");
1662
1766
  const { existsSync: existsSync5 } = await import("fs");
1663
1767
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1664
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-APSORFOH.js");
1768
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-NCTFSHDU.js");
1665
1769
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1666
1770
  const cwd = process.cwd();
1667
1771
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -1970,7 +2074,7 @@ No tools match "${filter}".
1970
2074
  return;
1971
2075
  }
1972
2076
  if (sub === "reload") {
1973
- const count = manager.loadSkills();
2077
+ const count = manager.loadSkills(getActivePluginAssets(ctx.config.getConfigDir()).skillDirs);
1974
2078
  ctx.renderer.printSuccess(`Reloaded: ${count} skill(s) found.`);
1975
2079
  return;
1976
2080
  }
@@ -2697,7 +2801,7 @@ ${hint}` : "")
2697
2801
  }
2698
2802
  const sub = args[0]?.toLowerCase();
2699
2803
  if (sub === "reload") {
2700
- const count = mgr.loadCommands();
2804
+ const count = mgr.loadCommands(getActivePluginAssets(ctx.config.getConfigDir()).commandDirs);
2701
2805
  console.log(theme.success(` \u2713 Reloaded ${count} custom command(s).`));
2702
2806
  return;
2703
2807
  }
@@ -2720,7 +2824,7 @@ ${hint}` : "")
2720
2824
  usage: "/test [command|filter]",
2721
2825
  async execute(args, ctx) {
2722
2826
  try {
2723
- const { executeTests } = await import("./run-tests-ZRK4TQUN.js");
2827
+ const { executeTests } = await import("./run-tests-SWU2XEV7.js");
2724
2828
  const argStr = args.join(" ").trim();
2725
2829
  let testArgs = {};
2726
2830
  if (argStr) {
@@ -2875,10 +2979,10 @@ ${hint}` : "")
2875
2979
  // ── /memory ────────────────────────────────────────────────────────────────
2876
2980
  {
2877
2981
  name: "memory",
2878
- description: "Persistent memory (memory.md) + chat memory recall index (v0.4.89+)",
2879
- usage: "/memory [show|add <text>|clear|path|rebuild|refresh|status|recall <query>|index-clear]",
2982
+ description: "Governed persistent memory + chat memory recall index",
2983
+ usage: "/memory [show|add|approve|reject|delete|expire|export|path|rebuild|refresh|status|recall|index-clear]",
2880
2984
  async execute(args, ctx) {
2881
- const memoryFile = join2(ctx.config.getConfigDir(), MEMORY_FILE_NAME);
2985
+ const configDir = ctx.config.getConfigDir();
2882
2986
  const sub = args[0] ?? "show";
2883
2987
  if (sub === "rebuild" || sub === "refresh") {
2884
2988
  const full = sub === "rebuild";
@@ -2912,6 +3016,13 @@ ${hint}` : "")
2912
3016
  }
2913
3017
  if (sub === "status") {
2914
3018
  const s = getChatIndexStatus();
3019
+ const memories = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3020
+ const active = memories.filter((m) => m.approved && !isMemoryExpired(m)).length;
3021
+ const pending = memories.filter((m) => !m.approved && !isMemoryExpired(m)).length;
3022
+ console.log(theme.heading("\n\u{1F9E0} Governed Memory"));
3023
+ console.log(` active : ${active}`);
3024
+ console.log(` pending : ${pending}`);
3025
+ console.log(` store : ${memoryStorePath(configDir)}`);
2915
3026
  console.log(theme.heading("\n\u{1F9E0} Chat Memory Index"));
2916
3027
  if (!s.exists) {
2917
3028
  console.log(theme.dim(" not built yet \u2014 run /memory rebuild\n"));
@@ -2958,60 +3069,116 @@ ${hint}` : "")
2958
3069
  }
2959
3070
  if (sub === "index-clear") {
2960
3071
  clearChatIndex();
2961
- ctx.renderer.printSuccess("Chat memory index cleared (memory.md untouched).");
3072
+ ctx.renderer.printSuccess("Chat memory index cleared (persistent memory untouched).");
2962
3073
  return;
2963
3074
  }
2964
- if (sub === "show" || sub === "view") {
2965
- if (!existsSync2(memoryFile)) {
2966
- ctx.renderer.printInfo("Memory is empty (memory.md not found)");
2967
- return;
3075
+ const parseFlags = (items) => {
3076
+ const flags = {};
3077
+ const text = [];
3078
+ for (let i = 0; i < items.length; i++) {
3079
+ const item = items[i];
3080
+ if (item.startsWith("--")) {
3081
+ const key = item.slice(2);
3082
+ const next = items[i + 1];
3083
+ if (next && !next.startsWith("--")) {
3084
+ flags[key] = next;
3085
+ i++;
3086
+ } else {
3087
+ flags[key] = true;
3088
+ }
3089
+ } else {
3090
+ text.push(item);
3091
+ }
2968
3092
  }
2969
- const content = readFileSync(memoryFile, "utf-8").trim();
2970
- if (!content) {
3093
+ return { flags, text: text.join(" ").trim() };
3094
+ };
3095
+ const asScope = (value) => value === "personal" || value === "project" || value === "session" || value === "team" ? value : void 0;
3096
+ const asSensitivity = (value) => value === "low" || value === "medium" || value === "high" ? value : void 0;
3097
+ if (sub === "show" || sub === "view" || sub === "list") {
3098
+ const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
3099
+ if (entries.length === 0) {
2971
3100
  ctx.renderer.printInfo("Memory is empty");
2972
3101
  return;
2973
3102
  }
2974
- const lines = content.split("\n").length;
2975
- console.log(theme.heading("\n\u{1F4DD} Persistent Memory:\n") + theme.dim("\u2500".repeat(60)));
2976
- console.log(chalk2.white(content));
2977
- console.log(theme.dim("\u2500".repeat(60)));
2978
- console.log(theme.dim(` ${content.length} chars \xB7 ${lines} lines
3103
+ console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:\n") + theme.dim("\u2500".repeat(72)));
3104
+ for (const entry of entries) {
3105
+ const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
3106
+ const source = entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : entry.sourceProject ? `project ${entry.sourceProject}` : "manual/tool";
3107
+ console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
3108
+ console.log(theme.dim(` source: ${source} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
3109
+ console.log(" " + entry.content.replace(/\n/g, "\n "));
3110
+ console.log();
3111
+ }
3112
+ console.log(theme.dim("\u2500".repeat(72)));
3113
+ console.log(theme.dim(` store: ${memoryStorePath(configDir)}
2979
3114
  `));
2980
3115
  } else if (sub === "add") {
2981
- const text = args.slice(1).join(" ").trim();
2982
- if (!text) {
2983
- ctx.renderer.printInfo("Usage: /memory add <text>");
3116
+ const parsed = parseFlags(args.slice(1));
3117
+ if (!parsed.text) {
3118
+ ctx.renderer.printInfo("Usage: /memory add [--scope personal|project|session|team] [--sensitivity low|medium|high] [--expires-at ISO] <text>");
2984
3119
  return;
2985
3120
  }
2986
- const now = /* @__PURE__ */ new Date();
2987
- const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
2988
- const entry = `
2989
- ## ${dateStr}
2990
- ${text}
2991
- `;
2992
3121
  try {
2993
- mkdirSync2(dirname2(memoryFile), { recursive: true });
2994
- const previous = existsSync2(memoryFile) ? readFileSync(memoryFile, "utf-8") : "";
2995
- atomicWriteFileSync(memoryFile, previous + entry);
2996
- ctx.renderer.printSuccess(`Added to memory: "${text.slice(0, 60)}${text.length > 60 ? "\u2026" : ""}"`);
3122
+ const entry = addMemoryEntry(configDir, parsed.text, {
3123
+ scope: asScope(parsed.flags.scope) ?? "personal",
3124
+ sensitivity: asSensitivity(parsed.flags.sensitivity),
3125
+ expiresAt: typeof parsed.flags["expires-at"] === "string" ? parsed.flags["expires-at"] : void 0,
3126
+ approved: parsed.flags.pending === true ? false : void 0,
3127
+ cwd: process.cwd(),
3128
+ redact: true
3129
+ });
3130
+ const status = entry.approved ? "approved" : "pending approval";
3131
+ ctx.renderer.printSuccess(`Added memory ${entry.id.slice(0, 8)} (${status}, scope:${entry.scope}, sensitivity:${entry.sensitivity})`);
2997
3132
  } catch (err) {
2998
3133
  ctx.renderer.renderError(`Failed to write memory: ${err instanceof Error ? err.message : String(err)}`);
2999
3134
  }
3000
- } else if (sub === "clear") {
3001
- if (!existsSync2(memoryFile)) {
3002
- ctx.renderer.printInfo("Memory is already empty");
3135
+ } else if (sub === "approve" || sub === "reject") {
3136
+ const id = args[1];
3137
+ if (!id) {
3138
+ ctx.renderer.printInfo(`Usage: /memory ${sub} <id>`);
3139
+ return;
3140
+ }
3141
+ try {
3142
+ const entry = updateMemoryApproval(configDir, id, sub === "approve");
3143
+ ctx.renderer.printSuccess(`${sub === "approve" ? "Approved" : "Rejected"} memory ${entry.id.slice(0, 8)}`);
3144
+ } catch (err) {
3145
+ ctx.renderer.renderError(err instanceof Error ? err.message : String(err));
3146
+ }
3147
+ } else if (sub === "delete") {
3148
+ const id = args[1];
3149
+ if (!id) {
3150
+ ctx.renderer.printInfo("Usage: /memory delete <id>");
3151
+ return;
3152
+ }
3153
+ try {
3154
+ const entry = deleteMemoryEntry(configDir, id);
3155
+ ctx.renderer.printSuccess(`Deleted memory ${entry.id.slice(0, 8)}`);
3156
+ } catch (err) {
3157
+ ctx.renderer.renderError(err instanceof Error ? err.message : String(err));
3158
+ }
3159
+ } else if (sub === "expire") {
3160
+ const id = args[1];
3161
+ if (!id) {
3162
+ ctx.renderer.printInfo("Usage: /memory expire <id> [ISO timestamp]");
3003
3163
  return;
3004
3164
  }
3005
3165
  try {
3006
- atomicWriteFileSync(memoryFile, "");
3007
- ctx.renderer.printSuccess("Memory cleared (memory.md emptied)");
3166
+ const entry = expireMemoryEntry(configDir, id, args[2]);
3167
+ ctx.renderer.printSuccess(`Expired memory ${entry.id.slice(0, 8)}`);
3008
3168
  } catch (err) {
3009
- ctx.renderer.renderError(`Failed to clear memory: ${err instanceof Error ? err.message : String(err)}`);
3169
+ ctx.renderer.renderError(err instanceof Error ? err.message : String(err));
3010
3170
  }
3171
+ } else if (sub === "export") {
3172
+ console.log(exportMemoryEntries(configDir, process.cwd()));
3173
+ } else if (sub === "clear") {
3174
+ atomicWriteFileSync(memoryStorePath(configDir), "");
3175
+ atomicWriteFileSync(memoryMarkdownPath(configDir), "");
3176
+ ctx.renderer.printSuccess("Memory cleared (memory.jsonl and memory.md emptied)");
3011
3177
  } else if (sub === "path") {
3012
- console.log(theme.accent(memoryFile));
3178
+ console.log(theme.accent(memoryStorePath(configDir)));
3179
+ console.log(theme.dim(memoryMarkdownPath(configDir)));
3013
3180
  } else {
3014
- ctx.renderer.printInfo("Usage: /memory [show|add <text>|clear|path]");
3181
+ ctx.renderer.printInfo("Usage: /memory [show|add|approve|reject|delete|expire|export|path|rebuild|refresh|status|recall|index-clear]");
3015
3182
  }
3016
3183
  }
3017
3184
  },
@@ -3305,7 +3472,7 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3305
3472
  usage: "/hooks [list|inspect <id>|trust <id>|untrust <id>|disable]",
3306
3473
  execute(args, ctx) {
3307
3474
  const sub = (args[0] || "list").toLowerCase();
3308
- const hooks = listHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir());
3475
+ const hooks = listHooks(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir());
3309
3476
  const findHook = (id) => hooks.find((h) => h.id === id || h.hash.startsWith(id ?? ""));
3310
3477
  if (sub === "list") {
3311
3478
  console.log(theme.heading(" Hooks"));
@@ -3384,7 +3551,7 @@ Summary: ${fileMap.size} file(s) \u2014 ${newFiles} new, ${modifiedFiles} modifi
3384
3551
  console.log(" profile : " + (ctx.config.get("defaultPermissionProfile") ?? "legacy"));
3385
3552
  console.log(" network : " + (ctx.config.get("networkPolicy")?.enabled ?? false ? "on" : "off"));
3386
3553
  console.log(" autoMode : " + (ctx.getToolExecutor().sessionAutoMode ? "on" : "off"));
3387
- console.log(" hooks : " + getPendingHookTrust(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()).length + " pending trust");
3554
+ console.log(" hooks : " + getPendingHookTrust(mergePluginHooks(ctx.config.get("hooks") ?? void 0, ctx.config.getConfigDir()), ctx.config.getConfigDir()).length + " pending trust");
3388
3555
  console.log(` patterns : ${DEFAULT_PATTERNS.length} built-in + ${security.customPatterns?.length ?? 0} custom`);
3389
3556
  console.log(` last save : ${ctx.sessions.lastRedactionHits} hit(s) redacted`);
3390
3557
  console.log();
@@ -4205,22 +4372,22 @@ var CustomCommandManager = class {
4205
4372
  this.commandsDir = commandsDir;
4206
4373
  }
4207
4374
  commands = /* @__PURE__ */ new Map();
4208
- loadCommands() {
4375
+ loadCommands(extraDirs = []) {
4209
4376
  this.commands.clear();
4210
- if (!existsSync3(this.commandsDir)) {
4211
- mkdirSync3(this.commandsDir, { recursive: true });
4212
- return 0;
4377
+ this.loadCommandDir(this.commandsDir, true);
4378
+ for (const dir of extraDirs) this.loadCommandDir(dir, false);
4379
+ return this.commands.size;
4380
+ }
4381
+ loadCommandDir(dir, createIfMissing) {
4382
+ if (!existsSync3(dir)) {
4383
+ if (createIfMissing) mkdirSync3(dir, { recursive: true });
4384
+ return;
4213
4385
  }
4214
- let count = 0;
4215
- for (const file of readdirSync(this.commandsDir)) {
4386
+ for (const file of readdirSync(dir)) {
4216
4387
  if (extname(file) !== ".md") continue;
4217
- const cmd = parseCommandFile(join3(this.commandsDir, file));
4218
- if (cmd) {
4219
- this.commands.set(cmd.meta.name, cmd);
4220
- count++;
4221
- }
4388
+ const cmd = parseCommandFile(join3(dir, file));
4389
+ if (cmd && !this.commands.has(cmd.meta.name)) this.commands.set(cmd.meta.name, cmd);
4222
4390
  }
4223
- return count;
4224
4391
  }
4225
4392
  listCommands() {
4226
4393
  return [...this.commands.values()];
@@ -4598,7 +4765,7 @@ var Repl = class {
4598
4765
  const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
4599
4766
  const permissionProfiles = this.config.get("permissionProfiles") ?? {};
4600
4767
  this.toolExecutor.setConfig({
4601
- hookConfig: this.config.get("hooks") ?? void 0,
4768
+ hookConfig: mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
4602
4769
  hookConfigDir: this.config.getConfigDir(),
4603
4770
  permissionRules: this.config.get("permissionRules") ?? [],
4604
4771
  defaultPermission: this.config.get("defaultPermission") ?? "confirm",
@@ -4608,11 +4775,12 @@ var Repl = class {
4608
4775
  workspaceRoot: process.cwd(),
4609
4776
  networkPolicy: this.config.get("networkPolicy")
4610
4777
  });
4611
- const pendingHooks = getPendingHookTrust(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
4778
+ const effectiveHooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
4779
+ const pendingHooks = getPendingHookTrust(effectiveHooks, this.config.getConfigDir());
4612
4780
  if (pendingHooks.length > 0) {
4613
4781
  process.stderr.write(theme.warning(" \u26A0 " + pendingHooks.length + " project hook(s) require trust. Run /hooks list and /hooks trust <id>.\n"));
4614
4782
  }
4615
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "SessionStart", { source: "repl" }, { configDir: this.config.getConfigDir() });
4783
+ runLifecycleHooks(effectiveHooks, "SessionStart", { source: "repl" }, { configDir: this.config.getConfigDir() });
4616
4784
  if (this.config.get("alwaysYolo") === true) {
4617
4785
  this.toolExecutor.sessionAutoApprove = true;
4618
4786
  }
@@ -4945,19 +5113,7 @@ ${content}
4945
5113
  return { layers: result.layers, mergedContent: result.mergedContent };
4946
5114
  }
4947
5115
  loadMemoryContent() {
4948
- const memoryPath = join4(this.config.getConfigDir(), MEMORY_FILE_NAME);
4949
- if (!existsSync4(memoryPath)) return null;
4950
- let content = readFileSync3(memoryPath, "utf-8").trim();
4951
- if (!content) return null;
4952
- if (content.length > MEMORY_MAX_CHARS) {
4953
- content = content.slice(-MEMORY_MAX_CHARS);
4954
- const firstEntry = content.indexOf("\n## ");
4955
- if (firstEntry !== -1) {
4956
- content = content.slice(firstEntry + 1);
4957
- }
4958
- }
4959
- const entryCount = (content.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? []).length;
4960
- return { content, entryCount };
5116
+ return formatMemoryForPrompt(this.config.getConfigDir(), process.cwd());
4961
5117
  }
4962
5118
  /**
4963
5119
  * 将项目上下文内容合并到 system prompt。
@@ -5085,7 +5241,7 @@ ${skillContent}`);
5085
5241
  return false;
5086
5242
  }
5087
5243
  const preDecisions = runLifecycleHooks(
5088
- this.config.get("hooks") ?? void 0,
5244
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5089
5245
  "PreCompact",
5090
5246
  { sessionId: session.id, messageCount: session.messages.length, instruction },
5091
5247
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -5161,7 +5317,7 @@ ${response.content.trim()}
5161
5317
  const removedCount = session.compact(summaryMsg, ackMsg, COMPACT_KEEP_LAST);
5162
5318
  const afterCount = session.messages.length;
5163
5319
  runLifecycleHooks(
5164
- this.config.get("hooks") ?? void 0,
5320
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5165
5321
  "PostCompact",
5166
5322
  { sessionId: session.id, beforeCount, afterCount, removedCount, keepLast: COMPACT_KEEP_LAST, instruction },
5167
5323
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -5363,14 +5519,15 @@ Session '${this.resumeSessionId}' not found.
5363
5519
  }
5364
5520
  const skillsDir = join4(this.config.getConfigDir(), SKILLS_DIR_NAME);
5365
5521
  this.skillManager = new SkillManager(skillsDir, this.config.get("ui").skillSizeWarn);
5366
- const skillCount = this.skillManager.loadSkills();
5522
+ const pluginAssets = getActivePluginAssets(this.config.getConfigDir());
5523
+ const skillCount = this.skillManager.loadSkills(pluginAssets.skillDirs);
5367
5524
  if (skillCount > 0) {
5368
5525
  process.stdout.write(theme.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
5369
5526
  `));
5370
5527
  }
5371
5528
  const commandsDir = join4(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
5372
5529
  this.customCommandManager = new CustomCommandManager(commandsDir);
5373
- const customCmdCount = this.customCommandManager.loadCommands();
5530
+ const customCmdCount = this.customCommandManager.loadCommands(pluginAssets.commandDirs);
5374
5531
  if (customCmdCount > 0) {
5375
5532
  process.stdout.write(theme.dim(` \u{1F4CB} Custom commands: ${customCmdCount} loaded (use /commands to list)
5376
5533
  `));
@@ -5426,6 +5583,7 @@ Session '${this.resumeSessionId}' not found.
5426
5583
  })();
5427
5584
  const mcpEnabled = this.config.get("mcpEnabled") !== false;
5428
5585
  const globalMcpServers = mcpEnabled ? this.config.get("mcpServers") ?? {} : {};
5586
+ const pluginMcpServers = mcpEnabled ? getActivePluginAssets(this.config.getConfigDir()).mcpServers : {};
5429
5587
  const projectMcpResult = mcpEnabled ? this.loadProjectMcpConfig() : null;
5430
5588
  if (!mcpEnabled) {
5431
5589
  process.stdout.write(theme.dim(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)\n"));
@@ -5447,10 +5605,13 @@ Session '${this.resumeSessionId}' not found.
5447
5605
  );
5448
5606
  }
5449
5607
  }
5450
- const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
5608
+ const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
5451
5609
  for (const id of Object.keys(globalMcpServers)) {
5452
5610
  this.mcpServerSources.set(id, "global");
5453
5611
  }
5612
+ for (const id of Object.keys(pluginMcpServers)) {
5613
+ this.mcpServerSources.set(id, "plugin");
5614
+ }
5454
5615
  for (const id of Object.keys(projectMcpServers)) {
5455
5616
  this.mcpServerSources.set(id, "project");
5456
5617
  }
@@ -5458,6 +5619,7 @@ Session '${this.resumeSessionId}' not found.
5458
5619
  if (Object.keys(mergedMcpServers).length > 0) {
5459
5620
  this.mcpManager = new McpManager();
5460
5621
  const mcpProjectCount = Object.keys(projectMcpServers).length;
5622
+ const mcpPluginCount = Object.keys(pluginMcpServers).length;
5461
5623
  let bannerEmitted = false;
5462
5624
  const emitBanner = (afterDeadline) => {
5463
5625
  if (bannerEmitted) return;
@@ -5603,7 +5765,7 @@ Session '${this.resumeSessionId}' not found.
5603
5765
  }
5604
5766
  runUserPromptHooks(sessionId, userInput) {
5605
5767
  const promptDecisions = runLifecycleHooks(
5606
- this.config.get("hooks") ?? void 0,
5768
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
5607
5769
  "UserPromptSubmit",
5608
5770
  { sessionId, prompt: userInput },
5609
5771
  { configDir: this.config.getConfigDir(), onSummary: (msg) => process.stdout.write(theme.dim(" [hook] " + msg) + "\n") }
@@ -7092,7 +7254,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
7092
7254
  void this.shutdownAsync();
7093
7255
  }
7094
7256
  async shutdownAsync() {
7095
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
7257
+ runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
7096
7258
  this.running = false;
7097
7259
  const sessionId = this.sessions.current?.id;
7098
7260
  if (sessionId) {
@@ -7207,7 +7369,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7207
7369
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7208
7370
  process.exit(1);
7209
7371
  }
7210
- const { startWebServer } = await import("./server-FXUF5P64.js");
7372
+ const { startWebServer } = await import("./server-LHYSS6CK.js");
7211
7373
  await startWebServer({ port, host: options.host });
7212
7374
  });
7213
7375
  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) => {
@@ -7374,16 +7536,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7374
7536
  console.log(footer + "\n");
7375
7537
  });
7376
7538
  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) => {
7377
- const { runUsageCli } = await import("./usage-X7MJX4YD.js");
7539
+ const { runUsageCli } = await import("./usage-6ZUUJBI2.js");
7378
7540
  await runUsageCli(options);
7379
7541
  });
7380
7542
  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) => {
7381
- const { runDoctorCli } = await import("./doctor-cli-GUIX4X5F.js");
7543
+ const { runDoctorCli } = await import("./doctor-cli-EWMFBP5Q.js");
7382
7544
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7383
7545
  });
7384
7546
  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) => {
7385
7547
  try {
7386
- const batch = await import("./batch-C6HCXZIG.js");
7548
+ const batch = await import("./batch-7V7OTMUP.js");
7387
7549
  switch (action) {
7388
7550
  case "submit":
7389
7551
  if (!arg) {
@@ -7426,7 +7588,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7426
7588
  }
7427
7589
  });
7428
7590
  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) => {
7429
- const { startMcpServer } = await import("./server-O3XHT56X.js");
7591
+ const { startMcpServer } = await import("./server-WUT7VYTD.js");
7430
7592
  await startMcpServer({
7431
7593
  allowDestructive: !!options.allowDestructive,
7432
7594
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7439,7 +7601,7 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7439
7601
  console.error(`Unknown pr action: ${action}. Use review | security-review | summarize.`);
7440
7602
  process.exit(1);
7441
7603
  }
7442
- const { runPrCli } = await import("./pr-JOL3IAGV.js");
7604
+ const { runPrCli } = await import("./pr-D6PEKEGK.js");
7443
7605
  const result = await runPrCli({
7444
7606
  action,
7445
7607
  target,
@@ -7459,8 +7621,8 @@ program.command("pr <action> [target]").description("PR review helpers: review |
7459
7621
  process.exit(result.exitCode);
7460
7622
  });
7461
7623
  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) => {
7462
- const { runCi } = await import("./ci-6ZTFO3LX.js");
7463
- const { formatCiResult } = await import("./ci-format-CLZ6QJRL.js");
7624
+ const { runCi } = await import("./ci-L6GH2WVC.js");
7625
+ const { formatCiResult } = await import("./ci-format-WW7454AY.js");
7464
7626
  const formats = [options.json, options.markdown, options.sarif].filter(Boolean).length;
7465
7627
  if (formats > 1) {
7466
7628
  console.error("Choose only one output format: --json, --markdown, or --sarif.");
@@ -7617,7 +7779,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7617
7779
  }),
7618
7780
  config.get("customProviders")
7619
7781
  );
7620
- const { startHub } = await import("./hub-ZGHQWNWE.js");
7782
+ const { startHub } = await import("./hub-CDL6T7CP.js");
7621
7783
  await startHub(
7622
7784
  {
7623
7785
  topic: topic ?? "",