@jmylchreest/aide-plugin 0.0.19 → 0.0.22

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 (112) hide show
  1. package/package.json +13 -20
  2. package/src/cli/config.ts +150 -0
  3. package/{dist/cli/index.js → src/cli/index.ts} +38 -34
  4. package/src/cli/install.ts +71 -0
  5. package/src/cli/status.ts +46 -0
  6. package/src/cli/uninstall.ts +48 -0
  7. package/src/core/aide-client.ts +201 -0
  8. package/src/core/cleanup.ts +62 -0
  9. package/{dist/core/index.js → src/core/index.ts} +1 -1
  10. package/src/core/mcp-sync.ts +708 -0
  11. package/src/core/persistence-logic.ts +77 -0
  12. package/src/core/pre-compact-logic.ts +36 -0
  13. package/src/core/session-init.ts +411 -0
  14. package/src/core/session-summary-logic.ts +233 -0
  15. package/src/core/skill-matcher.ts +279 -0
  16. package/src/core/tool-enforcement.ts +130 -0
  17. package/src/core/tool-tracking.ts +112 -0
  18. package/src/core/types.ts +164 -0
  19. package/src/lib/aide-downloader.ts +557 -0
  20. package/src/lib/aide-memory.ts +400 -0
  21. package/src/lib/hook-utils.ts +185 -0
  22. package/src/lib/hud.ts +482 -0
  23. package/src/lib/logger.ts +379 -0
  24. package/src/lib/skills-registry.ts +333 -0
  25. package/src/lib/usage.ts +485 -0
  26. package/src/lib/worktree.ts +483 -0
  27. package/src/opencode/hooks.ts +708 -0
  28. package/{dist/opencode/index.d.ts → src/opencode/index.ts} +15 -6
  29. package/src/opencode/types.ts +107 -0
  30. package/dist/cli/config.d.ts +0 -58
  31. package/dist/cli/config.d.ts.map +0 -1
  32. package/dist/cli/config.js +0 -115
  33. package/dist/cli/config.js.map +0 -1
  34. package/dist/cli/index.d.ts +0 -11
  35. package/dist/cli/index.d.ts.map +0 -1
  36. package/dist/cli/index.js.map +0 -1
  37. package/dist/cli/install.d.ts +0 -9
  38. package/dist/cli/install.d.ts.map +0 -1
  39. package/dist/cli/install.js +0 -49
  40. package/dist/cli/install.js.map +0 -1
  41. package/dist/cli/status.d.ts +0 -5
  42. package/dist/cli/status.d.ts.map +0 -1
  43. package/dist/cli/status.js +0 -34
  44. package/dist/cli/status.js.map +0 -1
  45. package/dist/cli/uninstall.d.ts +0 -8
  46. package/dist/cli/uninstall.d.ts.map +0 -1
  47. package/dist/cli/uninstall.js +0 -29
  48. package/dist/cli/uninstall.js.map +0 -1
  49. package/dist/core/aide-client.d.ts +0 -48
  50. package/dist/core/aide-client.d.ts.map +0 -1
  51. package/dist/core/aide-client.js +0 -171
  52. package/dist/core/aide-client.js.map +0 -1
  53. package/dist/core/cleanup.d.ts +0 -15
  54. package/dist/core/cleanup.d.ts.map +0 -1
  55. package/dist/core/cleanup.js +0 -48
  56. package/dist/core/cleanup.js.map +0 -1
  57. package/dist/core/index.d.ts +0 -18
  58. package/dist/core/index.d.ts.map +0 -1
  59. package/dist/core/index.js.map +0 -1
  60. package/dist/core/mcp-sync.d.ts +0 -99
  61. package/dist/core/mcp-sync.d.ts.map +0 -1
  62. package/dist/core/mcp-sync.js +0 -524
  63. package/dist/core/mcp-sync.js.map +0 -1
  64. package/dist/core/persistence-logic.d.ts +0 -24
  65. package/dist/core/persistence-logic.d.ts.map +0 -1
  66. package/dist/core/persistence-logic.js +0 -61
  67. package/dist/core/persistence-logic.js.map +0 -1
  68. package/dist/core/pre-compact-logic.d.ts +0 -11
  69. package/dist/core/pre-compact-logic.d.ts.map +0 -1
  70. package/dist/core/pre-compact-logic.js +0 -29
  71. package/dist/core/pre-compact-logic.js.map +0 -1
  72. package/dist/core/session-init.d.ts +0 -54
  73. package/dist/core/session-init.d.ts.map +0 -1
  74. package/dist/core/session-init.js +0 -349
  75. package/dist/core/session-init.js.map +0 -1
  76. package/dist/core/session-summary-logic.d.ts +0 -29
  77. package/dist/core/session-summary-logic.d.ts.map +0 -1
  78. package/dist/core/session-summary-logic.js +0 -167
  79. package/dist/core/session-summary-logic.js.map +0 -1
  80. package/dist/core/skill-matcher.d.ts +0 -46
  81. package/dist/core/skill-matcher.d.ts.map +0 -1
  82. package/dist/core/skill-matcher.js +0 -242
  83. package/dist/core/skill-matcher.js.map +0 -1
  84. package/dist/core/tool-enforcement.d.ts +0 -44
  85. package/dist/core/tool-enforcement.d.ts.map +0 -1
  86. package/dist/core/tool-enforcement.js +0 -102
  87. package/dist/core/tool-enforcement.js.map +0 -1
  88. package/dist/core/tool-tracking.d.ts +0 -20
  89. package/dist/core/tool-tracking.d.ts.map +0 -1
  90. package/dist/core/tool-tracking.js +0 -81
  91. package/dist/core/tool-tracking.js.map +0 -1
  92. package/dist/core/types.d.ts +0 -109
  93. package/dist/core/types.d.ts.map +0 -1
  94. package/dist/core/types.js +0 -31
  95. package/dist/core/types.js.map +0 -1
  96. package/dist/lib/aide-downloader.d.ts +0 -90
  97. package/dist/lib/aide-downloader.js +0 -471
  98. package/dist/lib/aide-downloader.js.map +0 -1
  99. package/dist/lib/hook-utils.d.ts +0 -45
  100. package/dist/lib/hook-utils.js +0 -170
  101. package/dist/lib/hook-utils.js.map +0 -1
  102. package/dist/opencode/hooks.d.ts +0 -32
  103. package/dist/opencode/hooks.d.ts.map +0 -1
  104. package/dist/opencode/hooks.js +0 -508
  105. package/dist/opencode/hooks.js.map +0 -1
  106. package/dist/opencode/index.d.ts.map +0 -1
  107. package/dist/opencode/index.js +0 -32
  108. package/dist/opencode/index.js.map +0 -1
  109. package/dist/opencode/types.d.ts +0 -129
  110. package/dist/opencode/types.d.ts.map +0 -1
  111. package/dist/opencode/types.js +0 -12
  112. package/dist/opencode/types.js.map +0 -1
