jinzd-ai-cli 0.4.217 → 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 (27) hide show
  1. package/README.md +4 -2
  2. package/dist/{batch-YCOVKTXD.js → batch-7V7OTMUP.js} +2 -2
  3. package/dist/{chunk-WZ3VKLF3.js → chunk-5CA2TJ5F.js} +1 -1
  4. package/dist/{chunk-QAYOI57M.js → chunk-C2Z42DI5.js} +1 -1
  5. package/dist/{chunk-MGBMNCHG.js → chunk-GX3HSGJX.js} +452 -156
  6. package/dist/{chunk-OUAZOE5U.js → chunk-H2UIHGHH.js} +23 -22
  7. package/dist/{chunk-KOPUCJXM.js → chunk-L4UREAID.js} +3 -3
  8. package/dist/{chunk-SNJAOXFT.js → chunk-MWKE2TNS.js} +1 -1
  9. package/dist/{chunk-XJGEQIYS.js → chunk-NTCB7CMT.js} +1 -1
  10. package/dist/{chunk-524WZOKS.js → chunk-OUC75QCF.js} +1 -1
  11. package/dist/{chunk-WKOQ5CYC.js → chunk-P4VBLXKS.js} +1 -1
  12. package/dist/{chunk-VTH7BLXK.js → chunk-VGFTM3XT.js} +1 -1
  13. package/dist/{ci-52RZIYWB.js → ci-L6GH2WVC.js} +4 -4
  14. package/dist/{ci-format-73UXKE65.js → ci-format-WW7454AY.js} +2 -2
  15. package/dist/{constants-DIXAD35W.js → constants-NCTFSHDU.js} +1 -1
  16. package/dist/{doctor-cli-7GOQPULZ.js → doctor-cli-EWMFBP5Q.js} +4 -4
  17. package/dist/electron-server.js +575 -231
  18. package/dist/{hub-GIGBITZN.js → hub-CDL6T7CP.js} +1 -1
  19. package/dist/index.js +148 -46
  20. package/dist/{pr-KPQ5RPKB.js → pr-D6PEKEGK.js} +4 -4
  21. package/dist/{run-tests-K7QR5QN4.js → run-tests-NXVVKAK2.js} +1 -1
  22. package/dist/{run-tests-ZDSA3QES.js → run-tests-SWU2XEV7.js} +2 -2
  23. package/dist/{server-QT3SC2KI.js → server-LHYSS6CK.js} +85 -21
  24. package/dist/{server-22YF3U34.js → server-WUT7VYTD.js} +4 -4
  25. package/dist/{task-orchestrator-BHQQCVTY.js → task-orchestrator-C5AA2BI5.js} +4 -4
  26. package/dist/{usage-WZZFSFLM.js → usage-6ZUUJBI2.js} +2 -2
  27. package/package.json +1 -1
@@ -38,7 +38,7 @@ import {
38
38
  VERSION,
39
39
  buildUserIdentityPrompt,
40
40
  runTestsTool
41
- } from "./chunk-QAYOI57M.js";
41
+ } from "./chunk-C2Z42DI5.js";
42
42
  import {
43
43
  hasSemanticIndex,
44
44
  semanticSearch
@@ -64,8 +64,8 @@ import {
64
64
  import express from "express";
65
65
  import { createServer } from "http";
66
66
  import { WebSocketServer } from "ws";
67
- import { join as join22, dirname as dirname7, resolve as resolve10, relative as relative4, sep as sep3 } from "path";
68
- import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync13, statSync as statSync9, realpathSync } from "fs";
67
+ import { join as join23, dirname as dirname8, resolve as resolve11, relative as relative4, sep as sep3 } from "path";
68
+ import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync14, statSync as statSync10, realpathSync } from "fs";
69
69
  import { networkInterfaces } from "os";
70
70
 
71
71
  // src/config/config-manager.ts
