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