package/package.json CHANGED
@@ -1,38 +1,31 @@
1
1
  {
2
2
  "name": "@jmylchreest/aide-plugin",
3
- "version": "0.0.19",
3
+ "version": "0.0.22",
4
4
  "description": "aide plugin for OpenCode — multi-agent orchestration, memory, skills, and persistence",
5
5
  "type": "module",
6
- "main": "./dist/opencode/index.js",
7
- "types": "./dist/opencode/index.d.ts",
6
+ "main": "./src/opencode/index.ts",
8
7
  "exports": {
9
8
  ".": {
10
- "import": "./dist/opencode/index.js",
11
- "types": "./dist/opencode/index.d.ts"
9
+ "import": "./src/opencode/index.ts"
12
10
  }
13
11
  },
14
12
  "files": [
15
- "dist/opencode",
16
- "dist/core",
17
- "dist/cli",
18
- "dist/lib/aide-downloader.js",
19
- "dist/lib/aide-downloader.js.map",
20
- "dist/lib/aide-downloader.d.ts",
21
- "dist/lib/hook-utils.js",
22
- "dist/lib/hook-utils.js.map",
23
- "dist/lib/hook-utils.d.ts",
13
+ "src/opencode",
14
+ "src/core",
15
+ "src/cli",
16
+ "src/lib",
24
17
  "bin",
25
18
  "README.md"
26
19
  ],
27
20
  "bin": {
28
- "aide-plugin": "./dist/cli/index.js",
21
+ "aide-plugin": "./src/cli/index.ts",
29
22
  "aide-wrapper": "./bin/aide-wrapper.sh"
30
23
  },
31
24
  "scripts": {
32
- "prebuild": "npm run copy-dist",
33
- "build": "echo 'Build uses root project dist/ output'",
34
- "copy-dist": "node scripts/copy-dist.mjs",
35
- "prepublishOnly": "npm run copy-dist"
25
+ "prebuild": "npm run copy-src",
26
+ "build": "echo 'Ships TypeScript source no compilation needed'",
27
+ "copy-src": "node scripts/copy-src.mjs",
28
+ "prepublishOnly": "npm run copy-src"
36
29
  },
37
30
  "keywords": [
38
31
  "aide",
@@ -52,7 +45,7 @@
52
45
  "directory": "packages/opencode-plugin"
53
46
  },
54
47
  "engines": {
55
- "node": ">=20.0.0"
48
+ "bun": ">=1.0.0"
56
49
  },
57
50
  "dependencies": {}
58
51
  }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * OpenCode config file utilities.
