@jmylchreest/aide-plugin 0.0.56 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/src/cli/codex-config.ts +428 -0
- package/src/cli/hook.ts +85 -0
- package/src/cli/index.ts +49 -12
- package/src/cli/install.ts +52 -25
- package/src/cli/status.ts +50 -17
- package/src/cli/uninstall.ts +29 -8
- package/src/core/mcp-sync.ts +208 -8
- package/src/core/types.ts +2 -2
- package/src/hooks/agent-cleanup.ts +91 -0
- package/src/hooks/comment-checker.ts +115 -0
- package/src/hooks/context-guard.ts +115 -0
- package/src/hooks/context-pruning.ts +216 -0
- package/src/hooks/hud-updater.ts +180 -0
- package/src/hooks/permission-handler.ts +173 -0
- package/src/hooks/persistence.ts +93 -0
- package/src/hooks/pre-compact.ts +127 -0
- package/src/hooks/pre-tool-enforcer.ts +120 -0
- package/src/hooks/session-end.ts +148 -0
- package/src/hooks/session-start.ts +488 -0
- package/src/hooks/session-summary.ts +147 -0
- package/src/hooks/skill-injector.ts +235 -0
- package/src/hooks/subagent-tracker.ts +525 -0
- package/src/hooks/task-completed.ts +445 -0
- package/src/hooks/tool-tracker.ts +89 -0
- package/src/hooks/write-guard.ts +95 -0
- package/src/lib/hook-utils.ts +53 -1
package/src/cli/install.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Install command — registers aide plugin and MCP server
|
|
2
|
+
* Install command — registers aide plugin and MCP server for OpenCode or Codex CLI.
|
|
3
3
|
*
|
|
4
4
|
* On reinstall, detects and upgrades stale MCP command configurations
|
|
5
5
|
* (e.g. old `aide-wrapper` commands) to the current format.
|
|
@@ -15,51 +15,37 @@ import {
|
|
|
15
15
|
PLUGIN_NAME,
|
|
16
16
|
MCP_SERVER_NAME,
|
|
17
17
|
} from "./config.js";
|
|
18
|
+
import { installCodex, isCodexConfigured } from "./codex-config.js";
|
|
18
19
|
|
|
19
20
|
export interface InstallFlags {
|
|
20
21
|
project?: boolean;
|
|
21
22
|
noMcp?: boolean;
|
|
23
|
+
platform?: "opencode" | "codex";
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/**
|
|
25
|
-
* Check if an existing MCP config has the current expected command format.
|
|
26
|
-
* Returns false if the command is missing, empty, or uses an outdated format.
|
|
27
|
-
*/
|
|
28
26
|
function isMcpCommandCurrent(config: ReturnType<typeof readConfig>): boolean {
|
|
29
27
|
const mcpConfig = config.mcp?.[MCP_SERVER_NAME];
|
|
30
|
-
if (!mcpConfig?.command || mcpConfig.command.length === 0)
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
28
|
+
if (!mcpConfig?.command || mcpConfig.command.length === 0) return false;
|
|
34
29
|
const cmd = mcpConfig.command;
|
|
35
|
-
|
|
36
|
-
// Current format: ["bunx", "-y", "@jmylchreest/aide-plugin", "mcp"]
|
|
37
|
-
if (
|
|
30
|
+
return (
|
|
38
31
|
cmd.length === 4 &&
|
|
39
32
|
cmd[0] === "bunx" &&
|
|
40
33
|
cmd[1] === "-y" &&
|
|
41
34
|
cmd[2] === PLUGIN_NAME &&
|
|
42
35
|
cmd[3] === "mcp"
|
|
43
|
-
)
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return false;
|
|
36
|
+
);
|
|
48
37
|
}
|
|
49
38
|
|
|
50
|
-
|
|
39
|
+
async function installOpenCode(flags: InstallFlags): Promise<void> {
|
|
51
40
|
const configPath = flags.project
|
|
52
41
|
? getProjectConfigPath()
|
|
53
42
|
: getGlobalConfigPath();
|
|
54
43
|
|
|
55
44
|
const scope = flags.project ? "project" : "global";
|
|
56
|
-
console.log(`Installing aide plugin (${scope})...\n`);
|
|
45
|
+
console.log(`Installing aide plugin for OpenCode (${scope})...\n`);
|
|
57
46
|
|
|
58
|
-
// Read existing config
|
|
59
47
|
const existing = readConfig(configPath);
|
|
60
48
|
const before = isAideConfigured(existing);
|
|
61
|
-
|
|
62
|
-
// Check if MCP command needs updating (stale format from older versions)
|
|
63
49
|
const mcpNeedsUpdate =
|
|
64
50
|
!flags.noMcp && before.mcp && !isMcpCommandCurrent(existing);
|
|
65
51
|
|
|
@@ -71,16 +57,13 @@ export async function install(flags: InstallFlags): Promise<void> {
|
|
|
71
57
|
return;
|
|
72
58
|
}
|
|
73
59
|
|
|
74
|
-
// If MCP config is stale, remove it so addAideToConfig will write the current version
|
|
75
60
|
if (mcpNeedsUpdate && existing.mcp) {
|
|
76
61
|
delete existing.mcp[MCP_SERVER_NAME];
|
|
77
62
|
}
|
|
78
63
|
|
|
79
|
-
// Apply changes
|
|
80
64
|
const updated = addAideToConfig(existing, { noMcp: flags.noMcp });
|
|
81
65
|
writeConfig(configPath, updated);
|
|
82
66
|
|
|
83
|
-
// Report what was done
|
|
84
67
|
const after = isAideConfigured(updated);
|
|
85
68
|
console.log(`Updated: ${configPath}\n`);
|
|
86
69
|
|
|
@@ -110,3 +93,47 @@ export async function install(flags: InstallFlags): Promise<void> {
|
|
|
110
93
|
);
|
|
111
94
|
}
|
|
112
95
|
}
|
|
96
|
+
|
|
97
|
+
async function installForCodex(flags: InstallFlags): Promise<void> {
|
|
98
|
+
const scope = flags.project ? "project" : "user";
|
|
99
|
+
console.log(`Installing aide for Codex CLI (${scope})...\n`);
|
|
100
|
+
|
|
101
|
+
const before = isCodexConfigured(scope);
|
|
102
|
+
if (before.mcp && before.hooks) {
|
|
103
|
+
console.log("aide is already configured for Codex CLI");
|
|
104
|
+
console.log(" mcp: registered in config.toml");
|
|
105
|
+
console.log(" hooks: registered in hooks.json");
|
|
106
|
+
console.log("\nNothing to do.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = installCodex(scope);
|
|
111
|
+
|
|
112
|
+
if (result.configWritten) {
|
|
113
|
+
console.log(" + Added aide MCP server to config.toml");
|
|
114
|
+
} else if (before.mcp) {
|
|
115
|
+
console.log(" = MCP server already registered in config.toml");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (result.hooksWritten) {
|
|
119
|
+
console.log(" + Generated hooks.json with aide hooks");
|
|
120
|
+
} else if (before.hooks) {
|
|
121
|
+
console.log(" = Hooks already registered in hooks.json");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log("\nInstallation complete. Start Codex CLI to use aide.");
|
|
125
|
+
|
|
126
|
+
if (!flags.project) {
|
|
127
|
+
console.log(
|
|
128
|
+
"\nThe plugin is installed globally and will apply to all Codex CLI sessions.",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function install(flags: InstallFlags): Promise<void> {
|
|
134
|
+
if (flags.platform === "codex") {
|
|
135
|
+
await installForCodex(flags);
|
|
136
|
+
} else {
|
|
137
|
+
await installOpenCode(flags);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/cli/status.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status command — shows current aide installation status for OpenCode.
|
|
2
|
+
* Status command — shows current aide installation status for OpenCode or Codex CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync } from "fs";
|
|
@@ -9,38 +9,71 @@ import {
|
|
|
9
9
|
isAideConfigured,
|
|
10
10
|
readConfig,
|
|
11
11
|
} from "./config.js";
|
|
12
|
+
import {
|
|
13
|
+
isCodexConfigured,
|
|
14
|
+
getCodexConfigTomlPath,
|
|
15
|
+
getCodexHooksJsonPath,
|
|
16
|
+
} from "./codex-config.js";
|
|
17
|
+
|
|
18
|
+
export interface StatusFlags {
|
|
19
|
+
platform?: "opencode" | "codex";
|
|
20
|
+
}
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
console.log("aide plugin status\n");
|
|
22
|
+
function showOpenCodeStatus(): void {
|
|
23
|
+
console.log("aide plugin status (OpenCode)\n");
|
|
15
24
|
|
|
16
25
|
const globalPath = getGlobalConfigPath();
|
|
17
26
|
const projectPath = getProjectConfigPath();
|
|
18
27
|
|
|
19
|
-
// Global config
|
|
20
28
|
console.log(`Global config: ${globalPath}`);
|
|
21
29
|
if (existsSync(globalPath)) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
console.log(
|
|
25
|
-
` plugin: ${globalStatus.plugin ? "registered" : "not found"}`,
|
|
26
|
-
);
|
|
27
|
-
console.log(` mcp: ${globalStatus.mcp ? "registered" : "not found"}`);
|
|
30
|
+
const s = isAideConfigured(readConfig(globalPath));
|
|
31
|
+
console.log(` plugin: ${s.plugin ? "registered" : "not found"}`);
|
|
32
|
+
console.log(` mcp: ${s.mcp ? "registered" : "not found"}`);
|
|
28
33
|
} else {
|
|
29
34
|
console.log(" (file does not exist)");
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
console.log();
|
|
33
38
|
|
|
34
|
-
// Project config
|
|
35
39
|
console.log(`Project config: ${projectPath}`);
|
|
36
40
|
if (existsSync(projectPath)) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
console.log(
|
|
40
|
-
` plugin: ${projectStatus.plugin ? "registered" : "not found"}`,
|
|
41
|
-
);
|
|
42
|
-
console.log(` mcp: ${projectStatus.mcp ? "registered" : "not found"}`);
|
|
41
|
+
const s = isAideConfigured(readConfig(projectPath));
|
|
42
|
+
console.log(` plugin: ${s.plugin ? "registered" : "not found"}`);
|
|
43
|
+
console.log(` mcp: ${s.mcp ? "registered" : "not found"}`);
|
|
43
44
|
} else {
|
|
44
45
|
console.log(" (file does not exist)");
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
|
|
49
|
+
function showCodexStatus(): void {
|
|
50
|
+
console.log("aide plugin status (Codex CLI)\n");
|
|
51
|
+
|
|
52
|
+
const userConfig = getCodexConfigTomlPath("user");
|
|
53
|
+
const userHooks = getCodexHooksJsonPath("user");
|
|
54
|
+
const userStatus = isCodexConfigured("user");
|
|
55
|
+
|
|
56
|
+
console.log(`User config: ${userConfig}`);
|
|
57
|
+
console.log(`User hooks: ${userHooks}`);
|
|
58
|
+
console.log(` mcp: ${userStatus.mcp ? "registered" : "not found"}`);
|
|
59
|
+
console.log(` hooks: ${userStatus.hooks ? "registered" : "not found"}`);
|
|
60
|
+
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
const projectConfig = getCodexConfigTomlPath("project");
|
|
64
|
+
const projectHooks = getCodexHooksJsonPath("project");
|
|
65
|
+
const projectStatus = isCodexConfigured("project");
|
|
66
|
+
|
|
67
|
+
console.log(`Project config: ${projectConfig}`);
|
|
68
|
+
console.log(`Project hooks: ${projectHooks}`);
|
|
69
|
+
console.log(` mcp: ${projectStatus.mcp ? "registered" : "not found"}`);
|
|
70
|
+
console.log(` hooks: ${projectStatus.hooks ? "registered" : "not found"}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function status(flags?: StatusFlags): Promise<void> {
|
|
74
|
+
if (flags?.platform === "codex") {
|
|
75
|
+
showCodexStatus();
|
|
76
|
+
} else {
|
|
77
|
+
showOpenCodeStatus();
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/cli/uninstall.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Uninstall command — removes aide plugin and MCP server from OpenCode config.
|
|
2
|
+
* Uninstall command — removes aide plugin and MCP server from OpenCode or Codex CLI config.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -10,18 +10,20 @@ import {
|
|
|
10
10
|
removeAideFromConfig,
|
|
11
11
|
writeConfig,
|
|
12
12
|
} from "./config.js";
|
|
13
|
+
import { uninstallCodex, isCodexConfigured } from "./codex-config.js";
|
|
13
14
|
|
|
14
15
|
export interface UninstallFlags {
|
|
15
16
|
project?: boolean;
|
|
17
|
+
platform?: "opencode" | "codex";
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
async function uninstallOpenCode(flags: UninstallFlags): Promise<void> {
|
|
19
21
|
const configPath = flags.project
|
|
20
22
|
? getProjectConfigPath()
|
|
21
23
|
: getGlobalConfigPath();
|
|
22
24
|
|
|
23
25
|
const scope = flags.project ? "project" : "global";
|
|
24
|
-
console.log(`Uninstalling aide plugin (${scope})...\n`);
|
|
26
|
+
console.log(`Uninstalling aide plugin from OpenCode (${scope})...\n`);
|
|
25
27
|
|
|
26
28
|
const existing = readConfig(configPath);
|
|
27
29
|
const before = isAideConfigured(existing);
|
|
@@ -36,13 +38,32 @@ export async function uninstall(flags: UninstallFlags): Promise<void> {
|
|
|
36
38
|
writeConfig(configPath, updated);
|
|
37
39
|
|
|
38
40
|
console.log(`Updated: ${configPath}\n`);
|
|
41
|
+
if (before.plugin) console.log(` - Removed aide plugin from plugin array`);
|
|
42
|
+
if (before.mcp) console.log(` - Removed aide MCP server`);
|
|
43
|
+
console.log("\nUninstallation complete.");
|
|
44
|
+
}
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
async function uninstallFromCodex(flags: UninstallFlags): Promise<void> {
|
|
47
|
+
const scope = flags.project ? "project" : "user";
|
|
48
|
+
console.log(`Uninstalling aide from Codex CLI (${scope})...\n`);
|
|
49
|
+
|
|
50
|
+
const before = isCodexConfigured(scope);
|
|
51
|
+
if (!before.mcp && !before.hooks) {
|
|
52
|
+
console.log("aide is not configured for Codex CLI.");
|
|
53
|
+
console.log("\nNothing to do.");
|
|
54
|
+
return;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
const result = uninstallCodex(scope);
|
|
58
|
+
if (result.configRemoved) console.log(" - Removed aide MCP server from config.toml");
|
|
59
|
+
if (result.hooksRemoved) console.log(" - Removed aide hooks from hooks.json");
|
|
47
60
|
console.log("\nUninstallation complete.");
|
|
48
61
|
}
|
|
62
|
+
|
|
63
|
+
export async function uninstall(flags: UninstallFlags): Promise<void> {
|
|
64
|
+
if (flags.platform === "codex") {
|
|
65
|
+
await uninstallFromCodex(flags);
|
|
66
|
+
} else {
|
|
67
|
+
await uninstallOpenCode(flags);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/core/mcp-sync.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Supports:
|
|
9
9
|
* - Claude Code: ~/.claude.json (user), .mcp.json (project) (reads legacy ~/.mcp.json)
|
|
10
10
|
* - OpenCode: ~/.config/opencode/opencode.json (user), ./opencode.json (project)
|
|
11
|
+
* - Codex CLI: ~/.codex/config.toml (user), .codex/config.toml (project)
|
|
11
12
|
* - Aide canonical: ~/.aide/config/mcp.json (user), .aide/config/mcp.json (project)
|
|
12
13
|
*
|
|
13
14
|
* Uses a journal to detect intentional deletions from the aide canonical file,
|
|
@@ -17,19 +18,84 @@
|
|
|
17
18
|
import {
|
|
18
19
|
existsSync,
|
|
19
20
|
readFileSync,
|
|
21
|
+
readdirSync,
|
|
20
22
|
writeFileSync,
|
|
21
23
|
mkdirSync,
|
|
22
24
|
statSync,
|
|
23
25
|
} from "fs";
|
|
24
26
|
import { join, dirname } from "path";
|
|
25
27
|
import { homedir } from "os";
|
|
28
|
+
import * as TOML from "smol-toml";
|
|
26
29
|
|
|
27
30
|
// =============================================================================
|
|
28
31
|
// Types
|
|
29
32
|
// =============================================================================
|
|
30
33
|
|
|
31
34
|
/** Platform identifier for the current assistant. */
|
|
32
|
-
export type McpPlatform = "claude-code" | "opencode";
|
|
35
|
+
export type McpPlatform = "claude-code" | "opencode" | "codex";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Discover MCP server names managed by installed Claude Code plugins.
|
|
39
|
+
*
|
|
40
|
+
* Scans plugin cache and marketplace directories for plugin.json files
|
|
41
|
+
* and returns the set of MCP server names they define. These servers
|
|
42
|
+
* must not be synced from assistant configs — doing so would override
|
|
43
|
+
* the plugin's own definition and bypass CLAUDE_PLUGIN_ROOT.
|
|
44
|
+
*/
|
|
45
|
+
function getPluginManagedServers(): Set<string> {
|
|
46
|
+
const names = new Set<string>();
|
|
47
|
+
const pluginsDir = join(homedir(), ".claude", "plugins");
|
|
48
|
+
|
|
49
|
+
// Scan both cache (installed plugins) and marketplaces (git-cloned plugins)
|
|
50
|
+
const searchDirs = [
|
|
51
|
+
join(pluginsDir, "cache"),
|
|
52
|
+
join(pluginsDir, "marketplaces"),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const baseDir of searchDirs) {
|
|
56
|
+
if (!existsSync(baseDir)) continue;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Walk up to 3 levels deep to find .claude-plugin/plugin.json
|
|
60
|
+
// cache: <marketplace>/<plugin>/<version>/.claude-plugin/plugin.json
|
|
61
|
+
// marketplaces: <name>/.claude-plugin/plugin.json
|
|
62
|
+
const findPluginJsons = (dir: string, depth: number): string[] => {
|
|
63
|
+
if (depth > 3) return [];
|
|
64
|
+
const results: string[] = [];
|
|
65
|
+
const pluginJson = join(dir, ".claude-plugin", "plugin.json");
|
|
66
|
+
if (existsSync(pluginJson)) results.push(pluginJson);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
70
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
71
|
+
results.push(...findPluginJsons(join(dir, entry.name), depth + 1));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Permission or read error — skip
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const pluginJsonPath of findPluginJsons(baseDir, 0)) {
|
|
81
|
+
try {
|
|
82
|
+
const content = JSON.parse(readFileSync(pluginJsonPath, "utf-8"));
|
|
83
|
+
if (content.mcpServers && typeof content.mcpServers === "object") {
|
|
84
|
+
for (const serverName of Object.keys(content.mcpServers)) {
|
|
85
|
+
names.add(serverName);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Skip unparseable plugin.json files
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Directory read error — skip
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return names;
|
|
98
|
+
}
|
|
33
99
|
|
|
34
100
|
/** Scope level for config files. */
|
|
35
101
|
export type McpScope = "user" | "project";
|
|
@@ -139,6 +205,11 @@ function getAssistantReadPaths(
|
|
|
139
205
|
? [join(homedir(), ".config", "opencode", "opencode.json")]
|
|
140
206
|
: [join(cwd, "opencode.json")];
|
|
141
207
|
}
|
|
208
|
+
if (platform === "codex") {
|
|
209
|
+
return scope === "user"
|
|
210
|
+
? [join(homedir(), ".codex", "config.toml")]
|
|
211
|
+
: [join(cwd, ".codex", "config.toml")];
|
|
212
|
+
}
|
|
142
213
|
return [];
|
|
143
214
|
}
|
|
144
215
|
|
|
@@ -157,6 +228,11 @@ function getAssistantWritePaths(
|
|
|
157
228
|
? [join(homedir(), ".config", "opencode", "opencode.json")]
|
|
158
229
|
: [join(cwd, "opencode.json")];
|
|
159
230
|
}
|
|
231
|
+
if (platform === "codex") {
|
|
232
|
+
return scope === "user"
|
|
233
|
+
? [join(homedir(), ".codex", "config.toml")]
|
|
234
|
+
: [join(cwd, ".codex", "config.toml")];
|
|
235
|
+
}
|
|
160
236
|
return [];
|
|
161
237
|
}
|
|
162
238
|
|
|
@@ -304,6 +380,59 @@ function readOpenCodeConfig(path: string): Record<string, CanonicalMcpServer> {
|
|
|
304
380
|
}
|
|
305
381
|
}
|
|
306
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Read Codex CLI MCP config from config.toml.
|
|
385
|
+
*
|
|
386
|
+
* Format:
|
|
387
|
+
* ```toml
|
|
388
|
+
* [mcp_servers.name]
|
|
389
|
+
* command = "npx"
|
|
390
|
+
* args = ["package-name"]
|
|
391
|
+
*
|
|
392
|
+
* [mcp_servers.name.env]
|
|
393
|
+
* KEY = "value"
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
function readCodexConfig(path: string): Record<string, CanonicalMcpServer> {
|
|
397
|
+
if (!existsSync(path)) return {};
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const raw = TOML.parse(readFileSync(path, "utf-8"));
|
|
401
|
+
const servers: Record<string, CanonicalMcpServer> = {};
|
|
402
|
+
|
|
403
|
+
const mcpServers = raw.mcp_servers as
|
|
404
|
+
| Record<string, Record<string, unknown>>
|
|
405
|
+
| undefined;
|
|
406
|
+
if (
|
|
407
|
+
typeof mcpServers !== "object" ||
|
|
408
|
+
mcpServers === null ||
|
|
409
|
+
Array.isArray(mcpServers)
|
|
410
|
+
)
|
|
411
|
+
return {};
|
|
412
|
+
|
|
413
|
+
for (const [name, def] of Object.entries(mcpServers)) {
|
|
414
|
+
const hasUrl = typeof def.url === "string";
|
|
415
|
+
const isRemote = hasUrl;
|
|
416
|
+
|
|
417
|
+
servers[name] = {
|
|
418
|
+
name,
|
|
419
|
+
type: isRemote ? "remote" : "local",
|
|
420
|
+
transport: isRemote ? "http" : undefined,
|
|
421
|
+
command: def.command as string | undefined,
|
|
422
|
+
args: def.args as string[] | undefined,
|
|
423
|
+
url: def.url as string | undefined,
|
|
424
|
+
env: (def.env as Record<string, string>) || undefined,
|
|
425
|
+
headers: def.headers as Record<string, string> | undefined,
|
|
426
|
+
enabled: true,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return servers;
|
|
431
|
+
} catch {
|
|
432
|
+
return {};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
307
436
|
/**
|
|
308
437
|
* Read MCP servers from a specific assistant's config file.
|
|
309
438
|
*/
|
|
@@ -313,6 +442,7 @@ function readAssistantConfig(
|
|
|
313
442
|
): Record<string, CanonicalMcpServer> {
|
|
314
443
|
if (platform === "claude-code") return readClaudeConfig(path);
|
|
315
444
|
if (platform === "opencode") return readOpenCodeConfig(path);
|
|
445
|
+
if (platform === "codex") return readCodexConfig(path);
|
|
316
446
|
return {};
|
|
317
447
|
}
|
|
318
448
|
|
|
@@ -446,6 +576,59 @@ function writeOpenCodeConfig(
|
|
|
446
576
|
writeFileSync(path, JSON.stringify(existing, null, 2) + "\n");
|
|
447
577
|
}
|
|
448
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Write canonical servers to a Codex CLI config.toml file.
|
|
581
|
+
*
|
|
582
|
+
* Preserves all non-mcp_servers content in the file.
|
|
583
|
+
*/
|
|
584
|
+
function writeCodexConfig(
|
|
585
|
+
path: string,
|
|
586
|
+
servers: Record<string, CanonicalMcpServer>,
|
|
587
|
+
): void {
|
|
588
|
+
const dir = dirname(path);
|
|
589
|
+
if (!existsSync(dir)) {
|
|
590
|
+
mkdirSync(dir, { recursive: true });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Read existing file to preserve non-mcp_servers keys
|
|
594
|
+
let existing: Record<string, unknown> = {};
|
|
595
|
+
if (existsSync(path)) {
|
|
596
|
+
try {
|
|
597
|
+
existing = TOML.parse(readFileSync(path, "utf-8")) as Record<
|
|
598
|
+
string,
|
|
599
|
+
unknown
|
|
600
|
+
>;
|
|
601
|
+
} catch {
|
|
602
|
+
// Start fresh
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const mcpServers: Record<string, Record<string, unknown>> = {};
|
|
607
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
608
|
+
const entry: Record<string, unknown> = {};
|
|
609
|
+
|
|
610
|
+
if (server.type === "remote") {
|
|
611
|
+
if (server.url) entry.url = server.url;
|
|
612
|
+
if (server.headers) entry.headers = server.headers;
|
|
613
|
+
} else {
|
|
614
|
+
if (server.command) entry.command = server.command;
|
|
615
|
+
if (server.args) entry.args = server.args;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
619
|
+
entry.env = server.env;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
mcpServers[name] = entry;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
existing.mcp_servers = mcpServers;
|
|
626
|
+
writeFileSync(
|
|
627
|
+
path,
|
|
628
|
+
TOML.stringify(existing as Record<string, unknown>) + "\n",
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
449
632
|
/**
|
|
450
633
|
* Write canonical servers to the current assistant's config file.
|
|
451
634
|
*/
|
|
@@ -456,6 +639,7 @@ function writeAssistantConfig(
|
|
|
456
639
|
): void {
|
|
457
640
|
if (platform === "claude-code") writeClaudeConfig(path, servers);
|
|
458
641
|
else if (platform === "opencode") writeOpenCodeConfig(path, servers);
|
|
642
|
+
else if (platform === "codex") writeCodexConfig(path, servers);
|
|
459
643
|
}
|
|
460
644
|
|
|
461
645
|
// =============================================================================
|
|
@@ -579,7 +763,7 @@ interface McpSource {
|
|
|
579
763
|
* Gather all MCP config source files for a given scope.
|
|
580
764
|
*/
|
|
581
765
|
function gatherSources(scope: McpScope, cwd: string): McpSource[] {
|
|
582
|
-
const platforms: McpPlatform[] = ["claude-code", "opencode"];
|
|
766
|
+
const platforms: McpPlatform[] = ["claude-code", "opencode", "codex"];
|
|
583
767
|
const sources: McpSource[] = [];
|
|
584
768
|
|
|
585
769
|
const aidePath =
|
|
@@ -782,7 +966,7 @@ function syncScope(
|
|
|
782
966
|
if (!bestPresent) result.skipped++;
|
|
783
967
|
}
|
|
784
968
|
|
|
785
|
-
// Write to aide canonical config (if changed)
|
|
969
|
+
// Write to aide canonical config (if changed) — includes ALL servers
|
|
786
970
|
const sortedStringify = (obj: object) =>
|
|
787
971
|
JSON.stringify(obj, Object.keys(obj).sort());
|
|
788
972
|
const aideChanged =
|
|
@@ -793,21 +977,37 @@ function syncScope(
|
|
|
793
977
|
result.modified = true;
|
|
794
978
|
}
|
|
795
979
|
|
|
980
|
+
// For Claude Code, exclude servers managed by plugins — these are
|
|
981
|
+
// defined in plugin.json and must not be written to assistant configs
|
|
982
|
+
// (doing so overrides the plugin's definition and bypasses
|
|
983
|
+
// CLAUDE_PLUGIN_ROOT). The aide canonical config keeps them so they
|
|
984
|
+
// can still sync to non-plugin assistants like OpenCode.
|
|
985
|
+
let assistantServers = finalServers;
|
|
986
|
+
if (platform === "claude-code") {
|
|
987
|
+
const pluginManaged = getPluginManagedServers();
|
|
988
|
+
if (pluginManaged.size > 0) {
|
|
989
|
+
assistantServers = { ...finalServers };
|
|
990
|
+
for (const name of pluginManaged) {
|
|
991
|
+
delete assistantServers[name];
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
796
996
|
// Write to current assistant's config
|
|
797
997
|
const assistantPaths = getAssistantWritePaths(platform, scope, cwd);
|
|
798
998
|
for (const p of assistantPaths) {
|
|
799
999
|
const existingAssistant = readAssistantConfig(platform, p);
|
|
800
1000
|
const assistantChanged =
|
|
801
|
-
sortedStringify(existingAssistant) !== sortedStringify(
|
|
1001
|
+
sortedStringify(existingAssistant) !== sortedStringify(assistantServers);
|
|
802
1002
|
|
|
803
1003
|
if (assistantChanged) {
|
|
804
|
-
writeAssistantConfig(platform, p,
|
|
1004
|
+
writeAssistantConfig(platform, p, assistantServers);
|
|
805
1005
|
result.modified = true;
|
|
806
1006
|
}
|
|
807
1007
|
}
|
|
808
1008
|
|
|
809
|
-
result.serversWritten = Object.keys(
|
|
810
|
-
result.serverNames = Object.keys(
|
|
1009
|
+
result.serversWritten = Object.keys(assistantServers).length;
|
|
1010
|
+
result.serverNames = Object.keys(assistantServers);
|
|
811
1011
|
|
|
812
1012
|
return result;
|
|
813
1013
|
}
|
|
@@ -824,7 +1024,7 @@ function syncScope(
|
|
|
824
1024
|
* handles intentional deletions via a journal, and writes the result to
|
|
825
1025
|
* both the aide canonical config and the current assistant's config files.
|
|
826
1026
|
*
|
|
827
|
-
* @param platform - The current assistant platform ("claude-code" or "
|
|
1027
|
+
* @param platform - The current assistant platform ("claude-code", "opencode", or "codex")
|
|
828
1028
|
* @param cwd - The project working directory
|
|
829
1029
|
* @returns Combined sync results for both user and project scopes
|
|
830
1030
|
*/
|
package/src/core/types.ts
CHANGED
|
@@ -111,7 +111,7 @@ export interface Skill {
|
|
|
111
111
|
path: string;
|
|
112
112
|
triggers: string[];
|
|
113
113
|
description?: string;
|
|
114
|
-
/** Optional platform restriction. If set, only matched on listed platforms ("opencode", "claude-code"). */
|
|
114
|
+
/** Optional platform restriction. If set, only matched on listed platforms ("opencode", "claude-code", "codex"). */
|
|
115
115
|
platforms?: string[];
|
|
116
116
|
/** Optional binary requirement. If set, skill is only matched when all listed binaries exist on PATH. */
|
|
117
117
|
requires_binary?: string[];
|
|
@@ -156,7 +156,7 @@ export const MAX_PERSISTENCE_ITERATIONS = 20;
|
|
|
156
156
|
* Identifies which host platform aide is running in.
|
|
157
157
|
* Used for platform-specific behavior like binary discovery or context injection.
|
|
158
158
|
*/
|
|
159
|
-
export type AidePlatform = "claude-code" | "opencode" | "unknown";
|
|
159
|
+
export type AidePlatform = "claude-code" | "opencode" | "codex" | "unknown";
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
162
|
* Options for finding the aide binary.
|