@jmylchreest/aide-plugin 0.0.55 → 0.0.57

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.
@@ -2,7 +2,6 @@
2
2
  /**
3
3
  * aide-wrapper.ts - Ensures aide binary exists before executing
4
4
  *
5
- * Cross-platform TypeScript replacement for aide-wrapper.sh.
6
5
  * Called by an assistant's MCP server configuration.
7
6
  * Finds the aide binary, downloads it if missing, then delegates to it.
8
7
  *
@@ -333,7 +332,15 @@ if (!binaryExists()) {
333
332
  );
334
333
  }
335
334
  } else {
336
- log(`Release binary v${binaryVersion ?? "unknown"}`);
335
+ const pluginVersion = getPluginVersion();
336
+ if (pluginVersion && binaryVersion && !versionGte(binaryVersion, pluginVersion)) {
337
+ needsDownload = true;
338
+ log(
339
+ `Release binary v${binaryVersion} is older than plugin v${pluginVersion}, re-downloading`,
340
+ );
341
+ } else {
342
+ log(`Release binary v${binaryVersion ?? "unknown"} (plugin v${pluginVersion ?? "unknown"})`);
343
+ }
337
344
  }
338
345
  }
339
346
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmylchreest/aide-plugin",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "aide plugin for OpenCode — multi-agent orchestration, memory, skills, and persistence",
5
5
  "type": "module",
6
6
  "main": "./src/opencode/index.ts",
@@ -17,9 +17,11 @@
17
17
  import {
18
18
  existsSync,
19
19
  readFileSync,
20
+ readdirSync,
20
21
  writeFileSync,
21
22
  mkdirSync,
22
23
  statSync,
24
+ unlinkSync,
23
25
  } from "fs";
24
26
  import { join, dirname } from "path";
25
27
  import { homedir } from "os";
@@ -31,6 +33,69 @@ import { homedir } from "os";
31
33
  /** Platform identifier for the current assistant. */
32
34
  export type McpPlatform = "claude-code" | "opencode";
33
35
 
36
+ /**
37
+ * Discover MCP server names managed by installed Claude Code plugins.
38
+ *
39
+ * Scans plugin cache and marketplace directories for plugin.json files
40
+ * and returns the set of MCP server names they define. These servers
41
+ * must not be synced from assistant configs — doing so would override
42
+ * the plugin's own definition and bypass CLAUDE_PLUGIN_ROOT.
43
+ */
44
+ function getPluginManagedServers(): Set<string> {
45
+ const names = new Set<string>();
46
+ const pluginsDir = join(homedir(), ".claude", "plugins");
47
+
48
+ // Scan both cache (installed plugins) and marketplaces (git-cloned plugins)
49
+ const searchDirs = [
50
+ join(pluginsDir, "cache"),
51
+ join(pluginsDir, "marketplaces"),
52
+ ];
53
+
54
+ for (const baseDir of searchDirs) {
55
+ if (!existsSync(baseDir)) continue;
56
+
57
+ try {
58
+ // Walk up to 3 levels deep to find .claude-plugin/plugin.json
59
+ // cache: <marketplace>/<plugin>/<version>/.claude-plugin/plugin.json
60
+ // marketplaces: <name>/.claude-plugin/plugin.json
61
+ const findPluginJsons = (dir: string, depth: number): string[] => {
62
+ if (depth > 3) return [];
63
+ const results: string[] = [];
64
+ const pluginJson = join(dir, ".claude-plugin", "plugin.json");
65
+ if (existsSync(pluginJson)) results.push(pluginJson);
66
+
67
+ try {
68
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
69
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
70
+ results.push(...findPluginJsons(join(dir, entry.name), depth + 1));
71
+ }
72
+ }
73
+ } catch {
74
+ // Permission or read error — skip
75
+ }
76
+ return results;
77
+ };
78
+
79
+ for (const pluginJsonPath of findPluginJsons(baseDir, 0)) {
80
+ try {
81
+ const content = JSON.parse(readFileSync(pluginJsonPath, "utf-8"));
82
+ if (content.mcpServers && typeof content.mcpServers === "object") {
83
+ for (const serverName of Object.keys(content.mcpServers)) {
84
+ names.add(serverName);
85
+ }
86
+ }
87
+ } catch {
88
+ // Skip unparseable plugin.json files
89
+ }
90
+ }
91
+ } catch {
92
+ // Directory read error — skip
93
+ }
94
+ }
95
+
96
+ return names;
97
+ }
98
+
34
99
  /** Scope level for config files. */
35
100
  export type McpScope = "user" | "project";
36
101
 
@@ -782,7 +847,7 @@ function syncScope(
782
847
  if (!bestPresent) result.skipped++;
783
848
  }
784
849
 
785
- // Write to aide canonical config (if changed)
850
+ // Write to aide canonical config (if changed) — includes ALL servers
786
851
  const sortedStringify = (obj: object) =>
787
852
  JSON.stringify(obj, Object.keys(obj).sort());
788
853
  const aideChanged =
@@ -793,21 +858,43 @@ function syncScope(
793
858
  result.modified = true;
794
859
  }
795
860
 
861
+ // For Claude Code, exclude servers managed by plugins — these are
862
+ // defined in plugin.json and must not be written to assistant configs
863
+ // (doing so overrides the plugin's definition and bypasses
864
+ // CLAUDE_PLUGIN_ROOT). The aide canonical config keeps them so they
865
+ // can still sync to non-plugin assistants like OpenCode.
866
+ let assistantServers = finalServers;
867
+ if (platform === "claude-code") {
868
+ const pluginManaged = getPluginManagedServers();
869
+ if (pluginManaged.size > 0) {
870
+ assistantServers = { ...finalServers };
871
+ for (const name of pluginManaged) {
872
+ delete assistantServers[name];
873
+ }
874
+ }
875
+ }
876
+
796
877
  // Write to current assistant's config
797
878
  const assistantPaths = getAssistantWritePaths(platform, scope, cwd);
798
879
  for (const p of assistantPaths) {
799
880
  const existingAssistant = readAssistantConfig(platform, p);
800
881
  const assistantChanged =
801
- sortedStringify(existingAssistant) !== sortedStringify(finalServers);
882
+ sortedStringify(existingAssistant) !== sortedStringify(assistantServers);
802
883
 
803
884
  if (assistantChanged) {
804
- writeAssistantConfig(platform, p, finalServers);
885
+ if (Object.keys(assistantServers).length === 0 && existsSync(p)) {
886
+ // No servers to write — remove the file rather than leaving
887
+ // an empty mcpServers block that the assistant still loads.
888
+ try { unlinkSync(p); } catch { /* ignore */ }
889
+ } else if (Object.keys(assistantServers).length > 0) {
890
+ writeAssistantConfig(platform, p, assistantServers);
891
+ }
805
892
  result.modified = true;
806
893
  }
807
894
  }
808
895
 
809
- result.serversWritten = Object.keys(finalServers).length;
810
- result.serverNames = Object.keys(finalServers);
896
+ result.serversWritten = Object.keys(assistantServers).length;
897
+ result.serverNames = Object.keys(assistantServers);
811
898
 
812
899
  return result;
813
900
  }