@jmylchreest/aide-plugin 0.0.57 → 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 +124 -11
- 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,
|
|
@@ -21,17 +22,17 @@ import {
|
|
|
21
22
|
writeFileSync,
|
|
22
23
|
mkdirSync,
|
|
23
24
|
statSync,
|
|
24
|
-
unlinkSync,
|
|
25
25
|
} from "fs";
|
|
26
26
|
import { join, dirname } from "path";
|
|
27
27
|
import { homedir } from "os";
|
|
28
|
+
import * as TOML from "smol-toml";
|
|
28
29
|
|
|
29
30
|
// =============================================================================
|
|
30
31
|
// Types
|
|
31
32
|
// =============================================================================
|
|
32
33
|
|
|
33
34
|
/** Platform identifier for the current assistant. */
|
|
34
|
-
export type McpPlatform = "claude-code" | "opencode";
|
|
35
|
+
export type McpPlatform = "claude-code" | "opencode" | "codex";
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* Discover MCP server names managed by installed Claude Code plugins.
|
|
@@ -204,6 +205,11 @@ function getAssistantReadPaths(
|
|
|
204
205
|
? [join(homedir(), ".config", "opencode", "opencode.json")]
|
|
205
206
|
: [join(cwd, "opencode.json")];
|
|
206
207
|
}
|
|
208
|
+
if (platform === "codex") {
|
|
209
|
+
return scope === "user"
|
|
210
|
+
? [join(homedir(), ".codex", "config.toml")]
|
|
211
|
+
: [join(cwd, ".codex", "config.toml")];
|
|
212
|
+
}
|
|
207
213
|
return [];
|
|
208
214
|
}
|
|
209
215
|
|
|
@@ -222,6 +228,11 @@ function getAssistantWritePaths(
|
|
|
222
228
|
? [join(homedir(), ".config", "opencode", "opencode.json")]
|
|
223
229
|
: [join(cwd, "opencode.json")];
|
|
224
230
|
}
|
|
231
|
+
if (platform === "codex") {
|
|
232
|
+
return scope === "user"
|
|
233
|
+
? [join(homedir(), ".codex", "config.toml")]
|
|
234
|
+
: [join(cwd, ".codex", "config.toml")];
|
|
235
|
+
}
|
|
225
236
|
return [];
|
|
226
237
|
}
|
|
227
238
|
|
|
@@ -369,6 +380,59 @@ function readOpenCodeConfig(path: string): Record<string, CanonicalMcpServer> {
|
|
|
369
380
|
}
|
|
370
381
|
}
|
|
371
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
|
+
|
|
372
436
|
/**
|
|
373
437
|
* Read MCP servers from a specific assistant's config file.
|
|
374
438
|
*/
|
|
@@ -378,6 +442,7 @@ function readAssistantConfig(
|
|
|
378
442
|
): Record<string, CanonicalMcpServer> {
|
|
379
443
|
if (platform === "claude-code") return readClaudeConfig(path);
|
|
380
444
|
if (platform === "opencode") return readOpenCodeConfig(path);
|
|
445
|
+
if (platform === "codex") return readCodexConfig(path);
|
|
381
446
|
return {};
|
|
382
447
|
}
|
|
383
448
|
|
|
@@ -511,6 +576,59 @@ function writeOpenCodeConfig(
|
|
|
511
576
|
writeFileSync(path, JSON.stringify(existing, null, 2) + "\n");
|
|
512
577
|
}
|
|
513
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
|
+
|
|
514
632
|
/**
|
|
515
633
|
* Write canonical servers to the current assistant's config file.
|
|
516
634
|
*/
|
|
@@ -521,6 +639,7 @@ function writeAssistantConfig(
|
|
|
521
639
|
): void {
|
|
522
640
|
if (platform === "claude-code") writeClaudeConfig(path, servers);
|
|
523
641
|
else if (platform === "opencode") writeOpenCodeConfig(path, servers);
|
|
642
|
+
else if (platform === "codex") writeCodexConfig(path, servers);
|
|
524
643
|
}
|
|
525
644
|
|
|
526
645
|
// =============================================================================
|
|
@@ -644,7 +763,7 @@ interface McpSource {
|
|
|
644
763
|
* Gather all MCP config source files for a given scope.
|
|
645
764
|
*/
|
|
646
765
|
function gatherSources(scope: McpScope, cwd: string): McpSource[] {
|
|
647
|
-
const platforms: McpPlatform[] = ["claude-code", "opencode"];
|
|
766
|
+
const platforms: McpPlatform[] = ["claude-code", "opencode", "codex"];
|
|
648
767
|
const sources: McpSource[] = [];
|
|
649
768
|
|
|
650
769
|
const aidePath =
|
|
@@ -882,13 +1001,7 @@ function syncScope(
|
|
|
882
1001
|
sortedStringify(existingAssistant) !== sortedStringify(assistantServers);
|
|
883
1002
|
|
|
884
1003
|
if (assistantChanged) {
|
|
885
|
-
|
|
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
|
-
}
|
|
1004
|
+
writeAssistantConfig(platform, p, assistantServers);
|
|
892
1005
|
result.modified = true;
|
|
893
1006
|
}
|
|
894
1007
|
}
|
|
@@ -911,7 +1024,7 @@ function syncScope(
|
|
|
911
1024
|
* handles intentional deletions via a journal, and writes the result to
|
|
912
1025
|
* both the aide canonical config and the current assistant's config files.
|
|
913
1026
|
*
|
|
914
|
-
* @param platform - The current assistant platform ("claude-code" or "
|
|
1027
|
+
* @param platform - The current assistant platform ("claude-code", "opencode", or "codex")
|
|
915
1028
|
* @param cwd - The project working directory
|
|
916
1029
|
* @returns Combined sync results for both user and project scopes
|
|
917
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.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent Cleanup Hook (Stop)
|
|
4
|
+
*
|
|
5
|
+
* Cleans up agent-specific state when an agent stops.
|
|
6
|
+
* This prevents stale state from polluting future agents with the same ID.
|
|
7
|
+
*
|
|
8
|
+
* Runs after persistence hook to clean up when agent is allowed to stop.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, appendFileSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
14
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
15
|
+
import { cleanupAgent } from "../core/cleanup.js";
|
|
16
|
+
import { debug } from "../lib/logger.js";
|
|
17
|
+
|
|
18
|
+
const SOURCE = "agent-cleanup";
|
|
19
|
+
|
|
20
|
+
interface HookInput {
|
|
21
|
+
hook_event_name: string;
|
|
22
|
+
session_id: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
agent_id?: string;
|
|
25
|
+
transcript_path?: string;
|
|
26
|
+
permission_mode?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main(): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
const input = await readStdin();
|
|
32
|
+
if (!input.trim()) {
|
|
33
|
+
console.log(JSON.stringify({ continue: true }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data: HookInput = JSON.parse(input);
|
|
38
|
+
const cwd = data.cwd || process.cwd();
|
|
39
|
+
const agentId = data.agent_id || data.session_id;
|
|
40
|
+
|
|
41
|
+
// Clean up agent-specific state — delegates to core
|
|
42
|
+
if (agentId) {
|
|
43
|
+
const binary = findAideBinary({
|
|
44
|
+
cwd,
|
|
45
|
+
pluginRoot:
|
|
46
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
47
|
+
});
|
|
48
|
+
if (binary) {
|
|
49
|
+
const cleared = cleanupAgent(binary, cwd, agentId);
|
|
50
|
+
if (cleared) {
|
|
51
|
+
const logDir = join(cwd, ".aide", "_logs");
|
|
52
|
+
if (existsSync(logDir)) {
|
|
53
|
+
const logPath = join(logDir, "agent-cleanup.log");
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
appendFileSync(
|
|
56
|
+
logPath,
|
|
57
|
+
`${timestamp} Cleaned up state for agent: ${agentId}\n`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Always continue - cleanup is best-effort
|
|
65
|
+
console.log(JSON.stringify({ continue: true }));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
debug(SOURCE, `Hook error: ${error}`);
|
|
68
|
+
console.log(JSON.stringify({ continue: true }));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.on("uncaughtException", (err) => {
|
|
73
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
74
|
+
try {
|
|
75
|
+
console.log(JSON.stringify({ continue: true }));
|
|
76
|
+
} catch {
|
|
77
|
+
console.log('{"continue":true}');
|
|
78
|
+
}
|
|
79
|
+
process.exit(0);
|
|
80
|
+
});
|
|
81
|
+
process.on("unhandledRejection", (reason) => {
|
|
82
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
83
|
+
try {
|
|
84
|
+
console.log(JSON.stringify({ continue: true }));
|
|
85
|
+
} catch {
|
|
86
|
+
console.log('{"continue":true}');
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
main();
|