ac-framework 1.9.0 → 1.9.2
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 +1 -1
- package/src/commands/init.js +9 -7
- package/src/services/mcp-installer.js +226 -76
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -54,9 +54,7 @@ const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
|
|
|
54
54
|
*/
|
|
55
55
|
async function setupPersistentMemory() {
|
|
56
56
|
const memoryDbPath = join(homedir(), '.acfm', 'memory.db');
|
|
57
|
-
|
|
58
|
-
// Only ask on first run
|
|
59
|
-
if (existsSync(memoryDbPath)) return;
|
|
57
|
+
const alreadyExists = existsSync(memoryDbPath);
|
|
60
58
|
|
|
61
59
|
console.log();
|
|
62
60
|
await animatedSeparator(60);
|
|
@@ -93,16 +91,20 @@ async function setupPersistentMemory() {
|
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
console.log();
|
|
96
|
-
console.log(chalk.hex('#B2BEC3')(' Initializing NexusVault...'));
|
|
94
|
+
console.log(chalk.hex('#B2BEC3')(alreadyExists ? ' Reconnecting NexusVault...' : ' Initializing NexusVault...'));
|
|
97
95
|
|
|
98
|
-
// Init the SQLite database
|
|
96
|
+
// Init the SQLite database (idempotent — skips if already exists)
|
|
99
97
|
const { initDatabase, isDatabaseInitialized } = await import('../memory/database.js');
|
|
100
98
|
if (!isDatabaseInitialized()) {
|
|
101
99
|
initDatabase();
|
|
102
100
|
}
|
|
103
101
|
console.log(
|
|
104
102
|
chalk.hex('#00CEC9')(' ◆ ') +
|
|
105
|
-
chalk.hex('#DFE6E9')(
|
|
103
|
+
chalk.hex('#DFE6E9')(
|
|
104
|
+
alreadyExists
|
|
105
|
+
? 'NexusVault database found at ~/.acfm/memory.db'
|
|
106
|
+
: 'NexusVault database created at ~/.acfm/memory.db'
|
|
107
|
+
)
|
|
106
108
|
);
|
|
107
109
|
|
|
108
110
|
// Install MCP server into detected assistants
|
|
@@ -284,7 +286,7 @@ export async function initCommand(options = {}) {
|
|
|
284
286
|
|
|
285
287
|
// Dynamic step counting: +1 step when downloading from GitHub
|
|
286
288
|
const stepOffset = useLatest ? 1 : 0;
|
|
287
|
-
const totalSteps =
|
|
289
|
+
const totalSteps = 5 + stepOffset;
|
|
288
290
|
|
|
289
291
|
// Framework source: bundled by default, overridden by --latest
|
|
290
292
|
let frameworkPath = FRAMEWORK_PATH;
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* mcp-installer.js — MCP config installer for AC Framework
|
|
3
3
|
*
|
|
4
4
|
* Detects installed AI assistants and injects the ac-framework-memory
|
|
5
|
-
* MCP server into their config files.
|
|
5
|
+
* MCP server into their config files using the CORRECT format for each.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Verified formats (Mar 2026):
|
|
8
|
+
* opencode → ~/.config/opencode/opencode.json key:"mcp" type:"local" command:array
|
|
9
|
+
* claude → ~/.claude.json key:"mcpServers" type:"stdio"
|
|
10
|
+
* cursor → ~/.cursor/mcp.json key:"mcpServers"
|
|
11
|
+
* windsurf → ~/.codeium/windsurf/mcp_config.json key:"mcpServers"
|
|
12
|
+
* gemini → ~/.gemini/settings.json key:"mcpServers"
|
|
13
|
+
* codex → ~/.codex/config.toml TOML format
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -17,61 +20,149 @@ import { fileURLToPath } from 'node:url';
|
|
|
17
20
|
|
|
18
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
/** Absolute path to the MCP server entry point */
|
|
21
24
|
export function getMCPServerPath() {
|
|
22
|
-
|
|
23
|
-
return srcPath;
|
|
25
|
+
return join(__dirname, '../mcp/server.js');
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
// ── Supported assistants ──────────────────────────────────────────
|
|
27
|
-
|
|
28
28
|
const home = homedir();
|
|
29
|
-
const IS_WIN = platform() === 'win32';
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
// ── Assistant definitions ─────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Each entry may have a custom `install` / `uninstall` function if its
|
|
33
|
+
// format differs from the standard JSON mcpServers pattern.
|
|
34
|
+
|
|
35
|
+
export const ASSISTANTS = [
|
|
36
|
+
// ── OpenCode ───────────────────────────────────────────────────
|
|
37
|
+
// Config: ~/.config/opencode/opencode.json
|
|
38
|
+
// Schema: { "mcp": { "<name>": { "type": "local", "command": ["node", "path"] } } }
|
|
38
39
|
{
|
|
39
40
|
name: 'opencode',
|
|
40
|
-
configPath: join(home, '.opencode', '
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
configPath: join(home, '.config', 'opencode', 'opencode.json'),
|
|
42
|
+
detectDir: join(home, '.config', 'opencode'),
|
|
43
|
+
install(serverPath) {
|
|
44
|
+
const configDir = dirname(this.configPath);
|
|
45
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
let config = {};
|
|
48
|
+
if (existsSync(this.configPath)) {
|
|
49
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { config = {}; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!config.mcp) config.mcp = {};
|
|
53
|
+
config.mcp['ac-framework-memory'] = {
|
|
54
|
+
type: 'local',
|
|
55
|
+
command: ['node', serverPath],
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
uninstall() {
|
|
62
|
+
if (!existsSync(this.configPath)) return true;
|
|
63
|
+
let config = {};
|
|
64
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { return true; }
|
|
65
|
+
if (config.mcp?.['ac-framework-memory']) {
|
|
66
|
+
delete config.mcp['ac-framework-memory'];
|
|
67
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
43
71
|
},
|
|
72
|
+
|
|
73
|
+
// ── Claude Code CLI ────────────────────────────────────────────
|
|
74
|
+
// Config: ~/.claude.json (top-level mcpServers key, merges with existing data)
|
|
75
|
+
// Schema: { "mcpServers": { "<name>": { "type": "stdio", "command": "node", "args": ["path"] } } }
|
|
44
76
|
{
|
|
45
77
|
name: 'claude',
|
|
46
|
-
// Claude Code CLI uses ~/.claude.json (mcpServers key)
|
|
47
78
|
configPath: join(home, '.claude.json'),
|
|
48
|
-
configKey: 'mcpServers',
|
|
49
|
-
detectDir: home,
|
|
50
79
|
detectFile: join(home, '.claude.json'),
|
|
80
|
+
install(serverPath) {
|
|
81
|
+
let config = {};
|
|
82
|
+
if (existsSync(this.configPath)) {
|
|
83
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { config = {}; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
87
|
+
config.mcpServers['ac-framework-memory'] = {
|
|
88
|
+
type: 'stdio',
|
|
89
|
+
command: 'node',
|
|
90
|
+
args: [serverPath],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
94
|
+
return true;
|
|
95
|
+
},
|
|
96
|
+
uninstall() {
|
|
97
|
+
if (!existsSync(this.configPath)) return true;
|
|
98
|
+
let config = {};
|
|
99
|
+
try { config = JSON.parse(readFileSync(this.configPath, 'utf8')); } catch { return true; }
|
|
100
|
+
if (config.mcpServers?.['ac-framework-memory']) {
|
|
101
|
+
delete config.mcpServers['ac-framework-memory'];
|
|
102
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
},
|
|
51
106
|
},
|
|
107
|
+
|
|
108
|
+
// ── Cursor IDE ─────────────────────────────────────────────────
|
|
109
|
+
// Config: ~/.cursor/mcp.json
|
|
110
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
52
111
|
{
|
|
53
112
|
name: 'cursor',
|
|
54
113
|
configPath: join(home, '.cursor', 'mcp.json'),
|
|
55
|
-
configKey: 'mcpServers',
|
|
56
114
|
detectDir: join(home, '.cursor'),
|
|
115
|
+
install(serverPath) {
|
|
116
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
117
|
+
},
|
|
118
|
+
uninstall() {
|
|
119
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
120
|
+
},
|
|
57
121
|
},
|
|
122
|
+
|
|
123
|
+
// ── Windsurf IDE ───────────────────────────────────────────────
|
|
124
|
+
// Config: ~/.codeium/windsurf/mcp_config.json (NOT ~/.windsurf/mcp.json)
|
|
125
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
58
126
|
{
|
|
59
127
|
name: 'windsurf',
|
|
60
|
-
configPath: join(home, '.windsurf', '
|
|
61
|
-
|
|
62
|
-
|
|
128
|
+
configPath: join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
129
|
+
detectDir: join(home, '.codeium', 'windsurf'),
|
|
130
|
+
install(serverPath) {
|
|
131
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
132
|
+
},
|
|
133
|
+
uninstall() {
|
|
134
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
135
|
+
},
|
|
63
136
|
},
|
|
137
|
+
|
|
138
|
+
// ── Google Gemini CLI ──────────────────────────────────────────
|
|
139
|
+
// Config: ~/.gemini/settings.json (NOT ~/.gemini/mcp.json)
|
|
140
|
+
// Schema: { "mcpServers": { "<name>": { "command": "node", "args": ["path"] } } }
|
|
64
141
|
{
|
|
65
142
|
name: 'gemini',
|
|
66
|
-
configPath: join(home, '.gemini', '
|
|
67
|
-
configKey: 'mcpServers',
|
|
143
|
+
configPath: join(home, '.gemini', 'settings.json'),
|
|
68
144
|
detectDir: join(home, '.gemini'),
|
|
145
|
+
install(serverPath) {
|
|
146
|
+
return installJsonMcpServers(this.configPath, serverPath);
|
|
147
|
+
},
|
|
148
|
+
uninstall() {
|
|
149
|
+
return uninstallJsonMcpServers(this.configPath);
|
|
150
|
+
},
|
|
69
151
|
},
|
|
152
|
+
|
|
153
|
+
// ── OpenAI Codex CLI ───────────────────────────────────────────
|
|
154
|
+
// Config: ~/.codex/config.toml (TOML format, NOT JSON)
|
|
155
|
+
// Schema: [mcp_servers.ac-framework-memory]\ncommand = "node"\nargs = ["/path"]
|
|
70
156
|
{
|
|
71
157
|
name: 'codex',
|
|
72
|
-
configPath: join(home, '.codex', '
|
|
73
|
-
configKey: 'mcpServers',
|
|
158
|
+
configPath: join(home, '.codex', 'config.toml'),
|
|
74
159
|
detectDir: join(home, '.codex'),
|
|
160
|
+
install(serverPath) {
|
|
161
|
+
return installTomlMcpServer(this.configPath, serverPath);
|
|
162
|
+
},
|
|
163
|
+
uninstall() {
|
|
164
|
+
return uninstallTomlMcpServer(this.configPath);
|
|
165
|
+
},
|
|
75
166
|
},
|
|
76
167
|
];
|
|
77
168
|
|
|
@@ -79,7 +170,6 @@ const ASSISTANTS = [
|
|
|
79
170
|
|
|
80
171
|
export function isAssistantInstalled(assistant) {
|
|
81
172
|
try {
|
|
82
|
-
// If a specific file is the detection signal, use that
|
|
83
173
|
if (assistant.detectFile) return existsSync(assistant.detectFile);
|
|
84
174
|
return existsSync(assistant.detectDir);
|
|
85
175
|
} catch {
|
|
@@ -87,39 +177,115 @@ export function isAssistantInstalled(assistant) {
|
|
|
87
177
|
}
|
|
88
178
|
}
|
|
89
179
|
|
|
90
|
-
// ──
|
|
180
|
+
// ── Generic JSON mcpServers helpers ───────────────────────────────
|
|
91
181
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
182
|
+
function installJsonMcpServers(configPath, serverPath) {
|
|
183
|
+
const configDir = dirname(configPath);
|
|
184
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
95
185
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
186
|
+
let config = {};
|
|
187
|
+
if (existsSync(configPath)) {
|
|
188
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { config = {}; }
|
|
189
|
+
}
|
|
99
190
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
191
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
192
|
+
config.mcpServers['ac-framework-memory'] = {
|
|
193
|
+
command: 'node',
|
|
194
|
+
args: [serverPath],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
109
200
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
201
|
+
function uninstallJsonMcpServers(configPath) {
|
|
202
|
+
if (!existsSync(configPath)) return true;
|
|
203
|
+
let config = {};
|
|
204
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { return true; }
|
|
205
|
+
if (config.mcpServers?.['ac-framework-memory']) {
|
|
206
|
+
delete config.mcpServers['ac-framework-memory'];
|
|
207
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── TOML helpers (Codex) ──────────────────────────────────────────
|
|
213
|
+
//
|
|
214
|
+
// We write TOML manually (no external dep). The section to add/replace:
|
|
215
|
+
//
|
|
216
|
+
// [mcp_servers.ac-framework-memory]
|
|
217
|
+
// command = "node"
|
|
218
|
+
// args = ["/abs/path/to/server.js"]
|
|
219
|
+
|
|
220
|
+
const TOML_SECTION = 'mcp_servers.ac-framework-memory';
|
|
221
|
+
|
|
222
|
+
function installTomlMcpServer(configPath, serverPath) {
|
|
223
|
+
const configDir = dirname(configPath);
|
|
224
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
225
|
+
|
|
226
|
+
// Escape backslashes for Windows paths
|
|
227
|
+
const escapedPath = serverPath.replace(/\\/g, '\\\\');
|
|
228
|
+
const newBlock = [
|
|
229
|
+
`[${TOML_SECTION}]`,
|
|
230
|
+
`command = "node"`,
|
|
231
|
+
`args = ["${escapedPath}"]`,
|
|
232
|
+
].join('\n');
|
|
233
|
+
|
|
234
|
+
let existing = '';
|
|
235
|
+
if (existsSync(configPath)) {
|
|
236
|
+
try { existing = readFileSync(configPath, 'utf8'); } catch { existing = ''; }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (existing.includes(`[${TOML_SECTION}]`)) {
|
|
240
|
+
// Replace existing block — remove old section, append new one
|
|
241
|
+
existing = removeTomlSection(existing, TOML_SECTION);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const separator = existing.trim() ? '\n\n' : '';
|
|
245
|
+
writeFileSync(configPath, existing.trimEnd() + separator + newBlock + '\n');
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function uninstallTomlMcpServer(configPath) {
|
|
250
|
+
if (!existsSync(configPath)) return true;
|
|
251
|
+
let content = '';
|
|
252
|
+
try { content = readFileSync(configPath, 'utf8'); } catch { return true; }
|
|
253
|
+
if (content.includes(`[${TOML_SECTION}]`)) {
|
|
254
|
+
content = removeTomlSection(content, TOML_SECTION);
|
|
255
|
+
writeFileSync(configPath, content);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Removes a TOML section (and its key-value lines) from a TOML string.
|
|
262
|
+
* Stops at the next [section] header or end of file.
|
|
263
|
+
*/
|
|
264
|
+
function removeTomlSection(toml, sectionKey) {
|
|
265
|
+
const lines = toml.split('\n');
|
|
266
|
+
const result = [];
|
|
267
|
+
let inSection = false;
|
|
268
|
+
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
const trimmed = line.trim();
|
|
271
|
+
if (trimmed === `[${sectionKey}]`) {
|
|
272
|
+
inSection = true;
|
|
273
|
+
continue;
|
|
113
274
|
}
|
|
275
|
+
if (inSection && trimmed.startsWith('[')) {
|
|
276
|
+
inSection = false;
|
|
277
|
+
}
|
|
278
|
+
if (!inSection) result.push(line);
|
|
279
|
+
}
|
|
114
280
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
281
|
+
return result.join('\n');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── Install / Uninstall per assistant ────────────────────────────
|
|
120
285
|
|
|
121
|
-
|
|
122
|
-
|
|
286
|
+
export function installMCPForAssistant(assistant) {
|
|
287
|
+
try {
|
|
288
|
+
return assistant.install(getMCPServerPath());
|
|
123
289
|
} catch (error) {
|
|
124
290
|
console.error(` Failed to install MCP for ${assistant.name}: ${error.message}`);
|
|
125
291
|
return false;
|
|
@@ -128,21 +294,7 @@ export function installMCPForAssistant(assistant) {
|
|
|
128
294
|
|
|
129
295
|
export function uninstallMCPForAssistant(assistant) {
|
|
130
296
|
try {
|
|
131
|
-
|
|
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;
|
|
297
|
+
return assistant.uninstall();
|
|
146
298
|
} catch (error) {
|
|
147
299
|
console.error(` Failed to uninstall MCP for ${assistant.name}: ${error.message}`);
|
|
148
300
|
return false;
|
|
@@ -190,5 +342,3 @@ export function uninstallAllMCPs() {
|
|
|
190
342
|
}
|
|
191
343
|
return { success };
|
|
192
344
|
}
|
|
193
|
-
|
|
194
|
-
export { ASSISTANTS };
|