3
+ *
4
+ * Handles reading, merging, and writing opencode.json at both
5
+ * global (~/.config/opencode/opencode.json) and project (./opencode.json) scopes.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
+ import { dirname, join } from "path";
10
+ import { homedir } from "os";
11
+
12
+ export interface McpServerConfig {
13
+ type?: string;
14
+ command?: string[];
15
+ environment?: Record<string, string>;
16
+ enabled?: boolean;
17
+ }
18
+
19
+ export interface OpenCodeConfig {
20
+ $schema?: string;
21
+ plugin?: string[];
22
+ mcp?: Record<string, McpServerConfig>;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ const PLUGIN_NAME = "@jmylchreest/aide-plugin";
27
+ const MCP_SERVER_NAME = "aide";
28
+ const SCHEMA_URL = "https://opencode.ai/config.json";
29
+
30
+ /**
31
+ * Get the path to the global OpenCode config file.
32
+ */
33
+ export function getGlobalConfigPath(): string {
34
+ return join(homedir(), ".config", "opencode", "opencode.json");
35
+ }
36
+
37
+ /**
38
+ * Get the path to the project-level OpenCode config file.
39
+ */
40
+ export function getProjectConfigPath(): string {
41
+ return join(process.cwd(), "opencode.json");
42
+ }
43
+
44
+ /**
45
+ * Read and parse an opencode.json file. Returns an empty config if
46
+ * the file doesn't exist or can't be parsed.
47
+ */
48
+ export function readConfig(configPath: string): OpenCodeConfig {
49
+ if (!existsSync(configPath)) {
50
+ return {};
51
+ }
52
+ try {
53
+ const raw = readFileSync(configPath, "utf-8");
54
+ return JSON.parse(raw) as OpenCodeConfig;
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Write a config object to an opencode.json file, creating
62
+ * parent directories as needed.
63
+ */
64
+ export function writeConfig(configPath: string, config: OpenCodeConfig): void {
65
+ const dir = dirname(configPath);
66
+ mkdirSync(dir, { recursive: true });
67
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
68
+ }
69
+
70
+ /**
71
+ * Add the aide plugin and MCP server to a config object.
72
+ * Preserves all existing config keys.
73
+ */
74
+ export function addAideToConfig(
75
+ config: OpenCodeConfig,
76
+ options: { noMcp?: boolean } = {},
77
+ ): OpenCodeConfig {
78
+ const result = { ...config };
79
+
80
+ // Ensure schema
81
+ if (!result.$schema) {
82
+ result.$schema = SCHEMA_URL;
83
+ }
84
+
85
+ // Add plugin to array (deduplicated)
86
+ const plugins = result.plugin ?? [];
87
+ if (!plugins.includes(PLUGIN_NAME)) {
88
+ plugins.push(PLUGIN_NAME);
89
+ }
90
+ result.plugin = plugins;
91
+
92
+ // Add MCP server config
93
+ if (!options.noMcp) {
94
+ const mcp = result.mcp ?? {};
95
+ if (!mcp[MCP_SERVER_NAME]) {
96
+ mcp[MCP_SERVER_NAME] = {
97
+ type: "local",
98
+ command: ["npx", "-y", "-p", PLUGIN_NAME, "aide-wrapper", "mcp"],
99
+ environment: {
100
+ AIDE_CODE_WATCH: "1",
101
+ AIDE_CODE_WATCH_DELAY: "30s",
102
+ },
103
+ enabled: true,
104
+ };
105
+ }
106
+ result.mcp = mcp;
107
+ }
108
+
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Remove the aide plugin and MCP server from a config object.
114
+ */
115
+ export function removeAideFromConfig(config: OpenCodeConfig): OpenCodeConfig {
116
+ const result = { ...config };
117
+
118
+ // Remove plugin from array
119
+ if (result.plugin) {
120
+ result.plugin = result.plugin.filter((p) => p !== PLUGIN_NAME);
121
+ if (result.plugin.length === 0) {
122
+ delete result.plugin;
123
+ }
124
+ }
125
+
126
+ // Remove MCP server
127
+ if (result.mcp) {
128
+ delete result.mcp[MCP_SERVER_NAME];
129
+ if (Object.keys(result.mcp).length === 0) {
130
+ delete result.mcp;
131
+ }
132
+ }
133
+
134
+ return result;
135
+ }
136
+
137
+ /**
138
+ * Check if aide is configured in a config object.
139
+ */
140
+ export function isAideConfigured(config: OpenCodeConfig): {
141
+ plugin: boolean;
142
+ mcp: boolean;
143
+ } {
144
+ return {
145
+ plugin: config.plugin?.includes(PLUGIN_NAME) ?? false,
146
+ mcp: config.mcp?.[MCP_SERVER_NAME] != null,
147
+ };
148
+ }
149
+
150
+ export { PLUGIN_NAME, MCP_SERVER_NAME };
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * aide CLI — install/uninstall the aide plugin for OpenCode.
4
4
  *
@@ -7,13 +7,16 @@
7
7
  * bunx @jmylchreest/aide-plugin uninstall # Remove from OpenCode config
8
8
  * bunx @jmylchreest/aide-plugin status # Show current installation status
9
9
  */
10
+
10
11
  import { install } from "./install.js";
11
12
  import { uninstall } from "./uninstall.js";
12
13
  import { status } from "./status.js";
14
+
13
15
  const args = process.argv.slice(2);
14
16
  const command = args[0];
15
- function printUsage() {
16
- console.log(`aide - AI Development Environment plugin for OpenCode
17
+
18
+ function printUsage(): void {
19
+ console.log(`aide - AI Development Environment plugin for OpenCode
17
20
 
18
21
  Usage:
19
22
  aide-plugin install Install aide plugin globally for OpenCode
@@ -27,39 +30,40 @@ Options:
27
30
 
28
31
  Examples:
29
32
  bunx @jmylchreest/aide-plugin install
30
- npx @jmylchreest/aide-plugin install
31
33
  aide-plugin install --project`);
32
34
  }
33
- async function main() {
34
- const flags = {
35
- project: args.includes("--project"),
36
- noMcp: args.includes("--no-mcp"),
37
- };
38
- switch (command) {
39
- case "install":
40
- await install(flags);
41
- break;
42
- case "uninstall":
43
- await uninstall(flags);
44
- break;
45
- case "status":
46
- await status();
47
- break;
48
- case "--help":
49
- case "-h":
50
- case "help":
51
- printUsage();
52
- break;
53
- default:
54
- if (command) {
55
- console.error(`Unknown command: ${command}\n`);
56
- }
57
- printUsage();
58
- process.exit(command ? 1 : 0);
59
- }
35
+
36
+ async function main(): Promise<void> {
37
+ const flags = {
38
+ project: args.includes("--project"),
39
+ noMcp: args.includes("--no-mcp"),
40
+ };
41
+
42
+ switch (command) {
43
+ case "install":
44
+ await install(flags);
45
+ break;
46
+ case "uninstall":
47
+ await uninstall(flags);
48
+ break;
49
+ case "status":
50
+ await status();
51
+ break;
52
+ case "--help":
53
+ case "-h":
54
+ case "help":
55
+ printUsage();
56
+ break;
57
+ default:
58
+ if (command) {
59
+ console.error(`Unknown command: ${command}\n`);
60
+ }
61
+ printUsage();
62
+ process.exit(command ? 1 : 0);
63
+ }
60
64
  }
65
+
61
66
  main().catch((err) => {
62
- console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
63
- process.exit(1);
67
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
68
+ process.exit(1);
64
69
  });
65
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Install command — registers aide plugin and MCP server in OpenCode config.
3
+ */
4
+
5
+ import {
6
+ addAideToConfig,
7
+ getGlobalConfigPath,
8
+ getProjectConfigPath,
9
+ isAideConfigured,
10
+ readConfig,
11
+ writeConfig,
12
+ PLUGIN_NAME,
13
+ } from "./config.js";
14
+
15
+ export interface InstallFlags {
16
+ project?: boolean;
17
+ noMcp?: boolean;
18
+ }
19
+
20
+ export async function install(flags: InstallFlags): Promise<void> {
21
+ const configPath = flags.project
22
+ ? getProjectConfigPath()
23
+ : getGlobalConfigPath();
24
+
25
+ const scope = flags.project ? "project" : "global";
26
+ console.log(`Installing aide plugin (${scope})...\n`);
27
+
28
+ // Read existing config
29
+ const existing = readConfig(configPath);
30
+ const before = isAideConfigured(existing);
31
+
32
+ if (before.plugin && before.mcp) {
33
+ console.log(`aide is already configured in ${configPath}`);
34
+ console.log(" plugin: registered");
35
+ console.log(" mcp: registered");
36
+ console.log("\nNothing to do.");
37
+ return;
38
+ }
39
+
40
+ // Apply changes
41
+ const updated = addAideToConfig(existing, { noMcp: flags.noMcp });
42
+ writeConfig(configPath, updated);
43
+
44
+ // Report what was done
45
+ const after = isAideConfigured(updated);
46
+ console.log(`Updated: ${configPath}\n`);
47
+
48
+ if (!before.plugin && after.plugin) {
49
+ console.log(` + Added "${PLUGIN_NAME}" to plugin array`);
50
+ } else if (before.plugin) {
51
+ console.log(` = Plugin already registered`);
52
+ }
53
+
54
+ if (!flags.noMcp) {
55
+ if (!before.mcp && after.mcp) {
56
+ console.log(` + Added "aide" MCP server`);
57
+ } else if (before.mcp) {
58
+ console.log(` = MCP server already registered`);
59
+ }
60
+ } else {
61
+ console.log(` - Skipped MCP server registration (--no-mcp)`);
62
+ }
63
+
64
+ console.log("\nInstallation complete. Start OpenCode to use aide.");
65
+
66
+ if (!flags.project) {
67
+ console.log(
68
+ "\nThe plugin is installed globally and will apply to all OpenCode projects.",
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Status command — shows current aide installation status for OpenCode.
3
+ */
4
+
5
+ import { existsSync } from "fs";
6
+ import {
7
+ getGlobalConfigPath,
8
+ getProjectConfigPath,
9
+ isAideConfigured,
10
+ readConfig,
11
+ } from "./config.js";
12
+
13
+ export async function status(): Promise<void> {
14
+ console.log("aide plugin status\n");
15
+
16
+ const globalPath = getGlobalConfigPath();
17
+ const projectPath = getProjectConfigPath();
18
+
19
+ // Global config
20
+ console.log(`Global config: ${globalPath}`);
21
+ if (existsSync(globalPath)) {
22
+ const globalConfig = readConfig(globalPath);
23
+ const globalStatus = isAideConfigured(globalConfig);
24
+ console.log(
25
+ ` plugin: ${globalStatus.plugin ? "registered" : "not found"}`,
26
+ );
27
+ console.log(` mcp: ${globalStatus.mcp ? "registered" : "not found"}`);
28
+ } else {
29
+ console.log(" (file does not exist)");
30
+ }
31
+
32
+ console.log();
33
+
34
+ // Project config
35
+ console.log(`Project config: ${projectPath}`);
36
+ if (existsSync(projectPath)) {
37
+ const projectConfig = readConfig(projectPath);
38
+ const projectStatus = isAideConfigured(projectConfig);
39
+ console.log(
40
+ ` plugin: ${projectStatus.plugin ? "registered" : "not found"}`,
41
+ );
42
+ console.log(` mcp: ${projectStatus.mcp ? "registered" : "not found"}`);
43
+ } else {
44
+ console.log(" (file does not exist)");
45
+ }
46
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Uninstall command — removes aide plugin and MCP server from OpenCode config.
3
+ */
4
+
5
+ import {
6
+ getGlobalConfigPath,
7
+ getProjectConfigPath,
8
+ isAideConfigured,
9
+ readConfig,
10
+ removeAideFromConfig,
11
+ writeConfig,
12
+ } from "./config.js";
13
+
14
+ export interface UninstallFlags {
15
+ project?: boolean;
16
+ }
17
+
18
+ export async function uninstall(flags: UninstallFlags): Promise<void> {
19
+ const configPath = flags.project
20
+ ? getProjectConfigPath()
21
+ : getGlobalConfigPath();
22
+
23
+ const scope = flags.project ? "project" : "global";
24
+ console.log(`Uninstalling aide plugin (${scope})...\n`);
25
+
26
+ const existing = readConfig(configPath);
27
+ const before = isAideConfigured(existing);
28
+
29
+ if (!before.plugin && !before.mcp) {
30
+ console.log(`aide is not configured in ${configPath}`);
31
+ console.log("\nNothing to do.");
32
+ return;
33
+ }
34
+
35
+ const updated = removeAideFromConfig(existing);
36
+ writeConfig(configPath, updated);
37
+
38
+ console.log(`Updated: ${configPath}\n`);
39
+
40
+ if (before.plugin) {
41
+ console.log(` - Removed aide plugin from plugin array`);
42
+ }
43
+ if (before.mcp) {
44
+ console.log(` - Removed aide MCP server`);
45
+ }
46
+
47
+ console.log("\nUninstallation complete.");
48
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Platform-agnostic aide binary discovery and state operations.
3
+ *
4
+ * Extracted from src/lib/hook-utils.ts. Both Claude Code hooks and
5
+ * OpenCode plugin use this module for binary discovery and state management.
6
+ *
7
+ * Key difference from hook-utils.ts: accepts explicit binary path and
8
+ * platform-specific options instead of relying on CLAUDE_PLUGIN_ROOT.
9
+ */
10
+
11
+ import { execSync, execFileSync } from "child_process";
12
+ import { existsSync, realpathSync } from "fs";
13
+ import { join } from "path";
14
+ import type { FindBinaryOptions } from "./types.js";
15
+ import { debug } from "../lib/logger.js";
16
+
17
+ const SOURCE = "aide-client";
18
+
19
+ /**
20
+ * Find the aide binary — platform-agnostic implementation.
21
+ *
22
+ * Search order:
23
+ * 1. pluginRoot/bin/aide (if pluginRoot provided)
24
+ * 2. cwd/.aide/bin/aide (project-local install)
25
+ * 3. Each path in additionalPaths
26
+ * 4. PATH fallback (system-wide install)
27
+ */
28
+ export function findAideBinary(opts: FindBinaryOptions = {}): string | null {
29
+ const { cwd, additionalPaths = [] } = opts;
30
+ let { pluginRoot } = opts;
31
+
32
+ // Resolve symlinks on pluginRoot
33
+ if (pluginRoot) {
34
+ try {
35
+ pluginRoot = realpathSync(pluginRoot);
36
+ } catch (err) {
37
+ debug(SOURCE, `realpath failed for pluginRoot ${pluginRoot}: ${err}`);
38
+ }
39
+ }
40
+
41
+ // 1. Plugin root bin/
42
+ if (pluginRoot) {
43
+ const pluginBinary = join(pluginRoot, "bin", "aide");
44
+ if (existsSync(pluginBinary)) {
45
+ return pluginBinary;
46
+ }
47
+ }
48
+
49
+ // 2. Project-local .aide/bin/
50
+ if (cwd) {
51
+ const projectBinary = join(cwd, ".aide", "bin", "aide");
52
+ if (existsSync(projectBinary)) {
53
+ return projectBinary;
54
+ }
55
+ }
56
+
57
+ // 3. Additional paths
58
+ for (const searchPath of additionalPaths) {
59
+ const binary = join(searchPath, "aide");
60
+ if (existsSync(binary)) {
61
+ return binary;
62
+ }
63
+ }
64
+
65
+ // 4. PATH fallback
66
+ try {
67
+ const result = execSync("which aide", { stdio: "pipe", timeout: 2000 })
68
+ .toString()
69
+ .trim();
70
+ if (result) return result;
71
+ } catch (err) {
72
+ debug(SOURCE, `aide not found in PATH: ${err}`);
73
+ }
74
+
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Run an aide command and return stdout, or null on failure.
80
+ */
81
+ export function runAide(
82
+ binary: string,
83
+ cwd: string,
84
+ args: string[],
85
+ options?: { timeout?: number; env?: Record<string, string | undefined> },
86
+ ): string | null {
87
+ try {
88
+ const env = options?.env ? { ...process.env, ...options.env } : process.env;
89
+ return execFileSync(binary, args, {
90
+ cwd,
91
+ encoding: "utf-8",
92
+ timeout: options?.timeout ?? 10000,
93
+ env,
94
+ });
95
+ } catch (err) {
96
+ debug(SOURCE, `runAide failed: ${args.join(" ")}: ${err}`);
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Set state in aide (global or per-agent)
103
+ */
104
+ export function setState(
105
+ binary: string,
106
+ cwd: string,
107
+ key: string,
108
+ value: string,
109
+ agentId?: string,
110
+ ): boolean {
111
+ try {
112
+ const args = ["state", "set", key, value];
113
+ if (agentId) args.push(`--agent=${agentId}`);
114
+ execFileSync(binary, args, { cwd, stdio: "pipe", timeout: 5000 });
115
+ return true;
116
+ } catch (err) {
117
+ debug(SOURCE, `setState failed for key=${key}: ${err}`);
118
+ return false;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get state from aide
124
+ */
125
+ export function getState(
126
+ binary: string,
127
+ cwd: string,
128
+ key: string,
129
+ agentId?: string,
130
+ ): string | null {
131
+ try {
132
+ const args = ["state", "get", key];
133
+ if (agentId) args.push(`--agent=${agentId}`);
134
+ const output = execFileSync(binary, args, {
135
+ cwd,
136
+ encoding: "utf-8",
137
+ timeout: 5000,
138
+ });
139
+ const match = output.match(/=\s*(.+)$/m);
140
+ return match ? match[1].trim() : null;
141
+ } catch (err) {
142
+ debug(SOURCE, `getState failed for key=${key}: ${err}`);
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Delete a state key from aide
149
+ */
150
+ export function deleteState(
151
+ binary: string,
152
+ cwd: string,
153
+ key: string,
154
+ agentId?: string,
155
+ ): boolean {
156
+ try {
157
+ const fullKey = agentId ? `agent:${agentId}:${key}` : key;
158
+ execFileSync(binary, ["state", "delete", fullKey], {
159
+ cwd,
160
+ stdio: "pipe",
161
+ });
162
+ return true;
163
+ } catch (err) {
164
+ debug(SOURCE, `deleteState failed for key=${key}: ${err}`);
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Clear all state for an agent
171
+ */
172
+ export function clearAgentState(
173
+ binary: string,
174
+ cwd: string,
175
+ agentId: string,
176
+ ): boolean {
177
+ try {
178
+ execFileSync(binary, ["state", "clear", `--agent=${agentId}`], {
179
+ cwd,
180
+ stdio: "pipe",
181
+ });
182
+ return true;
183
+ } catch (err) {
184
+ debug(SOURCE, `clearAgentState failed for agent=${agentId}: ${err}`);
185
+ return false;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Escape a string for safe shell usage (when shell is unavoidable)
191
+ */
192
+ export function shellEscape(str: string): string {
193
+ return str
194
+ .replace(/\\/g, "\\\\")
195
+ .replace(/"/g, '\\"')
196
+ .replace(/\$/g, "\\$")
197
+ .replace(/`/g, "\\`")
198
+ .replace(/!/g, "\\!")
199
+ .replace(/\n/g, " ")
200
+ .slice(0, 1000);
201
+ }