ac-framework 1.8.0 → 1.9.0

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.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * mcp-installer.js — MCP config installer for AC Framework
3
+ *
4
+ * Detects installed AI assistants and injects the ac-framework-memory
5
+ * MCP server into their config files.
6
+ *
7
+ * Best-practice config format per MCP spec:
8
+ * { "mcpServers": { "ac-framework-memory": { "command": "node", "args": [<absPath>] } } }
9
+ *
10
+ * Claude Code uses a different top-level key format (mcpServers inside claude.json).
11
+ */
12
+
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { dirname, join } from 'node:path';
15
+ import { homedir, platform } from 'node:os';
16
+ import { fileURLToPath } from 'node:url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ // Absolute path to the MCP server entry point (ESM, referenced by node)
21
+ export function getMCPServerPath() {
22
+ const srcPath = join(__dirname, '../mcp/server.js');
23
+ return srcPath;
24
+ }
25
+
26
+ // ── Supported assistants ──────────────────────────────────────────
27
+
28
+ const home = homedir();
29
+ const IS_WIN = platform() === 'win32';
30
+
31
+ /**
32
+ * Each assistant entry:
33
+ * configPath – absolute path to the JSON config file
34
+ * configKey – top-level key that holds server map ("mcpServers" | "servers")
35
+ * detectDir – directory whose existence signals the assistant is present
36
+ */
37
+ const ASSISTANTS = [
38
+ {
39
+ name: 'opencode',
40
+ configPath: join(home, '.opencode', 'mcp.json'),
41
+ configKey: 'mcpServers',
42
+ detectDir: join(home, '.opencode'),
43
+ },
44
+ {
45
+ name: 'claude',
46
+ // Claude Code CLI uses ~/.claude.json (mcpServers key)
47
+ configPath: join(home, '.claude.json'),
48
+ configKey: 'mcpServers',
49
+ detectDir: home,
50
+ detectFile: join(home, '.claude.json'),
51
+ },
52
+ {
53
+ name: 'cursor',
54
+ configPath: join(home, '.cursor', 'mcp.json'),
55
+ configKey: 'mcpServers',
56
+ detectDir: join(home, '.cursor'),
57
+ },
58
+ {
59
+ name: 'windsurf',
60
+ configPath: join(home, '.windsurf', 'mcp.json'),
61
+ configKey: 'mcpServers',
62
+ detectDir: join(home, '.windsurf'),
63
+ },
64
+ {
65
+ name: 'gemini',
66
+ configPath: join(home, '.gemini', 'mcp.json'),
67
+ configKey: 'mcpServers',
68
+ detectDir: join(home, '.gemini'),
69
+ },
70
+ {
71
+ name: 'codex',
72
+ configPath: join(home, '.codex', 'mcp.json'),
73
+ configKey: 'mcpServers',
74
+ detectDir: join(home, '.codex'),
75
+ },
76
+ ];
77
+
78
+ // ── Detection ─────────────────────────────────────────────────────
79
+
80
+ export function isAssistantInstalled(assistant) {
81
+ try {
82
+ // If a specific file is the detection signal, use that
83
+ if (assistant.detectFile) return existsSync(assistant.detectFile);
84
+ return existsSync(assistant.detectDir);
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ // ── Install / Uninstall ───────────────────────────────────────────
91
+
92
+ export function installMCPForAssistant(assistant) {
93
+ try {
94
+ const configDir = dirname(assistant.configPath);
95
+
96
+ if (!existsSync(configDir)) {
97
+ mkdirSync(configDir, { recursive: true });
98
+ }
99
+
100
+ // Read existing config or start fresh
101
+ let config = {};
102
+ if (existsSync(assistant.configPath)) {
103
+ try {
104
+ config = JSON.parse(readFileSync(assistant.configPath, 'utf8'));
105
+ } catch {
106
+ config = {};
107
+ }
108
+ }
109
+
110
+ // Ensure the server map key exists
111
+ if (!config[assistant.configKey]) {
112
+ config[assistant.configKey] = {};
113
+ }
114
+
115
+ // Add / overwrite our server entry
116
+ config[assistant.configKey]['ac-framework-memory'] = {
117
+ command: 'node',
118
+ args: [getMCPServerPath()],
119
+ };
120
+
121
+ writeFileSync(assistant.configPath, JSON.stringify(config, null, 2));
122
+ return true;
123
+ } catch (error) {
124
+ console.error(` Failed to install MCP for ${assistant.name}: ${error.message}`);
125
+ return false;
126
+ }
127
+ }
128
+
129
+ export function uninstallMCPForAssistant(assistant) {
130
+ try {
131
+ if (!existsSync(assistant.configPath)) return true;
132
+
133
+ let config = {};
134
+ try {
135
+ config = JSON.parse(readFileSync(assistant.configPath, 'utf8'));
136
+ } catch {
137
+ return true;
138
+ }
139
+
140
+ if (config[assistant.configKey]?.['ac-framework-memory']) {
141
+ delete config[assistant.configKey]['ac-framework-memory'];
142
+ writeFileSync(assistant.configPath, JSON.stringify(config, null, 2));
143
+ }
144
+
145
+ return true;
146
+ } catch (error) {
147
+ console.error(` Failed to uninstall MCP for ${assistant.name}: ${error.message}`);
148
+ return false;
149
+ }
150
+ }
151
+
152
+ // ── Batch helpers ─────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Detects which assistants are installed and installs MCPs for them.
156
+ * Returns { installed, success } counts.
157
+ */
158
+ export function detectAndInstallMCPs() {
159
+ let installed = 0;
160
+ let success = 0;
161
+
162
+ for (const assistant of ASSISTANTS) {
163
+ if (isAssistantInstalled(assistant)) {
164
+ installed++;
165
+ if (installMCPForAssistant(assistant)) success++;
166
+ }
167
+ }
168
+
169
+ return { installed, success, assistants: ASSISTANTS };
170
+ }
171
+
172
+ /**
173
+ * Installs MCPs for ALL supported assistants regardless of detection.
174
+ */
175
+ export function installAllMCPs() {
176
+ let success = 0;
177
+ for (const assistant of ASSISTANTS) {
178
+ if (installMCPForAssistant(assistant)) success++;
179
+ }
180
+ return { total: ASSISTANTS.length, success };
181
+ }
182
+
183
+ /**
184
+ * Uninstalls MCPs from all detected assistants.
185
+ */
186
+ export function uninstallAllMCPs() {
187
+ let success = 0;
188
+ for (const assistant of ASSISTANTS) {
189
+ if (isAssistantInstalled(assistant) && uninstallMCPForAssistant(assistant)) success++;
190
+ }
191
+ return { success };
192
+ }
193
+
194
+ export { ASSISTANTS };