vectorforce 0.0.0-stage → 0.2.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.
- package/.agents/agents/AGENTS.md +12 -0
- package/.agents/agents/vectorforce-architect.md +35 -0
- package/.agents/agents/vectorforce-auditor.md +25 -0
- package/.agents/agents/vectorforce-samples-builder.md +30 -0
- package/.agents/mcp_config.json +11 -0
- package/.codex/config.toml +5 -0
- package/AGENTS.md +115 -0
- package/LICENSE +21 -0
- package/README.md +198 -2
- package/bin/vf.js +1159 -0
- package/codex.json +14 -0
- package/commandcode.json +11 -0
- package/musecode.json +14 -0
- package/package.json +40 -4
- package/src/core/errors.js +45 -0
- package/src/core/hash.js +56 -0
- package/src/core/types.js +194 -0
- package/src/index.js +17 -0
- package/src/mcp/harness-config.js +546 -0
- package/src/mcp/mcp-server.js +507 -0
- package/src/operations/briefing-service.js +313 -0
- package/src/operations/checkpoint-service.js +120 -0
- package/src/operations/handoff-service.js +343 -0
- package/src/operations/project-service.js +132 -0
- package/src/operations/reasoning-service.js +356 -0
- package/src/policy/authorizer.js +147 -0
- package/src/providers/code-index-provider.js +285 -0
- package/src/providers/document-provider.js +105 -0
- package/src/providers/git-provider.js +98 -0
- package/src/providers/llm-provider.js +425 -0
- package/src/storage/sqlite-store.js +387 -0
- package/src/studio/server.js +764 -0
- package/studio/dist/assets/index-C0_eVpV1.js +9 -0
- package/studio/dist/assets/index-CxTK-2ty.css +2 -0
- package/studio/dist/favicon.svg +1 -0
- package/studio/dist/icons.svg +24 -0
- package/studio/dist/index.html +17 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { homedir, tmpdir } from 'node:os';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, realpathSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
function isTempWorkspace(workspacePath) {
|
|
7
|
+
if (process.env.VF_NO_GLOBAL_CONFIG === '1' || process.env.NODE_TEST_CONTEXT) return true;
|
|
8
|
+
try {
|
|
9
|
+
const realWs = realpathSync(workspacePath);
|
|
10
|
+
const realTmp = realpathSync(tmpdir());
|
|
11
|
+
return realWs.startsWith(realTmp);
|
|
12
|
+
} catch {
|
|
13
|
+
return workspacePath.startsWith(tmpdir()) || workspacePath.includes('vf-test-') || workspacePath.includes('vf-practical-');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class HarnessConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Get the absolute path to bin/vf.js (cross-platform: macOS, Linux, Windows)
|
|
20
|
+
*/
|
|
21
|
+
static getBinPath() {
|
|
22
|
+
return fileURLToPath(new URL('../../bin/vf.js', import.meta.url));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generate configuration object for Antigravity.
|
|
27
|
+
*/
|
|
28
|
+
static getAntigravityServerEntry() {
|
|
29
|
+
return {
|
|
30
|
+
command: 'node',
|
|
31
|
+
args: [HarnessConfig.getBinPath(), 'mcp']
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Preview changes to Antigravity configuration.
|
|
37
|
+
* @param {string} workspacePath
|
|
38
|
+
* @returns {{ filePath: string, exists: boolean, currentContent: object|null, mergedContent: object }}
|
|
39
|
+
*/
|
|
40
|
+
static previewAntigravity(workspacePath) {
|
|
41
|
+
const dir = join(workspacePath, '.agents');
|
|
42
|
+
const filePath = join(dir, 'mcp_config.json');
|
|
43
|
+
|
|
44
|
+
let current = null;
|
|
45
|
+
if (existsSync(filePath)) {
|
|
46
|
+
try {
|
|
47
|
+
current = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
48
|
+
} catch {
|
|
49
|
+
current = {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const merged = {
|
|
54
|
+
...current,
|
|
55
|
+
mcpServers: {
|
|
56
|
+
...(current?.mcpServers || {}),
|
|
57
|
+
vectorforce: HarnessConfig.getAntigravityServerEntry()
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
filePath,
|
|
63
|
+
exists: existsSync(filePath),
|
|
64
|
+
currentContent: current,
|
|
65
|
+
mergedContent: merged
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Install or update Antigravity MCP configuration reversibly.
|
|
71
|
+
* @param {string} workspacePath
|
|
72
|
+
* @returns {string} filePath
|
|
73
|
+
*/
|
|
74
|
+
static installAntigravity(workspacePath) {
|
|
75
|
+
const { filePath, mergedContent } = HarnessConfig.previewAntigravity(workspacePath);
|
|
76
|
+
const dir = join(workspacePath, '.agents');
|
|
77
|
+
if (!existsSync(dir)) {
|
|
78
|
+
mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
79
|
+
}
|
|
80
|
+
writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
|
|
81
|
+
|
|
82
|
+
if (!isTempWorkspace(workspacePath)) {
|
|
83
|
+
try {
|
|
84
|
+
const globalAgyDir = join(homedir(), '.gemini', 'antigravity');
|
|
85
|
+
if (existsSync(globalAgyDir)) {
|
|
86
|
+
const globalMcpPath = join(globalAgyDir, 'mcp_config.json');
|
|
87
|
+
let globalConfig = {};
|
|
88
|
+
if (existsSync(globalMcpPath)) {
|
|
89
|
+
try {
|
|
90
|
+
globalConfig = JSON.parse(readFileSync(globalMcpPath, 'utf8'));
|
|
91
|
+
} catch {
|
|
92
|
+
globalConfig = {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
globalConfig.mcpServers = {
|
|
96
|
+
...(globalConfig.mcpServers || {}),
|
|
97
|
+
vectorforce: {
|
|
98
|
+
command: 'node',
|
|
99
|
+
args: [HarnessConfig.getBinPath(), 'mcp', '--workspace', workspacePath],
|
|
100
|
+
env: {
|
|
101
|
+
VF_WORKSPACE: workspacePath,
|
|
102
|
+
VF_DB_PATH: join(workspacePath, '.vectorforce', 'vectorforce.db')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
writeFileSync(globalMcpPath, JSON.stringify(globalConfig, null, 2) + '\n', 'utf8');
|
|
107
|
+
}
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return filePath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reversibly uninstall VectorForce from Antigravity configuration.
|
|
116
|
+
* @param {string} workspacePath
|
|
117
|
+
* @returns {boolean} whether removed
|
|
118
|
+
*/
|
|
119
|
+
static uninstallAntigravity(workspacePath) {
|
|
120
|
+
const filePath = join(workspacePath, '.agents', 'mcp_config.json');
|
|
121
|
+
if (!existsSync(filePath)) return false;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const config = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
125
|
+
if (config?.mcpServers?.vectorforce) {
|
|
126
|
+
delete config.mcpServers.vectorforce;
|
|
127
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
} catch {}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Preview changes to Command Code configuration.
|
|
136
|
+
* @param {string} workspacePath
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Generate configuration object for Command Code (.mcp.json).
|
|
140
|
+
*/
|
|
141
|
+
static getCommandCodeServerEntry() {
|
|
142
|
+
return {
|
|
143
|
+
type: 'stdio',
|
|
144
|
+
command: 'node',
|
|
145
|
+
args: [HarnessConfig.getBinPath(), 'mcp']
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Preview changes to Command Code configuration.
|
|
151
|
+
* @param {string} workspacePath
|
|
152
|
+
*/
|
|
153
|
+
static previewCommandCode(workspacePath) {
|
|
154
|
+
const filePath = join(workspacePath, 'commandcode.json');
|
|
155
|
+
const mcpJsonPath = join(workspacePath, '.mcp.json');
|
|
156
|
+
let current = null;
|
|
157
|
+
if (existsSync(filePath)) {
|
|
158
|
+
try {
|
|
159
|
+
current = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
160
|
+
} catch {
|
|
161
|
+
current = {};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const merged = {
|
|
166
|
+
...current,
|
|
167
|
+
mcpServers: {
|
|
168
|
+
...(current?.mcpServers || {}),
|
|
169
|
+
vectorforce: HarnessConfig.getAntigravityServerEntry()
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
let mcpCurrent = null;
|
|
174
|
+
if (existsSync(mcpJsonPath)) {
|
|
175
|
+
try {
|
|
176
|
+
mcpCurrent = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
|
|
177
|
+
} catch {
|
|
178
|
+
mcpCurrent = {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const mcpMerged = {
|
|
183
|
+
...mcpCurrent,
|
|
184
|
+
mcpServers: {
|
|
185
|
+
...(mcpCurrent?.mcpServers || {}),
|
|
186
|
+
vectorforce: HarnessConfig.getCommandCodeServerEntry()
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const cliCommand = `cmd mcp add --transport stdio vectorforce -- node ${HarnessConfig.getBinPath()} mcp`;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
filePath,
|
|
194
|
+
mcpJsonPath,
|
|
195
|
+
exists: existsSync(filePath),
|
|
196
|
+
cliCommand,
|
|
197
|
+
currentContent: current,
|
|
198
|
+
mergedContent: merged,
|
|
199
|
+
mcpMerged
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Install Command Code configuration reversibly.
|
|
205
|
+
* Writes both .mcp.json (native Command Code v1.84.0 format) and commandcode.json.
|
|
206
|
+
* @param {string} workspacePath
|
|
207
|
+
*/
|
|
208
|
+
static installCommandCode(workspacePath) {
|
|
209
|
+
const { filePath, mcpJsonPath, mergedContent, mcpMerged } = HarnessConfig.previewCommandCode(workspacePath);
|
|
210
|
+
writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
|
|
211
|
+
writeFileSync(mcpJsonPath, JSON.stringify(mcpMerged, null, 2) + '\n', 'utf8');
|
|
212
|
+
return filePath;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Uninstall VectorForce from Command Code configuration.
|
|
217
|
+
* @param {string} workspacePath
|
|
218
|
+
*/
|
|
219
|
+
static uninstallCommandCode(workspacePath) {
|
|
220
|
+
const filePath = join(workspacePath, 'commandcode.json');
|
|
221
|
+
const mcpJsonPath = join(workspacePath, '.mcp.json');
|
|
222
|
+
let removed = false;
|
|
223
|
+
|
|
224
|
+
if (existsSync(filePath)) {
|
|
225
|
+
try {
|
|
226
|
+
const config = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
227
|
+
if (config?.mcpServers?.vectorforce) {
|
|
228
|
+
delete config.mcpServers.vectorforce;
|
|
229
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
230
|
+
removed = true;
|
|
231
|
+
}
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (existsSync(mcpJsonPath)) {
|
|
236
|
+
try {
|
|
237
|
+
const mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
|
|
238
|
+
if (mcpConfig?.mcpServers?.vectorforce) {
|
|
239
|
+
delete mcpConfig.mcpServers.vectorforce;
|
|
240
|
+
writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
|
|
241
|
+
removed = true;
|
|
242
|
+
}
|
|
243
|
+
} catch {}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return removed;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Generate configuration object for Muse Code.
|
|
251
|
+
*/
|
|
252
|
+
static getMuseCodeServerEntry() {
|
|
253
|
+
return {
|
|
254
|
+
command: 'node',
|
|
255
|
+
args: [HarnessConfig.getBinPath(), 'mcp'],
|
|
256
|
+
env: {
|
|
257
|
+
VF_HARNESS: 'muse-code'
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Preview changes to Muse Code configuration.
|
|
264
|
+
* @param {string} workspacePath
|
|
265
|
+
*/
|
|
266
|
+
static previewMuseCode(workspacePath) {
|
|
267
|
+
const dir = join(workspacePath, '.muse');
|
|
268
|
+
const filePath = join(dir, 'mcp_config.json');
|
|
269
|
+
const museJsonPath = join(workspacePath, 'musecode.json');
|
|
270
|
+
|
|
271
|
+
let current = null;
|
|
272
|
+
if (existsSync(filePath)) {
|
|
273
|
+
try {
|
|
274
|
+
current = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
275
|
+
} catch {
|
|
276
|
+
current = {};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const merged = {
|
|
281
|
+
...current,
|
|
282
|
+
mcpServers: {
|
|
283
|
+
...(current?.mcpServers || {}),
|
|
284
|
+
vectorforce: HarnessConfig.getMuseCodeServerEntry()
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
filePath,
|
|
290
|
+
museJsonPath,
|
|
291
|
+
exists: existsSync(filePath),
|
|
292
|
+
currentContent: current,
|
|
293
|
+
mergedContent: merged
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Install Muse Code MCP configuration reversibly.
|
|
299
|
+
* @param {string} workspacePath
|
|
300
|
+
*/
|
|
301
|
+
static installMuseCode(workspacePath) {
|
|
302
|
+
const { filePath, museJsonPath, mergedContent } = HarnessConfig.previewMuseCode(workspacePath);
|
|
303
|
+
const dir = join(workspacePath, '.muse');
|
|
304
|
+
if (!existsSync(dir)) {
|
|
305
|
+
mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
306
|
+
}
|
|
307
|
+
writeFileSync(filePath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
|
|
308
|
+
writeFileSync(museJsonPath, JSON.stringify(mergedContent, null, 2) + '\n', 'utf8');
|
|
309
|
+
|
|
310
|
+
if (!isTempWorkspace(workspacePath)) {
|
|
311
|
+
try {
|
|
312
|
+
const globalMuseDir = join(homedir(), '.config', 'muse');
|
|
313
|
+
const globalSettingsPath = join(globalMuseDir, 'settings.json');
|
|
314
|
+
if (existsSync(globalSettingsPath)) {
|
|
315
|
+
const settings = JSON.parse(readFileSync(globalSettingsPath, 'utf8'));
|
|
316
|
+
settings.mcp_servers = {
|
|
317
|
+
...(settings.mcp_servers || {}),
|
|
318
|
+
vectorforce: {
|
|
319
|
+
transport: 'stdio',
|
|
320
|
+
command: 'node',
|
|
321
|
+
args: [HarnessConfig.getBinPath(), 'mcp'],
|
|
322
|
+
env: {
|
|
323
|
+
VF_HARNESS: 'muse-code',
|
|
324
|
+
VF_WORKSPACE: workspacePath
|
|
325
|
+
},
|
|
326
|
+
mode: 'optional'
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
delete settings.mcpServers;
|
|
330
|
+
writeFileSync(globalSettingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
331
|
+
}
|
|
332
|
+
} catch {}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return filePath;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Uninstall VectorForce from Muse Code configuration reversibly.
|
|
340
|
+
* @param {string} workspacePath
|
|
341
|
+
*/
|
|
342
|
+
static uninstallMuseCode(workspacePath) {
|
|
343
|
+
const filePath = join(workspacePath, '.muse', 'mcp_config.json');
|
|
344
|
+
const museJsonPath = join(workspacePath, 'musecode.json');
|
|
345
|
+
let removed = false;
|
|
346
|
+
|
|
347
|
+
if (existsSync(filePath)) {
|
|
348
|
+
try {
|
|
349
|
+
const config = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
350
|
+
if (config?.mcpServers?.vectorforce) {
|
|
351
|
+
delete config.mcpServers.vectorforce;
|
|
352
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
353
|
+
removed = true;
|
|
354
|
+
}
|
|
355
|
+
} catch {}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (existsSync(museJsonPath)) {
|
|
359
|
+
try {
|
|
360
|
+
const config = JSON.parse(readFileSync(museJsonPath, 'utf8'));
|
|
361
|
+
if (config?.mcpServers?.vectorforce) {
|
|
362
|
+
delete config.mcpServers.vectorforce;
|
|
363
|
+
writeFileSync(museJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
364
|
+
removed = true;
|
|
365
|
+
}
|
|
366
|
+
} catch {}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!isTempWorkspace(workspacePath)) {
|
|
370
|
+
try {
|
|
371
|
+
const globalSettingsPath = join(homedir(), '.config', 'muse', 'settings.json');
|
|
372
|
+
if (existsSync(globalSettingsPath)) {
|
|
373
|
+
const settings = JSON.parse(readFileSync(globalSettingsPath, 'utf8'));
|
|
374
|
+
if (settings?.mcp_servers?.vectorforce) {
|
|
375
|
+
delete settings.mcp_servers.vectorforce;
|
|
376
|
+
writeFileSync(globalSettingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return removed;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Generate configuration object for Codex.
|
|
387
|
+
*/
|
|
388
|
+
static getCodexServerEntry() {
|
|
389
|
+
return {
|
|
390
|
+
command: 'node',
|
|
391
|
+
args: [HarnessConfig.getBinPath(), 'mcp'],
|
|
392
|
+
env: {
|
|
393
|
+
VF_HARNESS: 'codex'
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Format or merge TOML configuration for Codex MCP server.
|
|
400
|
+
* @param {string} existingToml
|
|
401
|
+
* @returns {string} merged TOML
|
|
402
|
+
*/
|
|
403
|
+
static _mergeCodexToml(existingToml) {
|
|
404
|
+
const binPath = HarnessConfig.getBinPath().replace(/\\/g, '\\\\');
|
|
405
|
+
const serverBlock = `[mcp_servers.vectorforce]\ncommand = "node"\nargs = ["${binPath}", "mcp"]\n[mcp_servers.vectorforce.env]\nVF_HARNESS = "codex"\n`;
|
|
406
|
+
|
|
407
|
+
if (!existingToml || !existingToml.trim()) {
|
|
408
|
+
return serverBlock;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
|
|
412
|
+
if (sectionRegex.test(existingToml)) {
|
|
413
|
+
return existingToml.replace(sectionRegex, serverBlock.trimEnd()) + '\n';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return existingToml.trimEnd() + '\n\n' + serverBlock;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Preview changes to Codex configuration.
|
|
421
|
+
* @param {string} workspacePath
|
|
422
|
+
*/
|
|
423
|
+
static previewCodex(workspacePath) {
|
|
424
|
+
const dir = join(workspacePath, '.codex');
|
|
425
|
+
const filePath = join(dir, 'config.toml');
|
|
426
|
+
const codexJsonPath = join(workspacePath, 'codex.json');
|
|
427
|
+
|
|
428
|
+
let currentToml = null;
|
|
429
|
+
if (existsSync(filePath)) {
|
|
430
|
+
try {
|
|
431
|
+
currentToml = readFileSync(filePath, 'utf8');
|
|
432
|
+
} catch {
|
|
433
|
+
currentToml = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let currentJson = null;
|
|
438
|
+
if (existsSync(codexJsonPath)) {
|
|
439
|
+
try {
|
|
440
|
+
currentJson = JSON.parse(readFileSync(codexJsonPath, 'utf8'));
|
|
441
|
+
} catch {
|
|
442
|
+
currentJson = {};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const mergedToml = HarnessConfig._mergeCodexToml(currentToml);
|
|
447
|
+
const mergedJson = {
|
|
448
|
+
...currentJson,
|
|
449
|
+
mcpServers: {
|
|
450
|
+
...(currentJson?.mcpServers || {}),
|
|
451
|
+
vectorforce: HarnessConfig.getCodexServerEntry()
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const cliCommand = `codex mcp add vectorforce -- node ${HarnessConfig.getBinPath()} mcp`;
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
filePath,
|
|
459
|
+
codexJsonPath,
|
|
460
|
+
exists: existsSync(filePath) || existsSync(codexJsonPath),
|
|
461
|
+
cliCommand,
|
|
462
|
+
currentContent: currentToml,
|
|
463
|
+
mergedContent: mergedToml,
|
|
464
|
+
mergedJson
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Install Codex MCP configuration reversibly.
|
|
470
|
+
* Writes both .codex/config.toml (standard Codex CLI) and codex.json.
|
|
471
|
+
* @param {string} workspacePath
|
|
472
|
+
* @returns {string} filePath
|
|
473
|
+
*/
|
|
474
|
+
static installCodex(workspacePath) {
|
|
475
|
+
const { filePath, codexJsonPath, mergedContent, mergedJson } = HarnessConfig.previewCodex(workspacePath);
|
|
476
|
+
const dir = join(workspacePath, '.codex');
|
|
477
|
+
if (!existsSync(dir)) {
|
|
478
|
+
mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
479
|
+
}
|
|
480
|
+
writeFileSync(filePath, mergedContent, 'utf8');
|
|
481
|
+
writeFileSync(codexJsonPath, JSON.stringify(mergedJson, null, 2) + '\n', 'utf8');
|
|
482
|
+
|
|
483
|
+
if (!isTempWorkspace(workspacePath)) {
|
|
484
|
+
try {
|
|
485
|
+
const globalCodex = join(homedir(), '.codex');
|
|
486
|
+
if (existsSync(globalCodex)) {
|
|
487
|
+
const globalTomlPath = join(globalCodex, 'config.toml');
|
|
488
|
+
const existingGlobal = existsSync(globalTomlPath) ? readFileSync(globalTomlPath, 'utf8') : '';
|
|
489
|
+
const updatedGlobal = HarnessConfig._mergeCodexToml(existingGlobal);
|
|
490
|
+
writeFileSync(globalTomlPath, updatedGlobal, 'utf8');
|
|
491
|
+
}
|
|
492
|
+
} catch {}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return filePath;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Uninstall VectorForce from Codex configuration reversibly.
|
|
500
|
+
* @param {string} workspacePath
|
|
501
|
+
* @returns {boolean} whether removed
|
|
502
|
+
*/
|
|
503
|
+
static uninstallCodex(workspacePath) {
|
|
504
|
+
const filePath = join(workspacePath, '.codex', 'config.toml');
|
|
505
|
+
const codexJsonPath = join(workspacePath, 'codex.json');
|
|
506
|
+
let removed = false;
|
|
507
|
+
|
|
508
|
+
if (existsSync(filePath)) {
|
|
509
|
+
try {
|
|
510
|
+
const toml = readFileSync(filePath, 'utf8');
|
|
511
|
+
const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
|
|
512
|
+
if (sectionRegex.test(toml)) {
|
|
513
|
+
const cleaned = toml.replace(sectionRegex, '').trim();
|
|
514
|
+
writeFileSync(filePath, cleaned ? cleaned + '\n' : '', 'utf8');
|
|
515
|
+
removed = true;
|
|
516
|
+
}
|
|
517
|
+
} catch {}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (existsSync(codexJsonPath)) {
|
|
521
|
+
try {
|
|
522
|
+
const config = JSON.parse(readFileSync(codexJsonPath, 'utf8'));
|
|
523
|
+
if (config?.mcpServers?.vectorforce) {
|
|
524
|
+
delete config.mcpServers.vectorforce;
|
|
525
|
+
writeFileSync(codexJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
526
|
+
removed = true;
|
|
527
|
+
}
|
|
528
|
+
} catch {}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const globalTomlPath = join(homedir(), '.codex', 'config.toml');
|
|
533
|
+
if (existsSync(globalTomlPath)) {
|
|
534
|
+
const toml = readFileSync(globalTomlPath, 'utf8');
|
|
535
|
+
const sectionRegex = /\[mcp_servers\.vectorforce\][\s\S]*?(?=(\n\[(?!mcp_servers\.vectorforce))|$)/;
|
|
536
|
+
if (sectionRegex.test(toml)) {
|
|
537
|
+
const cleaned = toml.replace(sectionRegex, '').trim();
|
|
538
|
+
writeFileSync(globalTomlPath, cleaned ? cleaned + '\n' : '', 'utf8');
|
|
539
|
+
removed = true;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} catch {}
|
|
543
|
+
|
|
544
|
+
return removed;
|
|
545
|
+
}
|
|
546
|
+
}
|