@@ -1847,12 +1847,12 @@ function findPhantomClaims(content, extraMessages) {
1847
1847
  const claimed = extractClaimedFilePaths(content);
1848
1848
  if (claimed.length === 0) return [];
1849
1849
  const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
1850
- const basename6 = (p) => {
1850
+ const basename7 = (p) => {
1851
1851
  const parts = normalize(p).split("/");
1852
1852
  return parts[parts.length - 1] ?? "";
1853
1853
  };
1854
1854
  const written = extractWrittenFilePaths(extraMessages).map(normalize);
1855
- const writtenBases = new Set(written.map(basename6));
1855
+ const writtenBases = new Set(written.map(basename7));
1856
1856
  const writtenFull = new Set(written);
1857
1857
  const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
1858
1858
  return claimed.filter((raw) => {
@@ -1861,8 +1861,8 @@ function findPhantomClaims(content, extraMessages) {
1861
1861
  for (const w of writtenFull) {
1862
1862
  if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
1863
1863
  }
1864
- if (writtenBases.has(basename6(norm2))) return false;
1865
- const base = basename6(norm2);
1864
+ if (writtenBases.has(basename7(norm2))) return false;
1865
+ const base = basename7(norm2);
1866
1866
  if (base && bashText.includes(base)) return false;
1867
1867
  return true;
1868
1868
  });
@@ -7182,7 +7182,7 @@ var ToolExecutor = class {
7182
7182
  rl.resume();
7183
7183
  process.stdout.write(prompt);
7184
7184
  this.confirming = true;
7185
- return new Promise((resolve11) => {
7185
+ return new Promise((resolve12) => {
7186
7186
  let completed = false;
7187
7187
  const cleanup = (result) => {
7188
7188
  if (completed) return;
@@ -7192,7 +7192,7 @@ var ToolExecutor = class {
7192
7192
  rl.pause();
7193
7193
  rlAny.output = savedOutput;
7194
7194
  this.confirming = false;
7195
- resolve11(result);
7195
+ resolve12(result);
7196
7196
  };
7197
7197
  const onLine = (line) => {
7198
7198
  const trimmed = line.trim();
@@ -7366,7 +7366,7 @@ var ToolExecutor = class {
7366
7366
  rl.resume();
7367
7367
  process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
7368
7368
  this.confirming = true;
7369
- return new Promise((resolve11) => {
7369
+ return new Promise((resolve12) => {
7370
7370
  let completed = false;
7371
7371
  const cleanup = (answer) => {
7372
7372
  if (completed) return;
@@ -7376,7 +7376,7 @@ var ToolExecutor = class {
7376
7376
  rl.pause();
7377
7377
  rlAny.output = savedOutput;
7378
7378
  this.confirming = false;
7379
- resolve11(answer === "y");
7379
+ resolve12(answer === "y");
7380
7380
  };
7381
7381
  const onLine = (line) => {
7382
7382
  const trimmed = line.trim();
@@ -8625,7 +8625,7 @@ var runInteractiveTool = {
8625
8625
  PYTHONDONTWRITEBYTECODE: "1"
8626
8626
  };
8627
8627
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
8628
- return new Promise((resolve11) => {
8628
+ return new Promise((resolve12) => {
8629
8629
  const child = spawn2(executable, cmdArgs.map(String), {
8630
8630
  cwd: process.cwd(),
8631
8631
  env,
@@ -8658,22 +8658,22 @@ var runInteractiveTool = {
8658
8658
  setTimeout(writeNextLine, 400);
8659
8659
  const timer = setTimeout(() => {
8660
8660
  child.kill();
8661
- resolve11(`${prefixWarnings}[Timeout after ${timeout}ms]
8661
+ resolve12(`${prefixWarnings}[Timeout after ${timeout}ms]
8662
8662
  ${buildOutput(stdout, stderr)}`);
8663
8663
  }, timeout);
8664
8664
  child.on("close", (code) => {
8665
8665
  clearTimeout(timer);
8666
8666
  const output = buildOutput(stdout, stderr);
8667
8667
  if (code !== 0 && code !== null) {
8668
- resolve11(`${prefixWarnings}Exit code ${code}:
8668
+ resolve12(`${prefixWarnings}Exit code ${code}:
8669
8669
  ${output}`);
8670
8670
  } else {
8671
- resolve11(`${prefixWarnings}${output || "(no output)"}`);
8671
+ resolve12(`${prefixWarnings}${output || "(no output)"}`);
8672
8672
  }
8673
8673
  });
8674
8674
  child.on("error", (err) => {
8675
8675
  clearTimeout(timer);
8676
- resolve11(
8676
+ resolve12(
8677
8677
  `${prefixWarnings}Failed to start process "${executable}": ${err.message}
8678
8678
  Hint: On Windows, use the full path to the executable, e.g.:
8679
8679
  C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
@@ -9723,7 +9723,7 @@ function promptUser(rl, question) {
9723
9723
  console.log();
9724
9724
  console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
9725
9725
  process.stdout.write(chalk4.cyan("> "));
9726
- return new Promise((resolve11) => {
9726
+ return new Promise((resolve12) => {
9727
9727
  let completed = false;
9728
9728
  const cleanup = (answer) => {
9729
9729
  if (completed) return;
@@ -9733,7 +9733,7 @@ function promptUser(rl, question) {
9733
9733
  rl.pause();
9734
9734
  rlAny.output = savedOutput;
9735
9735
  askUserContext.prompting = false;
9736
- resolve11(answer);
9736
+ resolve12(answer);
9737
9737
  };
9738
9738
  const onLine = (line) => {
9739
9739
  cleanup(line);
@@ -9965,9 +9965,292 @@ function formatResults2(query, data, _requested) {
9965
9965
  }
9966
9966
 
9967
9967
  // src/agents/agent-config.ts
9968
- import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
9969
- import { join as join12 } from "path";
9968
+ import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13 } from "fs";
9969
+ import { join as join13 } from "path";
9970
9970
  import { homedir as homedir6 } from "os";
9971
+
9972
+ // src/plugins/plugin-manager.ts
9973
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync12, cpSync, rmSync, statSync as statSync6 } from "fs";
9974
+ import { basename as basename5, dirname as dirname6, join as join12, resolve as resolve7 } from "path";
9975
+ import { createHash as createHash3 } from "crypto";
9976
+ import { z as z2 } from "zod";
9977
+ var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
9978
+ var STATE_FILE = "plugin-state.json";
9979
+ var HookCommandSchema = z2.union([
9980
+ z2.string(),
9981
+ z2.object({
9982
+ command: z2.string(),
9983
+ source: z2.enum(["user", "project", "managed"]).optional(),
9984
+ description: z2.string().optional(),
9985
+ required: z2.boolean().optional(),
9986
+ timeoutMs: z2.number().int().min(100).max(3e4).optional(),
9987
+ disabled: z2.boolean().optional()
9988
+ }),
9989
+ z2.array(z2.union([
9990
+ z2.string(),
9991
+ z2.object({
9992
+ command: z2.string(),
9993
+ source: z2.enum(["user", "project", "managed"]).optional(),
9994
+ description: z2.string().optional(),
9995
+ required: z2.boolean().optional(),
9996
+ timeoutMs: z2.number().int().min(100).max(3e4).optional(),
9997
+ disabled: z2.boolean().optional()
9998
+ })
9999
+ ]))
10000
+ ]);
10001
+ var HookEventNameSchema = z2.enum([
10002
+ "SessionStart",
10003
+ "UserPromptSubmit",
10004
+ "PreToolUse",
10005
+ "PermissionRequest",
10006
+ "PostToolUse",
10007
+ "PreCompact",
10008
+ "PostCompact",
10009
+ "Stop",
10010
+ "SubagentStart",
10011
+ "SubagentStop"
10012
+ ]);
10013
+ var McpServerSchema = z2.object({
10014
+ command: z2.string(),
10015
+ args: z2.array(z2.string()).default([]),
10016
+ env: z2.record(z2.string()).optional(),
10017
+ timeout: z2.number().default(3e4)
10018
+ });
10019
+ var PluginManifestSchema = z2.object({
10020
+ name: z2.string().regex(/^[a-zA-Z0-9._-]{1,80}$/),
10021
+ version: z2.string().min(1),
10022
+ description: z2.string().optional(),
10023
+ skills: z2.array(z2.string()).default([]),
10024
+ hooks: z2.object({
10025
+ enabled: z2.boolean().optional(),
10026
+ events: z2.record(HookEventNameSchema, HookCommandSchema).default({})
10027
+ }).default({ events: {} }),
10028
+ commands: z2.array(z2.string()).default([]),
10029
+ mcpServers: z2.record(McpServerSchema).default({}),
10030
+ agents: z2.array(z2.string()).default([]),
10031
+ permissionHints: z2.array(z2.string()).default([])
10032
+ }).strict();
10033
+ function pluginRoot(configDir) {
10034
+ return join12(configDir, PLUGINS_DIR_NAME);
10035
+ }
10036
+ function pluginManifestPath(pluginDir) {
10037
+ return join12(pluginDir, MANIFEST_RELATIVE);
10038
+ }
10039
+ function statePath(configDir) {
10040
+ return join12(pluginRoot(configDir), STATE_FILE);
10041
+ }
10042
+ function loadState(configDir) {
10043
+ const file = statePath(configDir);
10044
+ if (!existsSync16(file)) return { version: 1, plugins: [] };
10045
+ try {
10046
+ const parsed = JSON.parse(readFileSync12(file, "utf-8"));
10047
+ return { version: 1, plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [] };
10048
+ } catch {
10049
+ return { version: 1, plugins: [] };
10050
+ }
10051
+ }
10052
+ function saveState(configDir, state2) {
10053
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10054
+ atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
10055
+ }
10056
+ function hashPluginManifest(manifestPath) {
10057
+ return createHash3("sha256").update(readFileSync12(manifestPath)).digest("hex");
10058
+ }
10059
+ function readPluginManifest(manifestPath) {
10060
+ return PluginManifestSchema.parse(JSON.parse(readFileSync12(manifestPath, "utf-8")));
10061
+ }
10062
+ function findPluginSourceDir(inputPath) {
10063
+ const abs = resolve7(inputPath);
10064
+ const direct = statSync6(abs).isDirectory() ? abs : dirname6(abs);
10065
+ if (existsSync16(pluginManifestPath(direct))) return direct;
10066
+ const nested = join12(direct, ".aicli-plugin");
10067
+ if (existsSync16(join12(nested, "plugin.json"))) return direct;
10068
+ throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
10069
+ }
10070
+ function installPlugin(configDir, inputPath) {
10071
+ const sourceDir = findPluginSourceDir(inputPath);
10072
+ const manifest = readPluginManifest(pluginManifestPath(sourceDir));
10073
+ const targetDir = join12(pluginRoot(configDir), manifest.name);
10074
+ mkdirSync8(pluginRoot(configDir), { recursive: true });
10075
+ if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
10076
+ cpSync(sourceDir, targetDir, { recursive: true });
10077
+ const manifestPath = pluginManifestPath(targetDir);
10078
+ const hash = hashPluginManifest(manifestPath);
10079
+ const state2 = loadState(configDir);
10080
+ const previous = state2.plugins.find((p) => p.name === manifest.name);
10081
+ const next = {
10082
+ name: manifest.name,
10083
+ enabled: previous?.enabled ?? false,
10084
+ trusted: false,
10085
+ hash,
10086
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10087
+ };
10088
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
10089
+ saveState(configDir, state2);
10090
+ return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
10091
+ }
10092
+ function setPluginEnabled(configDir, name, enabled) {
10093
+ const plugin = getInstalledPlugin(configDir, name);
10094
+ if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
10095
+ const state2 = loadState(configDir);
10096
+ const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
10097
+ name: plugin.name,
10098
+ enabled: false,
10099
+ trusted: false,
10100
+ hash: plugin.hash,
10101
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10102
+ };
10103
+ const next = { ...current, enabled, hash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
10104
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
10105
+ saveState(configDir, state2);
10106
+ return next;
10107
+ }
10108
+ function trustPlugin(configDir, name) {
10109
+ const plugin = getInstalledPlugin(configDir, name);
10110
+ if (!plugin.valid) throw new Error(plugin.error ?? `invalid plugin: ${name}`);
10111
+ const state2 = loadState(configDir);
10112
+ const current = state2.plugins.find((p) => p.name === plugin.name) ?? {
10113
+ name: plugin.name,
10114
+ enabled: false,
10115
+ trusted: false,
10116
+ hash: plugin.hash,
10117
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10118
+ };
10119
+ const next = { ...current, trusted: true, hash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
10120
+ state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
10121
+ saveState(configDir, state2);
10122
+ return next;
10123
+ }
10124
+ function getInstalledPlugin(configDir, name) {
10125
+ const plugins = listInstalledPlugins(configDir);
10126
+ const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
10127
+ if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
10128
+ if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
10129
+ return matches[0];
10130
+ }
10131
+ function listInstalledPlugins(configDir) {
10132
+ const root = pluginRoot(configDir);
10133
+ if (!existsSync16(root)) return [];
10134
+ const state2 = loadState(configDir);
10135
+ const out = [];
10136
+ for (const entry of readdirSync7(root)) {
10137
+ const dir = join12(root, entry);
10138
+ try {
10139
+ if (!statSync6(dir).isDirectory()) continue;
10140
+ } catch {
10141
+ continue;
10142
+ }
10143
+ const manifestPath = pluginManifestPath(dir);
10144
+ if (!existsSync16(manifestPath)) continue;
10145
+ try {
10146
+ const manifest = readPluginManifest(manifestPath);
10147
+ const hash = hashPluginManifest(manifestPath);
10148
+ const st = state2.plugins.find((p) => p.name === manifest.name);
10149
+ const trusted = st?.trusted === true && st.hash === hash;
10150
+ out.push({
10151
+ name: manifest.name,
10152
+ dir,
10153
+ manifestPath,
10154
+ manifest,
10155
+ hash,
10156
+ enabled: st?.enabled === true,
10157
+ trusted,
10158
+ valid: true
10159
+ });
10160
+ } catch (err) {
10161
+ out.push({
10162
+ name: entry,
10163
+ dir,
10164
+ manifestPath,
10165
+ manifest: { name: entry, version: "invalid", skills: [], commands: [], agents: [], hooks: { events: {} }, mcpServers: {}, permissionHints: [] },
10166
+ hash: "",
10167
+ enabled: false,
10168
+ trusted: false,
10169
+ valid: false,
10170
+ error: err instanceof Error ? err.message : String(err)
10171
+ });
10172
+ }
10173
+ }
10174
+ return out.sort((a, b) => a.name.localeCompare(b.name));
10175
+ }
10176
+ function activePlugins(configDir) {
10177
+ return listInstalledPlugins(configDir).filter((p) => p.valid && p.enabled && p.trusted);
10178
+ }
10179
+ function uniqueDirs(plugin, rels) {
10180
+ const dirs = /* @__PURE__ */ new Set();
10181
+ for (const rel of rels) {
10182
+ const abs = resolve7(plugin.dir, rel);
10183
+ if (!abs.startsWith(resolve7(plugin.dir))) continue;
10184
+ if (existsSync16(abs)) dirs.add(statSync6(abs).isDirectory() ? abs : dirname6(abs));
10185
+ }
10186
+ return [...dirs];
10187
+ }
10188
+ function prefixCommand(plugin, command) {
10189
+ return command.replaceAll("{pluginDir}", plugin.dir).replaceAll("$PLUGIN_DIR", plugin.dir);
10190
+ }
10191
+ function pluginHooks(plugin) {
10192
+ const events = plugin.manifest.hooks?.events ?? {};
10193
+ const out = {};
10194
+ for (const [event, raw] of Object.entries(events)) {
10195
+ const normalize = (item) => {
10196
+ if (typeof item === "string") return { command: prefixCommand(plugin, item), source: "project", description: `plugin:${plugin.name}` };
10197
+ if (item && typeof item === "object") {
10198
+ const obj = item;
10199
+ return { ...obj, command: prefixCommand(plugin, String(obj.command ?? "")), source: "project", description: obj.description ?? `plugin:${plugin.name}` };
10200
+ }
10201
+ return item;
10202
+ };
10203
+ out[event] = Array.isArray(raw) ? raw.map(normalize) : normalize(raw);
10204
+ }
10205
+ return Object.keys(out).length > 0 ? { enabled: plugin.manifest.hooks?.enabled ?? true, events: out } : void 0;
10206
+ }
10207
+ function mergePluginHooks(base, configDir) {
10208
+ const plugins = activePlugins(configDir);
10209
+ if (plugins.length === 0) return base;
10210
+ const merged = { ...base ?? {}, enabled: base?.enabled ?? true, events: { ...base?.events ?? {} } };
10211
+ for (const plugin of plugins) {
10212
+ const hooks = pluginHooks(plugin);
10213
+ if (!hooks?.events) continue;
10214
+ for (const [event, entry] of Object.entries(hooks.events)) {
10215
+ const key = event;
10216
+ const existing = merged.events?.[key];
10217
+ const existingItems = existing === void 0 ? [] : Array.isArray(existing) ? existing : [existing];
10218
+ const newItems = Array.isArray(entry) ? entry : [entry];
10219
+ merged.events[key] = [...existingItems, ...newItems];
10220
+ }
10221
+ }
10222
+ return merged;
10223
+ }
10224
+ function getActivePluginAssets(configDir) {
10225
+ const plugins = activePlugins(configDir);
10226
+ const mcpServers = {};
10227
+ const skillDirs = [];
10228
+ const commandDirs = [];
10229
+ const agentDirs = [];
10230
+ for (const plugin of plugins) {
10231
+ skillDirs.push(...uniqueDirs(plugin, plugin.manifest.skills));
10232
+ commandDirs.push(...uniqueDirs(plugin, plugin.manifest.commands));
10233
+ agentDirs.push(...uniqueDirs(plugin, plugin.manifest.agents));
10234
+ for (const [serverName, server] of Object.entries(plugin.manifest.mcpServers)) {
10235
+ mcpServers[`plugin_${plugin.name}_${serverName}`] = server;
10236
+ }
10237
+ }
10238
+ return { plugins, skillDirs, commandDirs, agentDirs, hooks: mergePluginHooks(void 0, configDir), mcpServers };
10239
+ }
10240
+ function describePlugin(plugin) {
10241
+ const m = plugin.manifest;
10242
+ return [
10243
+ `${plugin.name}@${m.version}`,
10244
+ ` status: ${plugin.enabled ? "enabled" : "disabled"} \xB7 ${plugin.trusted ? "trusted" : "untrusted"}${plugin.valid ? "" : " \xB7 invalid"}`,
10245
+ ` dir: ${plugin.dir}`,
10246
+ m.description ? ` description: ${m.description}` : void 0,
10247
+ ` assets: ${m.skills.length} skill(s), ${m.commands.length} command(s), ${m.agents.length} agent(s), ${Object.keys(m.mcpServers).length} MCP server(s), ${Object.keys(m.hooks?.events ?? {}).length} hook event(s)`,
10248
+ m.permissionHints.length ? ` permission hints: ${m.permissionHints.join("; ")}` : void 0,
10249
+ plugin.error ? ` error: ${plugin.error}` : void 0
10250
+ ].filter((line) => Boolean(line));
10251
+ }
10252
+
10253
+ // src/agents/agent-config.ts
9971
10254
  var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
9972
10255
  "read_file",
9973
10256
  "list_dir",
@@ -10061,13 +10344,13 @@ function parseAgentConfig(raw, source, path3) {
10061
10344
  return cfg;
10062
10345
  }
10063
10346
  function loadAgentDir(dir, source) {
10064
- if (!existsSync16(dir)) return [];
10347
+ if (!existsSync17(dir)) return [];
10065
10348
  const out = [];
10066
- for (const file of readdirSync7(dir)) {
10349
+ for (const file of readdirSync8(dir)) {
10067
10350
  if (!file.endsWith(".json")) continue;
10068
- const path3 = join12(dir, file);
10351
+ const path3 = join13(dir, file);
10069
10352
  try {
10070
- const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
10353
+ const parsed = JSON.parse(readFileSync13(path3, "utf-8"));
10071
10354
  const cfg = parseAgentConfig(parsed, source, path3);
10072
10355
  if (cfg) out.push(cfg);
10073
10356
  } catch {
@@ -10077,8 +10360,8 @@ function loadAgentDir(dir, source) {
10077
10360
  }
10078
10361
  function getAgentDirs(configDir, cwd = process.cwd()) {
10079
10362
  return {
10080
- user: join12(configDir ?? join12(homedir6(), CONFIG_DIR_NAME), "agents"),
10081
- project: join12(cwd, CONFIG_DIR_NAME, "agents")
10363
+ user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
10364
+ project: join13(cwd, CONFIG_DIR_NAME, "agents")
10082
10365
  };
10083
10366
  }
10084
10367
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -10086,6 +10369,11 @@ function listAgentConfigs(configDir, cwd = process.cwd()) {
10086
10369
  const byName = /* @__PURE__ */ new Map();
10087
10370
  for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
10088
10371
  for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
10372
+ if (configDir) {
10373
+ for (const dir of getActivePluginAssets(configDir).agentDirs) {
10374
+ for (const cfg of loadAgentDir(dir, "plugin")) byName.set(normalizeName(cfg.name), cfg);
10375
+ }
10376
+ }
10089
10377
  for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
10090
10378
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
10091
10379
  }
@@ -10788,14 +11076,14 @@ var taskStopTool = {
10788
11076
 
10789
11077
  // src/tools/builtin/git-tools.ts
10790
11078
  import { execFileSync as execFileSync2 } from "child_process";
10791
- import { existsSync as existsSync17 } from "fs";
10792
- import { join as join13 } from "path";
11079
+ import { existsSync as existsSync18 } from "fs";
11080
+ import { join as join14 } from "path";
10793
11081
  function assertGitRepo(cwd) {
10794
11082
  let dir = cwd;
10795
11083
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10796
11084
  while (dir && dir !== root) {
10797
- if (existsSync17(join13(dir, ".git"))) return;
10798
- const parent = join13(dir, "..");
11085
+ if (existsSync18(join14(dir, ".git"))) return;
11086
+ const parent = join14(dir, "..");
10799
11087
  if (parent === dir) break;
10800
11088
  dir = parent;
10801
11089
  }
@@ -11060,9 +11348,9 @@ ${commitOutput.trim()}`;
11060
11348
  };
11061
11349
 
11062
11350
  // src/tools/builtin/notebook-edit.ts
11063
- import { readFileSync as readFileSync13, existsSync as existsSync18 } from "fs";
11351
+ import { readFileSync as readFileSync14, existsSync as existsSync19 } from "fs";
11064
11352
  import { writeFile } from "fs/promises";
11065
- import { resolve as resolve7, extname as extname2 } from "path";
11353
+ import { resolve as resolve8, extname as extname2 } from "path";
11066
11354
  var notebookEditTool = {
11067
11355
  definition: {
11068
11356
  name: "notebook_edit",
@@ -11111,14 +11399,14 @@ var notebookEditTool = {
11111
11399
  if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
11112
11400
  throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
11113
11401
  }
11114
- const absPath = resolve7(filePath);
11402
+ const absPath = resolve8(filePath);
11115
11403
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
11116
11404
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
11117
11405
  }
11118
- if (!existsSync18(absPath)) {
11406
+ if (!existsSync19(absPath)) {
11119
11407
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
11120
11408
  }
11121
- const raw = readFileSync13(absPath, "utf-8");
11409
+ const raw = readFileSync14(absPath, "utf-8");
11122
11410
  const nb = parseNotebook(raw);
11123
11411
  const cellIdx0 = cellIndexRaw - 1;
11124
11412
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -11535,8 +11823,8 @@ function estimateToolDefinitionTokens(def) {
11535
11823
 
11536
11824
  // src/tools/registry.ts
11537
11825
  import { pathToFileURL } from "url";
11538
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11539
- import { join as join14 } from "path";
11826
+ import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
11827
+ import { join as join15 } from "path";
11540
11828
  var ToolRegistry = class {
11541
11829
  tools = /* @__PURE__ */ new Map();
11542
11830
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -11697,16 +11985,16 @@ var ToolRegistry = class {
11697
11985
  * Returns the number of successfully loaded plugins.
11698
11986
  */
11699
11987
  async loadPlugins(pluginsDir, allowPlugins = false) {
11700
- if (!existsSync19(pluginsDir)) {
11988
+ if (!existsSync20(pluginsDir)) {
11701
11989
  try {
11702
- mkdirSync8(pluginsDir, { recursive: true });
11990
+ mkdirSync9(pluginsDir, { recursive: true });
11703
11991
  } catch {
11704
11992
  }
11705
11993
  return 0;
11706
11994
  }
11707
11995
  let files;
11708
11996
  try {
11709
- files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
11997
+ files = readdirSync9(pluginsDir).filter((f) => f.endsWith(".js"));
11710
11998
  } catch {
11711
11999
  return 0;
11712
12000
  }
@@ -11723,12 +12011,12 @@ var ToolRegistry = class {
11723
12011
  process.stderr.write(
11724
12012
  `
11725
12013
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
11726
- ` + files.map((f) => ` + ${join14(pluginsDir, f)}`).join("\n") + "\n\n"
12014
+ ` + files.map((f) => ` + ${join15(pluginsDir, f)}`).join("\n") + "\n\n"
11727
12015
  );
11728
12016
  let loaded = 0;
11729
12017
  for (const file of files) {
11730
12018
  try {
11731
- const fileUrl = pathToFileURL(join14(pluginsDir, file)).href;
12019
+ const fileUrl = pathToFileURL(join15(pluginsDir, file)).href;
11732
12020
  const mod = await import(fileUrl);
11733
12021
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
11734
12022
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11890,7 +12178,7 @@ var McpClient = class {
11890
12178
  // 内部方法:JSON-RPC 通信
11891
12179
  // ══════════════════════════════════════════════════════════════════
11892
12180
  sendRequest(method, params) {
11893
- return new Promise((resolve11, reject) => {
12181
+ return new Promise((resolve12, reject) => {
11894
12182
  if (!this.process?.stdin?.writable) {
11895
12183
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
11896
12184
  }
@@ -11914,7 +12202,7 @@ var McpClient = class {
11914
12202
  this.pendingRequests.set(id, {
11915
12203
  resolve: (result) => {
11916
12204
  cleanup();
11917
- resolve11(result);
12205
+ resolve12(result);
11918
12206
  },
11919
12207
  reject: (error) => {
11920
12208
  cleanup();
@@ -11991,13 +12279,13 @@ var McpClient = class {
11991
12279
  }
11992
12280
  /** Promise 超时包装 */
11993
12281
  withTimeout(promise, ms, label) {
11994
- return new Promise((resolve11, reject) => {
12282
+ return new Promise((resolve12, reject) => {
11995
12283
  const timer = setTimeout(() => {
11996
12284
  reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
11997
12285
  }, ms);
11998
12286
  promise.then((val) => {
11999
12287
  clearTimeout(timer);
12000
- resolve11(val);
12288
+ resolve12(val);
12001
12289
  }).catch((err) => {
12002
12290
  clearTimeout(timer);
12003
12291
  reject(err);
@@ -12274,12 +12562,12 @@ var McpManager = class {
12274
12562
  };
12275
12563
 
12276
12564
  // src/skills/manager.ts
12277
- import { existsSync as existsSync20, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync6 } from "fs";
12278
- import { join as join15 } from "path";
12565
+ import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync7 } from "fs";
12566
+ import { join as join16 } from "path";
12279
12567
 
12280
12568
  // src/skills/types.ts
12281
- import { readFileSync as readFileSync14 } from "fs";
12282
- import { basename as basename5 } from "path";
12569
+ import { readFileSync as readFileSync15 } from "fs";
12570
+ import { basename as basename6 } from "path";
12283
12571
  function parseSimpleYaml(yaml) {
12284
12572
  const result = {};
12285
12573
  for (const line of yaml.split("\n")) {
@@ -12300,7 +12588,7 @@ function parseYamlArray(value) {
12300
12588
  function parseSkillFile(filePath) {
12301
12589
  let raw;
12302
12590
  try {
12303
- raw = readFileSync14(filePath, "utf-8");
12591
+ raw = readFileSync15(filePath, "utf-8");
12304
12592
  } catch {
12305
12593
  return null;
12306
12594
  }
@@ -12308,7 +12596,7 @@ function parseSkillFile(filePath) {
12308
12596
  if (!frontmatterMatch) {
12309
12597
  return {
12310
12598
  meta: {
12311
- name: basename5(filePath, ".md"),
12599
+ name: basename6(filePath, ".md"),
12312
12600
  description: ""
12313
12601
  },
12314
12602
  content: raw.trim(),
@@ -12319,7 +12607,7 @@ function parseSkillFile(filePath) {
12319
12607
  const parsed = parseSimpleYaml(yaml);
12320
12608
  return {
12321
12609
  meta: {
12322
- name: parsed["name"] ?? basename5(filePath, ".md"),
12610
+ name: parsed["name"] ?? basename6(filePath, ".md"),
12323
12611
  description: parsed["description"] ?? "",
12324
12612
  tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
12325
12613
  },
@@ -12340,36 +12628,40 @@ var SkillManager = class {
12340
12628
  this.skillsDir = skillsDir;
12341
12629
  this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
12342
12630
  }
12343
- /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
12344
- loadSkills() {
12631
+ /** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
12632
+ loadSkills(extraDirs = []) {
12345
12633
  this.skills.clear();
12346
- if (!existsSync20(this.skillsDir)) {
12347
- try {
12348
- mkdirSync9(this.skillsDir, { recursive: true });
12349
- } catch {
12634
+ this.loadSkillDir(this.skillsDir, true);
12635
+ for (const dir of extraDirs) this.loadSkillDir(dir, false);
12636
+ return this.skills.size;
12637
+ }
12638
+ loadSkillDir(dir, createIfMissing) {
12639
+ if (!existsSync21(dir)) {
12640
+ if (createIfMissing) {
12641
+ try {
12642
+ mkdirSync10(dir, { recursive: true });
12643
+ } catch {
12644
+ }
12350
12645
  }
12351
- return 0;
12646
+ return;
12352
12647
  }
12353
12648
  let entries;
12354
12649
  try {
12355
- entries = readdirSync9(this.skillsDir);
12650
+ entries = readdirSync10(dir);
12356
12651
  } catch {
12357
- return 0;
12652
+ return;
12358
12653
  }
12359
12654
  for (const entry of entries) {
12360
12655
  let filePath;
12361
- const fullPath = join15(this.skillsDir, entry);
12656
+ const fullPath = join16(dir, entry);
12362
12657
  if (entry.endsWith(".md")) {
12363
12658
  filePath = fullPath;
12364
12659
  } else {
12365
12660
  try {
12366
- if (statSync6(fullPath).isDirectory()) {
12367
- const skillMd = join15(fullPath, "SKILL.md");
12368
- if (existsSync20(skillMd)) {
12369
- filePath = skillMd;
12370
- } else {
12371
- continue;
12372
- }
12661
+ if (statSync7(fullPath).isDirectory()) {
12662
+ const skillMd = join16(fullPath, "SKILL.md");
12663
+ if (existsSync21(skillMd)) filePath = skillMd;
12664
+ else continue;
12373
12665
  } else {
12374
12666
  continue;
12375
12667
  }
@@ -12379,13 +12671,10 @@ var SkillManager = class {
12379
12671
  }
12380
12672
  const skill = parseSkillFile(filePath);
12381
12673
  if (skill) {
12382
- if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) {
12383
- skill.meta.name = entry;
12384
- }
12385
- this.skills.set(skill.meta.name, skill);
12674
+ if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
12675
+ if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
12386
12676
  }
12387
12677
  }
12388
- return this.skills.size;
12389
12678
  }
12390
12679
  /** 列出所有已加载的技能 */
12391
12680
  listSkills() {
@@ -12430,7 +12719,7 @@ var SkillManager = class {
12430
12719
  // src/web/tool-executor-web.ts
12431
12720
  import { randomUUID as randomUUID3 } from "crypto";
12432
12721
  import { tmpdir as tmpdir2 } from "os";
12433
- import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
12722
+ import { existsSync as existsSync22, readFileSync as readFileSync16 } from "fs";
12434
12723
  var ToolExecutorWeb = class _ToolExecutorWeb {
12435
12724
  constructor(registry, ws) {
12436
12725
  this.registry = registry;
@@ -12500,33 +12789,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12500
12789
  }
12501
12790
  /** Resolve a pending confirm from client response */
12502
12791
  resolveConfirm(requestId, approved) {
12503
- const resolve11 = this.pendingConfirms.get(requestId);
12504
- if (resolve11) {
12792
+ const resolve12 = this.pendingConfirms.get(requestId);
12793
+ if (resolve12) {
12505
12794
  this.clearPendingTimer(requestId);
12506
12795
  this.pendingConfirms.delete(requestId);
12507
12796
  this.confirming = false;
12508
- resolve11(approved);
12797
+ resolve12(approved);
12509
12798
  }
12510
12799
  }
12511
12800
  /** Resolve a pending batch confirm from client response */
12512
12801
  resolveBatchConfirm(requestId, decision) {
12513
- const resolve11 = this.pendingBatchConfirms.get(requestId);
12514
- if (resolve11) {
12802
+ const resolve12 = this.pendingBatchConfirms.get(requestId);
12803
+ if (resolve12) {
12515
12804
  this.clearPendingTimer(requestId);
12516
12805
  this.pendingBatchConfirms.delete(requestId);
12517
12806
  this.confirming = false;
12518
12807
  if (decision === "all" || decision === "none") {
12519
- resolve11(decision);
12808
+ resolve12(decision);
12520
12809
  } else {
12521
- resolve11(new Set(decision));
12810
+ resolve12(new Set(decision));
12522
12811
  }
12523
12812
  }
12524
12813
  }
12525
12814
  /** Cancel all pending confirms (e.g., on disconnect) */
12526
12815
  cancelAll() {
12527
- for (const resolve11 of this.pendingConfirms.values()) resolve11(false);
12816
+ for (const resolve12 of this.pendingConfirms.values()) resolve12(false);
12528
12817
  this.pendingConfirms.clear();
12529
- for (const resolve11 of this.pendingBatchConfirms.values()) resolve11("none");
12818
+ for (const resolve12 of this.pendingBatchConfirms.values()) resolve12("none");
12530
12819
  this.pendingBatchConfirms.clear();
12531
12820
  this.confirming = false;
12532
12821
  }
@@ -12569,9 +12858,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12569
12858
  if (call.name === "write_file") {
12570
12859
  const filePath = String(call.arguments["path"] ?? "");
12571
12860
  const newContent = String(call.arguments["content"] ?? "");
12572
- if (filePath && existsSync21(filePath)) {
12861
+ if (filePath && existsSync22(filePath)) {
12573
12862
  try {
12574
- const old = readFileSync15(filePath, "utf-8");
12863
+ const old = readFileSync16(filePath, "utf-8");
12575
12864
  return renderDiff(old, newContent, { filePath });
12576
12865
  } catch {
12577
12866
  }
@@ -12602,8 +12891,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12602
12891
  diff: this.getDiffPreview(call)
12603
12892
  };
12604
12893
  this.send(msg);
12605
- return new Promise((resolve11) => {
12606
- this.pendingConfirms.set(requestId, resolve11);
12894
+ return new Promise((resolve12) => {
12895
+ this.pendingConfirms.set(requestId, resolve12);
12607
12896
  this.pendingTimers.set(requestId, setTimeout(() => {
12608
12897
  if (this.pendingConfirms.has(requestId)) {
12609
12898
  this.resolveConfirm(requestId, false);
@@ -12627,8 +12916,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12627
12916
  files
12628
12917
  };
12629
12918
  this.send(msg);
12630
- return new Promise((resolve11) => {
12631
- this.pendingBatchConfirms.set(requestId, resolve11);
12919
+ return new Promise((resolve12) => {
12920
+ this.pendingBatchConfirms.set(requestId, resolve12);
12632
12921
  this.pendingTimers.set(requestId, setTimeout(() => {
12633
12922
  if (this.pendingBatchConfirms.has(requestId)) {
12634
12923
  this.resolveBatchConfirm(requestId, "none");
@@ -12874,16 +13163,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12874
13163
  };
12875
13164
 
12876
13165
  // src/repl/dev-state.ts
12877
- import { existsSync as existsSync22, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12878
- import { join as join16 } from "path";
13166
+ import { existsSync as existsSync23, readFileSync as readFileSync17, unlinkSync as unlinkSync4, mkdirSync as mkdirSync11 } from "fs";
13167
+ import { join as join17 } from "path";
12879
13168
  import { homedir as homedir7 } from "os";
12880
13169
  function getDevStatePath() {
12881
- return join16(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
13170
+ return join17(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12882
13171
  }
12883
13172
  function loadDevState() {
12884
13173
  const path3 = getDevStatePath();
12885
- if (!existsSync22(path3)) return null;
12886
- const content = readFileSync16(path3, "utf-8").trim();
13174
+ if (!existsSync23(path3)) return null;
13175
+ const content = readFileSync17(path3, "utf-8").trim();
12887
13176
  return content || null;
12888
13177
  }
12889
13178
 
@@ -13140,8 +13429,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
13140
13429
  }
13141
13430
 
13142
13431
  // src/core/context-files.ts
13143
- import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
13144
- import { join as join17, relative as relative3, resolve as resolve8 } from "path";
13432
+ import { existsSync as existsSync24, readFileSync as readFileSync18 } from "fs";
13433
+ import { join as join18, relative as relative3, resolve as resolve9 } from "path";
13145
13434
  function uniqueNonEmpty(values) {
13146
13435
  const seen = /* @__PURE__ */ new Set();
13147
13436
  const out = [];
@@ -13164,14 +13453,14 @@ function displayPath(filePath, cwd) {
13164
13453
  return filePath;
13165
13454
  }
13166
13455
  function isInsideOrEqual(parent, child) {
13167
- const rel = relative3(resolve8(parent), resolve8(child));
13456
+ const rel = relative3(resolve9(parent), resolve9(child));
13168
13457
  return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
13169
13458
  }
13170
13459
  function readContextFile(level, filePath, cwd, maxBytes) {
13171
13460
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
13172
13461
  const shown = displayPath(filePath, cwd);
13173
13462
  try {
13174
- const raw = readFileSync17(filePath);
13463
+ const raw = readFileSync18(filePath);
13175
13464
  const byteCount = raw.byteLength;
13176
13465
  if (byteCount === 0) {
13177
13466
  return {
@@ -13218,8 +13507,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
13218
13507
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13219
13508
  const skipped = [];
13220
13509
  for (const candidate of candidates) {
13221
- const filePath = join17(dir, candidate);
13222
- if (!existsSync23(filePath)) continue;
13510
+ const filePath = join18(dir, candidate);
13511
+ if (!existsSync24(filePath)) continue;
13223
13512
  const result = readContextFile(level, filePath, cwd, maxBytes);
13224
13513
  if (result.skipped) skipped.push(result.skipped);
13225
13514
  if (result.layer) return { layer: result.layer, skipped };
@@ -13227,7 +13516,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
13227
13516
  return { layer: null, skipped };
13228
13517
  }
13229
13518
  function loadContextFiles(options) {
13230
- const cwd = resolve8(options.cwd);
13519
+ const cwd = resolve9(options.cwd);
13231
13520
  const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
13232
13521
  const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
13233
13522
  const skipped = [];
@@ -13235,7 +13524,7 @@ function loadContextFiles(options) {
13235
13524
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
13236
13525
  }
13237
13526
  if (options.setting && options.setting !== "auto") {
13238
- const filePath = resolve8(cwd, String(options.setting));
13527
+ const filePath = resolve9(cwd, String(options.setting));
13239
13528
  if (!isInsideOrEqual(cwd, filePath)) {
13240
13529
  skipped.push({
13241
13530
  level: "single",
@@ -13246,7 +13535,7 @@ function loadContextFiles(options) {
13246
13535
  });
13247
13536
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
13248
13537
  }
13249
- if (!existsSync23(filePath)) {
13538
+ if (!existsSync24(filePath)) {
13250
13539
  skipped.push({
13251
13540
  level: "single",
13252
13541
  filePath,
@@ -13279,7 +13568,7 @@ function loadContextFiles(options) {
13279
13568
  skipped.push(...result.skipped);
13280
13569
  if (result.layer) layers.push(result.layer);
13281
13570
  }
13282
- if (resolve8(options.cwd) !== resolve8(options.projectRoot)) {
13571
+ if (resolve9(options.cwd) !== resolve9(options.projectRoot)) {
13283
13572
  const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
13284
13573
  skipped.push(...result.skipped);
13285
13574
  if (result.layer) layers.push(result.layer);
@@ -13297,8 +13586,8 @@ function loadContextFiles(options) {
13297
13586
  }
13298
13587
 
13299
13588
  // src/web/session-handler.ts
13300
- import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync8, createWriteStream } from "fs";
13301
- import { join as join20, resolve as resolve9, dirname as dirname6 } from "path";
13589
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync12, statSync as statSync9, createWriteStream } from "fs";
13590
+ import { join as join21, resolve as resolve10, dirname as dirname7 } from "path";
13302
13591
 
13303
13592
  // src/cli/review-prompts.ts
13304
13593
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -13359,8 +13648,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
13359
13648
  }
13360
13649
 
13361
13650
  // src/repl/commands/project-init.ts
13362
- import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync10, statSync as statSync7 } from "fs";
13363
- import { join as join18 } from "path";
13651
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
13652
+ import { join as join19 } from "path";
13364
13653
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
13365
13654
  "node_modules",
13366
13655
  ".git",
@@ -13396,7 +13685,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13396
13685
  if (depth > maxDepth || count >= maxEntries) return;
13397
13686
  let entries;
13398
13687
  try {
13399
- entries = readdirSync10(d);
13688
+ entries = readdirSync11(d);
13400
13689
  } catch {
13401
13690
  return;
13402
13691
  }
@@ -13404,11 +13693,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13404
13693
  const sorted = filtered.sort((a, b) => {
13405
13694
  let aIsDir = false, bIsDir = false;
13406
13695
  try {
13407
- aIsDir = statSync7(join18(d, a)).isDirectory();
13696
+ aIsDir = statSync8(join19(d, a)).isDirectory();
13408
13697
  } catch {
13409
13698
  }
13410
13699
  try {
13411
- bIsDir = statSync7(join18(d, b)).isDirectory();
13700
+ bIsDir = statSync8(join19(d, b)).isDirectory();
13412
13701
  } catch {
13413
13702
  }
13414
13703
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -13416,12 +13705,12 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
13416
13705
  });
13417
13706
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
13418
13707
  const name = sorted[i];
13419
- const fullPath = join18(d, name);
13708
+ const fullPath = join19(d, name);
13420
13709
  const isLast = i === sorted.length - 1;
13421
13710
  const connector = isLast ? "+-- " : "|-- ";
13422
13711
  let isDir;
13423
13712
  try {
13424
- isDir = statSync7(fullPath).isDirectory();
13713
+ isDir = statSync8(fullPath).isDirectory();
13425
13714
  } catch {
13426
13715
  continue;
13427
13716
  }
@@ -13443,7 +13732,7 @@ function scanProject(cwd) {
13443
13732
  configFiles: [],
13444
13733
  directoryStructure: ""
13445
13734
  };
13446
- const check = (file) => existsSync24(join18(cwd, file));
13735
+ const check = (file) => existsSync25(join19(cwd, file));
13447
13736
  const configCandidates = [
13448
13737
  "package.json",
13449
13738
  "tsconfig.json",
@@ -13470,7 +13759,7 @@ function scanProject(cwd) {
13470
13759
  info.type = "node";
13471
13760
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
13472
13761
  try {
13473
- const pkg = JSON.parse(readFileSync18(join18(cwd, "package.json"), "utf-8"));
13762
+ const pkg = JSON.parse(readFileSync19(join19(cwd, "package.json"), "utf-8"));
13474
13763
  const scripts = pkg.scripts ?? {};
13475
13764
  info.buildCommand = scripts.build ? "npm run build" : void 0;
13476
13765
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -14130,7 +14419,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
14130
14419
  }
14131
14420
 
14132
14421
  // src/hub/persist.ts
14133
- import { join as join19 } from "path";
14422
+ import { join as join20 } from "path";
14134
14423
  function discussionToMessages(state2) {
14135
14424
  const out = [];
14136
14425
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -14168,7 +14457,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
14168
14457
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
14169
14458
  session.titleAiGenerated = true;
14170
14459
  await sm.save();
14171
- return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
14460
+ return { id: session.id, path: join20(config.getHistoryDir(), `${session.id}.json`) };
14172
14461
  }
14173
14462
 
14174
14463
  // src/web/session-handler.ts
@@ -14264,7 +14553,7 @@ var SessionHandler = class {
14264
14553
  }
14265
14554
  this.updateContextWindow();
14266
14555
  this.activeSystemPrompt = this.loadContextFiles();
14267
- const hooks = this.config.get("hooks");
14556
+ const hooks = mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir());
14268
14557
  const permissionRules = this.config.get("permissionRules");
14269
14558
  const defaultPermission = this.config.get("defaultPermission");
14270
14559
  const permissionProfileName = this.config.get("defaultPermissionProfile") ?? "legacy";
@@ -14364,10 +14653,10 @@ var SessionHandler = class {
14364
14653
  return;
14365
14654
  }
14366
14655
  case "ask_user_response": {
14367
- const resolve11 = this.pendingAskUser.get(msg.requestId);
14368
- if (resolve11) {
14656
+ const resolve12 = this.pendingAskUser.get(msg.requestId);
14657
+ if (resolve12) {
14369
14658
  this.pendingAskUser.delete(msg.requestId);
14370
- resolve11(msg.answer);
14659
+ resolve12(msg.answer);
14371
14660
  }
14372
14661
  return;
14373
14662
  }
@@ -14380,10 +14669,10 @@ var SessionHandler = class {
14380
14669
  case "memory_manage":
14381
14670
  return this.handleMemoryManage(msg.action, msg.id, msg.expiresAt);
14382
14671
  case "auto_pause_response": {
14383
- const resolve11 = this.pendingAutoPause.get(msg.requestId);
14384
- if (resolve11) {
14672
+ const resolve12 = this.pendingAutoPause.get(msg.requestId);
14673
+ if (resolve12) {
14385
14674
  this.pendingAutoPause.delete(msg.requestId);
14386
- resolve11({ action: msg.action, message: msg.message });
14675
+ resolve12({ action: msg.action, message: msg.message });
14387
14676
  }
14388
14677
  return;
14389
14678
  }
@@ -14398,10 +14687,10 @@ var SessionHandler = class {
14398
14687
  this.hubOrchestrator?.abort();
14399
14688
  return;
14400
14689
  case "hub_steer": {
14401
- const resolve11 = this.pendingHubReview.get(msg.requestId);
14402
- if (resolve11) {
14690
+ const resolve12 = this.pendingHubReview.get(msg.requestId);
14691
+ if (resolve12) {
14403
14692
  this.pendingHubReview.delete(msg.requestId);
14404
- resolve11({ action: msg.action, message: msg.message });
14693
+ resolve12({ action: msg.action, message: msg.message });
14405
14694
  }
14406
14695
  return;
14407
14696
  }
@@ -14411,12 +14700,12 @@ var SessionHandler = class {
14411
14700
  }
14412
14701
  }
14413
14702
  onDisconnect() {
14414
- runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
14703
+ runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
14415
14704
  this.toolExecutor.cancelAll();
14416
14705
  if (this.abortController) this.abortController.abort();
14417
- for (const resolve11 of this.pendingAskUser.values()) resolve11(null);
14706
+ for (const resolve12 of this.pendingAskUser.values()) resolve12(null);
14418
14707
  this.pendingAskUser.clear();
14419
- for (const resolve11 of this.pendingAutoPause.values()) resolve11({ action: "stop" });
14708
+ for (const resolve12 of this.pendingAutoPause.values()) resolve12({ action: "stop" });
14420
14709
  this.pendingAutoPause.clear();
14421
14710
  this.saveIfNeeded();
14422
14711
  }
@@ -14550,9 +14839,9 @@ var SessionHandler = class {
14550
14839
  this.hubOrchestrator = orchestrator;
14551
14840
  orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
14552
14841
  if (config.humanSteer) {
14553
- orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve11) => {
14842
+ orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve12) => {
14554
14843
  const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14555
- this.pendingHubReview.set(requestId, resolve11);
14844
+ this.pendingHubReview.set(requestId, resolve12);
14556
14845
  this.send({ type: "hub_review", requestId, round, maxRounds });
14557
14846
  });
14558
14847
  }
@@ -14590,7 +14879,7 @@ var SessionHandler = class {
14590
14879
  this.ensureSession();
14591
14880
  const session = this.sessions.current;
14592
14881
  const promptDecisions = runLifecycleHooks(
14593
- this.config.get("hooks") ?? void 0,
14882
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
14594
14883
  "UserPromptSubmit",
14595
14884
  { sessionId: session.id, prompt: content },
14596
14885
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -14885,7 +15174,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14885
15174
  this.send({ type: "response_done", content, usage });
14886
15175
  if (pendingTeeSave && isCleanDocumentBody(content)) {
14887
15176
  try {
14888
- mkdirSync11(dirname6(pendingTeeSave), { recursive: true });
15177
+ mkdirSync12(dirname7(pendingTeeSave), { recursive: true });
14889
15178
  const bodyToSave = stripOuterCodeFence(content);
14890
15179
  atomicWriteFileSync(pendingTeeSave, bodyToSave);
14891
15180
  undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
@@ -14910,8 +15199,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
14910
15199
  onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
14911
15200
  requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
14912
15201
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
14913
- const pauseResp = await new Promise((resolve11) => {
14914
- this.pendingAutoPause.set(requestId, resolve11);
15202
+ const pauseResp = await new Promise((resolve12) => {
15203
+ this.pendingAutoPause.set(requestId, resolve12);
14915
15204
  this.send({
14916
15205
  type: "auto_pause_request",
14917
15206
  requestId,
@@ -15061,7 +15350,7 @@ ${summaryContent}`,
15061
15350
  let isError = false;
15062
15351
  let summary;
15063
15352
  try {
15064
- mkdirSync11(dirname6(saveToFile), { recursive: true });
15353
+ mkdirSync12(dirname7(saveToFile), { recursive: true });
15065
15354
  fileStream = createWriteStream(saveToFile);
15066
15355
  const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
15067
15356
  const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
@@ -15090,8 +15379,8 @@ ${summaryContent}`,
15090
15379
  }
15091
15380
  if (chunk.done) break;
15092
15381
  }
15093
- await new Promise((resolve11, reject) => {
15094
- fileStream.end((err) => err ? reject(err) : resolve11());
15382
+ await new Promise((resolve12, reject) => {
15383
+ fileStream.end((err) => err ? reject(err) : resolve12());
15095
15384
  });
15096
15385
  const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
15097
15386
  if (verdict.kind === "reject") {
@@ -15117,7 +15406,7 @@ ${summaryContent}`,
15117
15406
  } catch (err) {
15118
15407
  if (fileStream) {
15119
15408
  try {
15120
- await new Promise((resolve11) => fileStream.end(() => resolve11()));
15409
+ await new Promise((resolve12) => fileStream.end(() => resolve12()));
15121
15410
  } catch {
15122
15411
  }
15123
15412
  }
@@ -15432,6 +15721,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
15432
15721
  " /add-dir [path|remove] \u2014 Add/remove directory from AI context",
15433
15722
  " /mcp [reconnect] \u2014 Show/manage MCP servers",
15434
15723
  " /plugins \u2014 Show loaded plugins",
15724
+ " /plugin <cmd> \u2014 Manage package plugins",
15435
15725
  " /commands \u2014 List custom commands",
15436
15726
  " /doctor \u2014 Health check (API keys, config, MCP)",
15437
15727
  " /bug \u2014 Generate bug report template",
@@ -15530,7 +15820,7 @@ ${lines.join("\n")}` });
15530
15820
  this.sendToolsList();
15531
15821
  this.sendStatus();
15532
15822
  } else if (sub === "reload") {
15533
- this.skillManager.loadSkills();
15823
+ this.skillManager.loadSkills(getActivePluginAssets(this.config.getConfigDir()).skillDirs);
15534
15824
  this.send({ type: "info", message: `\u{1F504} Reloaded ${this.skillManager.listSkills().length} skill(s).` });
15535
15825
  this.sendToolsList();
15536
15826
  } else {
@@ -15749,9 +16039,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15749
16039
  let modifiedFiles = 0;
15750
16040
  const diffLines2 = [];
15751
16041
  for (const [filePath, { earliest }] of fileMap) {
15752
- const currentContent = existsSync25(filePath) ? (() => {
16042
+ const currentContent = existsSync26(filePath) ? (() => {
15753
16043
  try {
15754
- return readFileSync19(filePath, "utf-8");
16044
+ return readFileSync20(filePath, "utf-8");
15755
16045
  } catch {
15756
16046
  return null;
15757
16047
  }
@@ -15852,7 +16142,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15852
16142
  break;
15853
16143
  }
15854
16144
  const sub = args[0]?.toLowerCase();
15855
- const resolve11 = (ref) => {
16145
+ const resolve12 = (ref) => {
15856
16146
  const r = session.resolveBranchRef(ref);
15857
16147
  if (r.ok) return r.id;
15858
16148
  if (r.reason === "ambiguous") {
@@ -15906,7 +16196,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15906
16196
  this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
15907
16197
  break;
15908
16198
  }
15909
- const id = resolve11(ref);
16199
+ const id = resolve12(ref);
15910
16200
  if (!id) break;
15911
16201
  const ok = session.switchBranch(id);
15912
16202
  if (ok) {
@@ -15926,7 +16216,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15926
16216
  this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
15927
16217
  break;
15928
16218
  }
15929
- const id = resolve11(ref);
16219
+ const id = resolve12(ref);
15930
16220
  if (!id) break;
15931
16221
  const ok = session.deleteBranch(id);
15932
16222
  if (ok) {
@@ -15945,7 +16235,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15945
16235
  this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
15946
16236
  break;
15947
16237
  }
15948
- const id = resolve11(ref);
16238
+ const id = resolve12(ref);
15949
16239
  if (!id) break;
15950
16240
  const ok = session.renameBranch(id, title);
15951
16241
  if (ok) {
@@ -15963,7 +16253,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15963
16253
  this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
15964
16254
  break;
15965
16255
  }
15966
- const id = resolve11(ref);
16256
+ const id = resolve12(ref);
15967
16257
  if (!id) break;
15968
16258
  const d = session.diffBranches(id);
15969
16259
  if (!d) {
@@ -16005,7 +16295,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16005
16295
  this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
16006
16296
  break;
16007
16297
  }
16008
- const id = resolve11(ref);
16298
+ const id = resolve12(ref);
16009
16299
  if (!id) break;
16010
16300
  const idx = parseInt(idxStr, 10);
16011
16301
  if (Number.isNaN(idx)) {
@@ -16267,7 +16557,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16267
16557
  case "test": {
16268
16558
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16269
16559
  try {
16270
- const { executeTests } = await import("./run-tests-K7QR5QN4.js");
16560
+ const { executeTests } = await import("./run-tests-NXVVKAK2.js");
16271
16561
  const argStr = args.join(" ").trim();
16272
16562
  let testArgs = {};
16273
16563
  if (argStr) {
@@ -16284,9 +16574,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16284
16574
  // ── /init ───────────────────────────────────────────────────────
16285
16575
  case "init": {
16286
16576
  const cwd = process.cwd();
16287
- const targetPath = join20(cwd, "AICLI.md");
16577
+ const targetPath = join21(cwd, "AICLI.md");
16288
16578
  const force = args.includes("--force");
16289
- if (existsSync25(targetPath) && !force) {
16579
+ if (existsSync26(targetPath) && !force) {
16290
16580
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
16291
16581
  Use /init --force to overwrite.` });
16292
16582
  break;
@@ -16319,19 +16609,19 @@ Use /context reload to load it.` });
16319
16609
  lines.push("**Config Files:**");
16320
16610
  lines.push(` Dir: ${configDir}`);
16321
16611
  const checkFile = (label, filePath) => {
16322
- const exists = existsSync25(filePath);
16612
+ const exists = existsSync26(filePath);
16323
16613
  let extra = "";
16324
16614
  if (exists) {
16325
16615
  try {
16326
- extra = ` (${statSync8(filePath).size} bytes)`;
16616
+ extra = ` (${statSync9(filePath).size} bytes)`;
16327
16617
  } catch {
16328
16618
  }
16329
16619
  }
16330
16620
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
16331
16621
  };
16332
- checkFile("config.json", join20(configDir, "config.json"));
16333
- checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
16334
- checkFile("dev-state.md", join20(configDir, "dev-state.md"));
16622
+ checkFile("config.json", join21(configDir, "config.json"));
16623
+ checkFile("memory.md", join21(configDir, MEMORY_FILE_NAME));
16624
+ checkFile("dev-state.md", join21(configDir, "dev-state.md"));
16335
16625
  lines.push("");
16336
16626
  if (this.mcpManager) {
16337
16627
  lines.push("**MCP Servers:**");
@@ -16434,7 +16724,7 @@ ${this.config.toFormattedJSON()}
16434
16724
  const result = this.contextLoadResult;
16435
16725
  const layers = ["\u{1F4DA} **Context Layers:**", ""];
16436
16726
  if (result && result.layers.length > 0) {
16437
- const labels = { global: "Global ", project: "Project", local: "Local " };
16727
+ const labels = { global: "Global ", project: "Project", plugin: "Plugin ", local: "Local " };
16438
16728
  for (const layer of result.layers) {
16439
16729
  layers.push(` \u2713 ${labels[layer.level] ?? layer.level}: ${layer.displayPath} (${layer.charCount} chars${layer.truncated ? ", truncated" : ""})`);
16440
16730
  }
@@ -16518,12 +16808,12 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
16518
16808
  this.send({ type: "info", message: "\u2713 All added directories removed from context." });
16519
16809
  break;
16520
16810
  }
16521
- const dirPath = resolve9(sub);
16522
- if (!existsSync25(dirPath)) {
16811
+ const dirPath = resolve10(sub);
16812
+ if (!existsSync26(dirPath)) {
16523
16813
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
16524
16814
  break;
16525
16815
  }
16526
- if (!statSync8(dirPath).isDirectory()) {
16816
+ if (!statSync9(dirPath).isDirectory()) {
16527
16817
  this.send({ type: "error", message: `Not a directory: ${dirPath}` });
16528
16818
  break;
16529
16819
  }
@@ -16535,14 +16825,14 @@ It will be included in AI context for subsequent messages.` });
16535
16825
  // ── /commands ───────────────────────────────────────────────────
16536
16826
  case "commands": {
16537
16827
  const configDir = this.config.getConfigDir();
16538
- const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
16539
- if (!existsSync25(commandsDir)) {
16828
+ const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
16829
+ if (!existsSync26(commandsDir)) {
16540
16830
  this.send({ type: "info", message: `No custom commands directory.
16541
16831
  Create: ${commandsDir}/ with .md files.` });
16542
16832
  break;
16543
16833
  }
16544
16834
  try {
16545
- const files = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
16835
+ const files = readdirSync12(commandsDir).filter((f) => f.endsWith(".md"));
16546
16836
  if (files.length === 0) {
16547
16837
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
16548
16838
  Add .md files to create commands.` });
@@ -16559,23 +16849,75 @@ Add .md files to create commands.` });
16559
16849
  }
16560
16850
  break;
16561
16851
  }
16562
- // ── /plugins ────────────────────────────────────────────────────
16852
+ // ── /plugins / /plugin ──────────────────────────────────────────
16563
16853
  case "plugins": {
16564
16854
  const configDir = this.config.getConfigDir();
16565
- const pluginsDir = join20(configDir, PLUGINS_DIR_NAME);
16855
+ const pluginsDir = join21(configDir, PLUGINS_DIR_NAME);
16566
16856
  const pluginTools = this.toolRegistry.listPluginTools();
16857
+ const pluginPackages = listInstalledPlugins(configDir);
16567
16858
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
16568
- if (pluginTools.length === 0) {
16859
+ if (pluginTools.length === 0 && pluginPackages.length === 0) {
16569
16860
  lines.push("No plugins loaded.");
16570
- lines.push("Add .js files to the plugins directory and set allowPlugins:true in config.");
16861
+ lines.push("Use /plugin install <path>, /plugin trust <name>, then /plugin enable <name>.");
16571
16862
  } else {
16572
- for (const t of pluginTools) {
16573
- lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
16863
+ if (pluginPackages.length > 0) {
16864
+ lines.push(`Package plugins (${pluginPackages.length}):`);
16865
+ for (const p of pluginPackages) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
16866
+ lines.push("");
16867
+ }
16868
+ if (pluginTools.length > 0) {
16869
+ lines.push(`Legacy JS plugin tools (${pluginTools.length}):`);
16870
+ for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
16574
16871
  }
16575
16872
  }
16576
16873
  this.send({ type: "info", message: lines.join("\n") });
16577
16874
  break;
16578
16875
  }
16876
+ case "plugin": {
16877
+ const configDir = this.config.getConfigDir();
16878
+ const sub = args[0] ?? "list";
16879
+ try {
16880
+ if (sub === "list") {
16881
+ const plugins = listInstalledPlugins(configDir);
16882
+ const lines = [`\u{1F50C} **Package Plugins**`, `Dir: ${pluginRoot(configDir)}`, ""];
16883
+ if (plugins.length === 0) lines.push("No package plugins installed.");
16884
+ for (const p of plugins) lines.push(` \u2022 ${p.name}@${p.manifest.version} \u2014 ${p.enabled ? "enabled" : "disabled"} / ${p.trusted ? "trusted" : "untrusted"}`);
16885
+ this.send({ type: "info", message: lines.join("\n") });
16886
+ break;
16887
+ }
16888
+ if (sub === "install") {
16889
+ const input = args[1];
16890
+ if (!input) throw new Error("Usage: /plugin install <path>");
16891
+ const plugin = installPlugin(configDir, input);
16892
+ this.send({ type: "info", message: `Installed plugin ${plugin.name}@${plugin.manifest.version}. Run /plugin trust ${plugin.name}, then /plugin enable ${plugin.name}.` });
16893
+ break;
16894
+ }
16895
+ if (sub === "trust") {
16896
+ const name2 = args[1];
16897
+ if (!name2) throw new Error("Usage: /plugin trust <name>");
16898
+ const state2 = trustPlugin(configDir, name2);
16899
+ this.send({ type: "info", message: `Trusted plugin ${state2.name}.` });
16900
+ break;
16901
+ }
16902
+ if (sub === "enable" || sub === "disable") {
16903
+ const name2 = args[1];
16904
+ if (!name2) throw new Error(`Usage: /plugin ${sub} <name>`);
16905
+ const state2 = setPluginEnabled(configDir, name2, sub === "enable");
16906
+ this.send({ type: "info", message: `${sub === "enable" ? "Enabled" : "Disabled"} plugin ${state2.name}. Restart Web UI for changes to take effect.` });
16907
+ break;
16908
+ }
16909
+ if (sub === "inspect") {
16910
+ const name2 = args[1];
16911
+ if (!name2) throw new Error("Usage: /plugin inspect <name>");
16912
+ this.send({ type: "info", message: describePlugin(getInstalledPlugin(configDir, name2)).join("\n") });
16913
+ break;
16914
+ }
16915
+ throw new Error("Usage: /plugin [list|install|enable|disable|inspect|trust]");
16916
+ } catch (err) {
16917
+ this.send({ type: "error", message: err instanceof Error ? err.message : String(err) });
16918
+ }
16919
+ break;
16920
+ }
16579
16921
  // ── /bug ────────────────────────────────────────────────────────
16580
16922
  case "bug": {
16581
16923
  const session = this.sessions.current;
@@ -16618,7 +16960,7 @@ Add .md files to create commands.` });
16618
16960
  return;
16619
16961
  }
16620
16962
  const preDecisions = runLifecycleHooks(
16621
- this.config.get("hooks") ?? void 0,
16963
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
16622
16964
  "PreCompact",
16623
16965
  { sessionId: session.id, messageCount: session.messages.length, instruction },
16624
16966
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -16655,7 +16997,7 @@ Add .md files to create commands.` });
16655
16997
  const removedCount = session.compact(summaryMsg, ackMsg, keepLast);
16656
16998
  const afterCount = session.messages.length;
16657
16999
  runLifecycleHooks(
16658
- this.config.get("hooks") ?? void 0,
17000
+ mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()),
16659
17001
  "PostCompact",
16660
17002
  { sessionId: session.id, beforeCount, afterCount, removedCount, keepLast, instruction },
16661
17003
  { configDir: this.config.getConfigDir(), onSummary: (message) => this.send({ type: "info", message: "[hook] " + message }) }
@@ -16792,7 +17134,7 @@ ${entry.content}`;
16792
17134
  const configDir = this.config.getConfigDir();
16793
17135
  try {
16794
17136
  atomicWriteFileSync(memoryStorePath(configDir), "");
16795
- atomicWriteFileSync(join20(configDir, MEMORY_FILE_NAME), "");
17137
+ atomicWriteFileSync(join21(configDir, MEMORY_FILE_NAME), "");
16796
17138
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
16797
17139
  this.sendMemoryEntries();
16798
17140
  } catch (err) {
@@ -17074,8 +17416,8 @@ async function setupProxy(configProxy) {
17074
17416
  }
17075
17417
 
17076
17418
  // src/web/auth.ts
17077
- import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
17078
- import { join as join21 } from "path";
17419
+ import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, copyFileSync } from "fs";
17420
+ import { join as join22 } from "path";
17079
17421
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
17080
17422
  var USERS_FILE = "users.json";
17081
17423
  var TOKEN_EXPIRY_HOURS = 24;
@@ -17090,7 +17432,7 @@ var AuthManager = class {
17090
17432
  db;
17091
17433
  constructor(baseDir) {
17092
17434
  this.baseDir = baseDir;
17093
- this.usersFile = join21(baseDir, USERS_FILE);
17435
+ this.usersFile = join22(baseDir, USERS_FILE);
17094
17436
  this.db = this.loadOrCreate();
17095
17437
  }
17096
17438
  // ── Public API ─────────────────────────────────────────────────
@@ -17119,9 +17461,9 @@ var AuthManager = class {
17119
17461
  }
17120
17462
  const salt = randomBytes(16).toString("hex");
17121
17463
  const passwordHash = this.hashPassword(password, salt);
17122
- const dataDir = join21(USERS_DIR, username);
17123
- const fullDataDir = join21(this.baseDir, dataDir);
17124
- mkdirSync12(join21(fullDataDir, "history"), { recursive: true });
17464
+ const dataDir = join22(USERS_DIR, username);
17465
+ const fullDataDir = join22(this.baseDir, dataDir);
17466
+ mkdirSync13(join22(fullDataDir, "history"), { recursive: true });
17125
17467
  const user = {
17126
17468
  username,
17127
17469
  passwordHash,
@@ -17236,7 +17578,7 @@ var AuthManager = class {
17236
17578
  getUserDataDir(username) {
17237
17579
  const user = this.db.users.find((u) => u.username === username);
17238
17580
  if (!user) throw new Error(`User not found: ${username}`);
17239
- return join21(this.baseDir, user.dataDir);
17581
+ return join22(this.baseDir, user.dataDir);
17240
17582
  }
17241
17583
  /** List all usernames */
17242
17584
  listUsers() {
@@ -17272,30 +17614,30 @@ var AuthManager = class {
17272
17614
  const err = this.register(username, password);
17273
17615
  if (err) return err;
17274
17616
  const userDir = this.getUserDataDir(username);
17275
- const globalConfig = join21(this.baseDir, "config.json");
17276
- if (existsSync26(globalConfig)) {
17617
+ const globalConfig = join22(this.baseDir, "config.json");
17618
+ if (existsSync27(globalConfig)) {
17277
17619
  try {
17278
- const content = readFileSync20(globalConfig, "utf-8");
17279
- writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
17620
+ const content = readFileSync21(globalConfig, "utf-8");
17621
+ writeFileSync3(join22(userDir, "config.json"), content, "utf-8");
17280
17622
  } catch {
17281
17623
  }
17282
17624
  }
17283
- const globalMemory = join21(this.baseDir, "memory.md");
17284
- if (existsSync26(globalMemory)) {
17625
+ const globalMemory = join22(this.baseDir, "memory.md");
17626
+ if (existsSync27(globalMemory)) {
17285
17627
  try {
17286
- const content = readFileSync20(globalMemory, "utf-8");
17287
- writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
17628
+ const content = readFileSync21(globalMemory, "utf-8");
17629
+ writeFileSync3(join22(userDir, "memory.md"), content, "utf-8");
17288
17630
  } catch {
17289
17631
  }
17290
17632
  }
17291
- const globalHistory = join21(this.baseDir, "history");
17292
- if (existsSync26(globalHistory)) {
17633
+ const globalHistory = join22(this.baseDir, "history");
17634
+ if (existsSync27(globalHistory)) {
17293
17635
  try {
17294
- const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
17295
- const userHistory = join21(userDir, "history");
17636
+ const files = readdirSync13(globalHistory).filter((f) => f.endsWith(".json"));
17637
+ const userHistory = join22(userDir, "history");
17296
17638
  for (const f of files) {
17297
17639
  try {
17298
- copyFileSync(join21(globalHistory, f), join21(userHistory, f));
17640
+ copyFileSync(join22(globalHistory, f), join22(userHistory, f));
17299
17641
  } catch {
17300
17642
  }
17301
17643
  }
@@ -17306,9 +17648,9 @@ var AuthManager = class {
17306
17648
  }
17307
17649
  // ── Private methods ────────────────────────────────────────────
17308
17650
  loadOrCreate() {
17309
- if (existsSync26(this.usersFile)) {
17651
+ if (existsSync27(this.usersFile)) {
17310
17652
  try {
17311
- return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
17653
+ return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
17312
17654
  } catch {
17313
17655
  }
17314
17656
  }
@@ -17324,7 +17666,7 @@ var AuthManager = class {
17324
17666
  this.saveDB(this.db);
17325
17667
  }
17326
17668
  saveDB(db) {
17327
- mkdirSync12(this.baseDir, { recursive: true });
17669
+ mkdirSync13(this.baseDir, { recursive: true });
17328
17670
  atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
17329
17671
  }
17330
17672
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
@@ -17356,11 +17698,11 @@ function getModuleDir() {
17356
17698
  if (typeof import.meta?.url === "string") {
17357
17699
  const url = new URL(import.meta.url);
17358
17700
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
17359
- return dirname7(filePath);
17701
+ return dirname8(filePath);
17360
17702
  }
17361
17703
  } catch {
17362
17704
  }
17363
- return resolve10(".");
17705
+ return resolve11(".");
17364
17706
  }
17365
17707
  async function startWebServer(options = {}) {
17366
17708
  const port = options.port ?? 3e3;
@@ -17395,6 +17737,8 @@ async function startWebServer(options = {}) {
17395
17737
  let mcpManager = null;
17396
17738
  const mcpEnabled = config.get("mcpEnabled") !== false;
17397
17739
  const globalMcpServers = mcpEnabled ? config.get("mcpServers") ?? {} : {};
17740
+ const pluginAssets = getActivePluginAssets(config.getConfigDir());
17741
+ const pluginMcpServers = mcpEnabled ? pluginAssets.mcpServers : {};
17398
17742
  const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
17399
17743
  let projectMcpServers = {};
17400
17744
  if (projectMcpResolved) {
@@ -17407,7 +17751,7 @@ async function startWebServer(options = {}) {
17407
17751
  console.log(` Skipped to prevent unintended RCE. Run "aicli" then "/mcp trust-project" to approve.`);
17408
17752
  }
17409
17753
  }
17410
- const mergedMcpServers = { ...globalMcpServers, ...projectMcpServers };
17754
+ const mergedMcpServers = { ...globalMcpServers, ...pluginMcpServers, ...projectMcpServers };
17411
17755
  if (!mcpEnabled) {
17412
17756
  console.log(" \u{1F50C} MCP: disabled (config.mcpEnabled=false)");
17413
17757
  }
@@ -17425,10 +17769,10 @@ async function startWebServer(options = {}) {
17425
17769
  }
17426
17770
  }
17427
17771
  let skillManager = null;
17428
- const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
17429
- if (existsSync27(skillsDir)) {
17772
+ const skillsDir = join23(config.getConfigDir(), SKILLS_DIR_NAME);
17773
+ if (existsSync28(skillsDir)) {
17430
17774
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
17431
- skillManager.loadSkills();
17775
+ skillManager.loadSkills(pluginAssets.skillDirs);
17432
17776
  const count = skillManager.listSkills().length;
17433
17777
  if (count > 0) {
17434
17778
  console.log(` Skills: ${count} available`);
@@ -17497,18 +17841,18 @@ async function startWebServer(options = {}) {
17497
17841
  next();
17498
17842
  };
17499
17843
  const moduleDir = getModuleDir();
17500
- let clientDir = join22(moduleDir, "web", "client");
17501
- if (!existsSync27(clientDir)) {
17502
- clientDir = join22(moduleDir, "client");
17844
+ let clientDir = join23(moduleDir, "web", "client");
17845
+ if (!existsSync28(clientDir)) {
17846
+ clientDir = join23(moduleDir, "client");
17503
17847
  }
17504
- if (!existsSync27(clientDir)) {
17505
- clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
17848
+ if (!existsSync28(clientDir)) {
17849
+ clientDir = join23(moduleDir, "..", "..", "src", "web", "client");
17506
17850
  }
17507
- if (!existsSync27(clientDir)) {
17508
- clientDir = join22(process.cwd(), "src", "web", "client");
17851
+ if (!existsSync28(clientDir)) {
17852
+ clientDir = join23(process.cwd(), "src", "web", "client");
17509
17853
  }
17510
17854
  console.log(` Static files: ${clientDir}`);
17511
- app.use("/vendor", express.static(join22(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17855
+ app.use("/vendor", express.static(join23(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17512
17856
  app.use(express.static(clientDir));
17513
17857
  app.get("/api/status", (_req, res) => {
17514
17858
  res.json({
@@ -17575,10 +17919,10 @@ async function startWebServer(options = {}) {
17575
17919
  app.get("/api/files", requireAuth, (req, res) => {
17576
17920
  const cwd = process.cwd();
17577
17921
  const prefix = req.query.prefix || "";
17578
- const targetDir = join22(cwd, prefix);
17922
+ const targetDir = join23(cwd, prefix);
17579
17923
  try {
17580
- const canonicalTarget = realpathSync(resolve10(targetDir));
17581
- const canonicalCwd = realpathSync(resolve10(cwd));
17924
+ const canonicalTarget = realpathSync(resolve11(targetDir));
17925
+ const canonicalCwd = realpathSync(resolve11(cwd));
17582
17926
  if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
17583
17927
  res.json({ files: [] });
17584
17928
  return;
@@ -17589,10 +17933,10 @@ async function startWebServer(options = {}) {
17589
17933
  }
17590
17934
  try {
17591
17935
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
17592
- const entries = readdirSync13(targetDir, { withFileTypes: true });
17936
+ const entries = readdirSync14(targetDir, { withFileTypes: true });
17593
17937
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
17594
17938
  name: e.name,
17595
- path: relative4(cwd, join22(targetDir, e.name)).replace(/\\/g, "/"),
17939
+ path: relative4(cwd, join23(targetDir, e.name)).replace(/\\/g, "/"),
17596
17940
  isDir: e.isDirectory()
17597
17941
  }));
17598
17942
  res.json({ files });
@@ -17628,8 +17972,8 @@ async function startWebServer(options = {}) {
17628
17972
  try {
17629
17973
  const authUser = req._authUser;
17630
17974
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
17631
- const filePath = join22(histDir, `${id}.json`);
17632
- if (!existsSync27(filePath)) {
17975
+ const filePath = join23(histDir, `${id}.json`);
17976
+ if (!existsSync28(filePath)) {
17633
17977
  res.status(404).json({ error: "Session not found" });
17634
17978
  return;
17635
17979
  }
@@ -17644,7 +17988,7 @@ async function startWebServer(options = {}) {
17644
17988
  res.status(404).json({ error: "Session not found" });
17645
17989
  return;
17646
17990
  }
17647
- const data = JSON.parse(readFileSync21(filePath, "utf-8"));
17991
+ const data = JSON.parse(readFileSync22(filePath, "utf-8"));
17648
17992
  res.json({ session: data });
17649
17993
  } catch (err) {
17650
17994
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -17657,10 +18001,10 @@ async function startWebServer(options = {}) {
17657
18001
  return;
17658
18002
  }
17659
18003
  const cwd = process.cwd();
17660
- const fullPath = resolve10(join22(cwd, filePath));
18004
+ const fullPath = resolve11(join23(cwd, filePath));
17661
18005
  try {
17662
18006
  const canonicalFull = realpathSync(fullPath);
17663
- const canonicalCwd = realpathSync(resolve10(cwd));
18007
+ const canonicalCwd = realpathSync(resolve11(cwd));
17664
18008
  if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
17665
18009
  res.json({ error: "Access denied" });
17666
18010
  return;
@@ -17670,12 +18014,12 @@ async function startWebServer(options = {}) {
17670
18014
  return;
17671
18015
  }
17672
18016
  try {
17673
- const stat = statSync9(fullPath);
18017
+ const stat = statSync10(fullPath);
17674
18018
  if (stat.size > 512 * 1024) {
17675
18019
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
17676
18020
  return;
17677
18021
  }
17678
- const content = readFileSync21(fullPath, "utf-8");
18022
+ const content = readFileSync22(fullPath, "utf-8");
17679
18023
  res.json({ content, size: stat.size });
17680
18024
  } catch {
17681
18025
  res.json({ error: "Cannot read file" });
@@ -17879,7 +18223,7 @@ async function startWebServer(options = {}) {
17879
18223
  });
17880
18224
  const MAX_PORT_ATTEMPTS = 10;
17881
18225
  let actualPort = port;
17882
- const result = await new Promise((resolve11, reject) => {
18226
+ const result = await new Promise((resolve12, reject) => {
17883
18227
  const tryListen = (attempt) => {
17884
18228
  server.once("error", (err) => {
17885
18229
  if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
@@ -17910,7 +18254,7 @@ async function startWebServer(options = {}) {
17910
18254
  }
17911
18255
  console.log(` Press Ctrl+C to stop
17912
18256
  `);
17913
- resolve11({ port: actualPort, host, url });
18257
+ resolve12({ port: actualPort, host, url });
17914
18258
  });
17915
18259
  };
17916
18260
  tryListen(1);
@@ -17930,17 +18274,17 @@ function resolveProjectMcpPath() {
17930
18274
  const cwd = process.cwd();
17931
18275
  const gitRoot = getGitRoot(cwd);
17932
18276
  const projectRoot = gitRoot ?? cwd;
17933
- const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17934
- return existsSync27(configPath) ? configPath : null;
18277
+ const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
18278
+ return existsSync28(configPath) ? configPath : null;
17935
18279
  }
17936
18280
  function loadProjectMcpConfig() {
17937
18281
  const cwd = process.cwd();
17938
18282
  const gitRoot = getGitRoot(cwd);
17939
18283
  const projectRoot = gitRoot ?? cwd;
17940
- const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17941
- if (!existsSync27(configPath)) return null;
18284
+ const configPath = join23(projectRoot, MCP_PROJECT_CONFIG_NAME);
18285
+ if (!existsSync28(configPath)) return null;
17942
18286
  try {
17943
- const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
18287
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
17944
18288
  return raw.mcpServers ?? raw;
17945
18289
  } catch {
17946
18290
  return null;