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.
package/bin/vf.js ADDED
@@ -0,0 +1,756 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { homedir } from 'node:os';
4
+ import { resolve, join, dirname } from 'node:path';
5
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import {
7
+ SqliteStore,
8
+ ProjectService,
9
+ CheckpointService,
10
+ BriefingService,
11
+ HandoffService,
12
+ ReasoningService,
13
+ LlmProvider,
14
+ CodeIndexProvider,
15
+ resolveLlmConfig,
16
+ McpServer,
17
+ HarnessConfig,
18
+ createStudioServer,
19
+ VectorForceError
20
+ } from '../src/index.js';
21
+
22
+ function getScriptWorkspace() {
23
+ try {
24
+ const scriptDir = dirname(resolve(process.argv[1] || ''));
25
+ let curr = scriptDir;
26
+ while (curr && curr !== '/' && curr !== homedir()) {
27
+ if (existsSync(join(curr, '.vectorforce', 'vectorforce.db')) || existsSync(join(curr, '.git'))) {
28
+ return curr;
29
+ }
30
+ const parent = dirname(curr);
31
+ if (parent === curr) break;
32
+ curr = parent;
33
+ }
34
+ } catch {}
35
+ return null;
36
+ }
37
+
38
+ function getWorkspacePath(startDir = process.cwd()) {
39
+ if (process.env.VF_WORKSPACE) return resolve(process.env.VF_WORKSPACE);
40
+
41
+ // If startDir is root or homedir, check if the script location belongs to a VectorForce workspace first
42
+ if (startDir === '/' || startDir === homedir()) {
43
+ const scriptWs = getScriptWorkspace();
44
+ if (scriptWs) return scriptWs;
45
+ }
46
+
47
+ let curr = resolve(startDir);
48
+ while (true) {
49
+ // homedir can contain ~/.vectorforce as a global fallback directory; do not treat homedir as a project workspace unless it is a git repo
50
+ if (curr !== homedir() && curr !== '/') {
51
+ if (existsSync(join(curr, '.vectorforce')) || existsSync(join(curr, '.git'))) {
52
+ return curr;
53
+ }
54
+ } else if (existsSync(join(curr, '.git'))) {
55
+ return curr;
56
+ }
57
+ const parent = dirname(curr);
58
+ if (parent === curr) break;
59
+ curr = parent;
60
+ }
61
+
62
+ return resolve(startDir);
63
+ }
64
+
65
+ function getDbPath(startDir = process.cwd()) {
66
+ if (process.env.VF_DB_PATH) return process.env.VF_DB_PATH;
67
+
68
+ // If startDir is root or homedir, check if the script location belongs to a VectorForce workspace first
69
+ if (startDir === '/' || startDir === homedir()) {
70
+ const scriptWs = getScriptWorkspace();
71
+ if (scriptWs) {
72
+ const dbCandidate = join(scriptWs, '.vectorforce', 'vectorforce.db');
73
+ if (existsSync(dbCandidate)) return dbCandidate;
74
+ }
75
+ }
76
+
77
+ // 1. Search up from current directory to root: look for .vectorforce/vectorforce.db or .git
78
+ let curr = resolve(startDir);
79
+ while (true) {
80
+ if (curr !== homedir() && curr !== '/') {
81
+ const localDb = join(curr, '.vectorforce', 'vectorforce.db');
82
+ if (existsSync(localDb)) {
83
+ return localDb;
84
+ }
85
+ const localDir = join(curr, '.vectorforce');
86
+ if (existsSync(localDir)) {
87
+ return localDb;
88
+ }
89
+ }
90
+ if (existsSync(join(curr, '.git'))) {
91
+ const gitLocalDir = join(curr, '.vectorforce');
92
+ mkdirSync(gitLocalDir, { recursive: true, mode: 0o700 });
93
+ return join(gitLocalDir, 'vectorforce.db');
94
+ }
95
+ const parent = dirname(curr);
96
+ if (parent === curr) break;
97
+ curr = parent;
98
+ }
99
+
100
+ // 2. Global fallback in ~/.vectorforce/vectorforce.db
101
+ try {
102
+ const homeDir = join(homedir(), '.vectorforce');
103
+ mkdirSync(homeDir, { recursive: true, mode: 0o700 });
104
+ return join(homeDir, 'vectorforce.db');
105
+ } catch {
106
+ const fallbackDir = join(startDir, '.vectorforce');
107
+ mkdirSync(fallbackDir, { recursive: true, mode: 0o700 });
108
+ return join(fallbackDir, 'vectorforce.db');
109
+ }
110
+ }
111
+
112
+ function createLlmProvider(workspacePath) {
113
+ try {
114
+ return { provider: new LlmProvider(resolveLlmConfig(process.env, {}, workspacePath)), error: null };
115
+ } catch (err) {
116
+ return { provider: null, error: err.message };
117
+ }
118
+ }
119
+
120
+ function getServices(explicitDbPath, workspacePath) {
121
+ const cwd = workspacePath || getWorkspacePath(process.cwd());
122
+ const dbPath = explicitDbPath || getDbPath(cwd);
123
+ const store = new SqliteStore(dbPath);
124
+ const projectService = new ProjectService(store);
125
+ const checkpointService = new CheckpointService(store, projectService);
126
+ const briefingService = new BriefingService(store, projectService);
127
+ const handoffService = new HandoffService(store, projectService, checkpointService);
128
+ const llm = createLlmProvider(cwd);
129
+ const reasoningService = new ReasoningService(store, projectService, briefingService, {
130
+ llmProvider: llm.provider,
131
+ codeIndexProvider: CodeIndexProvider
132
+ });
133
+ return {
134
+ store,
135
+ projectService,
136
+ checkpointService,
137
+ briefingService,
138
+ handoffService,
139
+ reasoningService,
140
+ llmProviderError: llm.error
141
+ };
142
+ }
143
+
144
+ function printUsage() {
145
+ console.log(`
146
+ VectorForce (vf) - Git for AI Engineering Context
147
+
148
+ Usage:
149
+ vf init [project_name] Initialize current workspace
150
+ vf task <id> [title] Create or register a task
151
+ vf tasks [active|completed|all] List tasks in workspace
152
+ vf policy list List allowed document paths
153
+ vf policy add <relative_path> Allow document for checkpointing
154
+ vf checkpoint [options] Capture an immutable checkpoint
155
+ vf resume [options] Generate authorized resume briefing
156
+ vf reason [options] AI reasoning over memory + codebase (alias: continue)
157
+ vf drift [options] Check for workspace and document drift
158
+ vf export [options] Export selective handoff package
159
+ vf import <package_path> [options] Import handoff package into workspace
160
+ vf recap [options] Show weekly timeline & engineering recap
161
+ vf timeline [options] Alias for vf recap
162
+ vf studio [options] Launch VectorForce Studio web control center
163
+ vf ui [options] Alias for vf studio
164
+ vf mcp Start stdio MCP server for Antigravity / Command Code
165
+ vf config Display MCP configuration snippet for harnesses
166
+ vf help Show this help
167
+
168
+ Checkpoint options:
169
+ --task <id> Task identifier
170
+ --summary <text> Concise summary
171
+ --goal <text> Add a goal (can repeat)
172
+ --constraint <text> Add a constraint (can repeat)
173
+ --decision <text> Add decision format "ID:Title:Rationale" (can repeat)
174
+ --next <text> Add next step (can repeat)
175
+ --doc <path> Request allowlisted document (can repeat)
176
+ --no-git Skip Git status collection
177
+
178
+ Resume options:
179
+ --task <id> Task identifier
180
+ --checkpoint <id> Specific checkpoint ID (defaults to latest head)
181
+ --dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
182
+
183
+ Reason options:
184
+ --task <id> Task identifier
185
+ --checkpoint <id> Specific checkpoint ID (defaults to latest head)
186
+ --focus <text> Optional question or focus for the reasoning pass
187
+ --model <name> Override the model id (e.g. a Meta Llama or Muse model)
188
+ --no-code Reason over memory only (do not send code context)
189
+ --raw Print the deterministic briefing without calling the model
190
+ --dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
191
+
192
+ Recap / Timeline options:
193
+ --days <number> Number of days to include (default: 7)
194
+ --task <id> Filter by specific task
195
+ --dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli
196
+
197
+ Handoff options:
198
+ --task <id> Task identifier
199
+ --recipient <name> Target recipient/audience
200
+ --checkpoint <id> Specific checkpoint ID (defaults to latest head)
201
+ --dest <harness> Destination harness: antigravity, command-code, muse, codex, pi, cli, any
202
+ --preview Preview export bundle without writing file
203
+ --out <path> Target output path for handoff package
204
+ `);
205
+ }
206
+
207
+ function parseFlags(args) {
208
+ const flags = {
209
+ goals: [],
210
+ constraints: [],
211
+ decisions: [],
212
+ nextSteps: [],
213
+ docs: [],
214
+ includeGit: true
215
+ };
216
+ for (let i = 0; i < args.length; i++) {
217
+ const arg = args[i];
218
+ if (arg === '--task' && args[i + 1]) {
219
+ flags.task = args[++i];
220
+ } else if (arg === '--summary' && args[i + 1]) {
221
+ flags.summary = args[++i];
222
+ } else if (arg === '--checkpoint' && args[i + 1]) {
223
+ flags.checkpoint = args[++i];
224
+ } else if (arg === '--dest' && args[i + 1]) {
225
+ flags.dest = args[++i];
226
+ } else if (arg === '--goal' && args[i + 1]) {
227
+ flags.goals.push(args[++i]);
228
+ } else if (arg === '--constraint' && args[i + 1]) {
229
+ flags.constraints.push(args[++i]);
230
+ } else if (arg === '--decision' && args[i + 1]) {
231
+ const parts = args[++i].split(':');
232
+ flags.decisions.push({
233
+ id: parts[0] || 'DEC',
234
+ title: parts[1] || parts[0],
235
+ rationale: parts[2] || 'Approved architecture decision'
236
+ });
237
+ } else if (arg === '--next' && args[i + 1]) {
238
+ flags.nextSteps.push(args[++i]);
239
+ } else if (arg === '--doc' && args[i + 1]) {
240
+ flags.docs.push(args[++i]);
241
+ } else if (arg === '--model' && args[i + 1]) {
242
+ flags.model = args[++i];
243
+ } else if (arg === '--focus' && args[i + 1]) {
244
+ flags.focus = args[++i];
245
+ } else if (arg === '--no-code') {
246
+ flags.includeCode = false;
247
+ } else if (arg === '--raw') {
248
+ flags.raw = true;
249
+ } else if (arg === '--no-git') {
250
+ flags.includeGit = false;
251
+ } else if (arg === '--recipient' && args[i + 1]) {
252
+ flags.recipient = args[++i];
253
+ } else if (arg === '--preview') {
254
+ flags.preview = true;
255
+ } else if (arg === '--out' && args[i + 1]) {
256
+ flags.outPath = args[++i];
257
+ } else if (arg === '--port' && args[i + 1]) {
258
+ flags.port = parseInt(args[++i], 10);
259
+ } else if (arg === '--workspace' && args[i + 1]) {
260
+ flags.workspace = resolve(args[++i]);
261
+ } else if (arg === '--db' && args[i + 1]) {
262
+ flags.db = resolve(args[++i]);
263
+ }
264
+ }
265
+ return flags;
266
+ }
267
+
268
+ async function main() {
269
+ const args = process.argv.slice(2);
270
+ const command = args[0] || 'help';
271
+
272
+ if (command === 'help' || command === '--help' || command === '-h') {
273
+ printUsage();
274
+ process.exit(0);
275
+ }
276
+
277
+ const flags = parseFlags(args.slice(1));
278
+ const cwd = flags.workspace || getWorkspacePath(process.cwd());
279
+ const dbPath = flags.db || getDbPath(cwd);
280
+ const { store, projectService, checkpointService, briefingService, handoffService, reasoningService, llmProviderError } = getServices(dbPath, cwd);
281
+
282
+ try {
283
+ switch (command) {
284
+ case 'init': {
285
+ const name = args[1] || null;
286
+ const project = projectService.initProject(cwd, name);
287
+ console.log(`Initialized VectorForce project: ${project.name} (${project.project_id})`);
288
+ break;
289
+ }
290
+
291
+ case 'task': {
292
+ const sub = args[1];
293
+ if (sub === 'complete' || sub === 'done') {
294
+ const taskId = args[2];
295
+ if (!taskId) {
296
+ console.error('Error: task id required (e.g. vf task complete phase-5-pilot)');
297
+ process.exit(1);
298
+ }
299
+ const task = projectService.updateTaskStatus(cwd, taskId, 'completed');
300
+ console.log(`Task marked as completed: ${task.title} (${task.task_id}) [${task.status}]`);
301
+ break;
302
+ }
303
+ const taskId = sub;
304
+ if (!taskId) {
305
+ console.error('Error: task id required (e.g. vf task feat-sqlite "Implement SQLite store")');
306
+ process.exit(1);
307
+ }
308
+ const title = args.slice(2).join(' ') || taskId;
309
+ const task = projectService.createTask(cwd, taskId, title);
310
+ console.log(`Task registered: ${task.title} (${task.task_id}) [${task.status}]`);
311
+ break;
312
+ }
313
+
314
+ case 'tasks': {
315
+ const filter = args[1] || 'active';
316
+ const project = projectService.getProjectForWorkspace(cwd);
317
+ const tasks = store.listTasks(project.project_id, filter);
318
+ console.log(`Tasks for ${project.name} [${filter}]:`);
319
+ for (const t of tasks) {
320
+ console.log(`- [${t.status}] ${t.task_id}: ${t.title}`);
321
+ }
322
+ break;
323
+ }
324
+
325
+ case 'policy': {
326
+ const sub = args[1];
327
+ const project = projectService.getProjectForWorkspace(cwd);
328
+ if (sub === 'list') {
329
+ const list = store.getDocumentPolicy(project.project_id);
330
+ console.log(`Document allowlist for ${project.name}:`);
331
+ if (list.length === 0) console.log(' (no documents allowlisted)');
332
+ for (const p of list) console.log(` - ${p}`);
333
+ } else if (sub === 'add') {
334
+ const docPath = args[2];
335
+ if (!docPath) {
336
+ console.error('Error: relative document path required');
337
+ process.exit(1);
338
+ }
339
+ const current = store.getDocumentPolicy(project.project_id);
340
+ const updated = store.setDocumentPolicy(project.project_id, [...current, docPath]);
341
+ console.log(`Updated allowlist: ${updated.join(', ')}`);
342
+ } else {
343
+ console.error('Unknown policy subcommand. Use "vf policy list" or "vf policy add <path>"');
344
+ process.exit(1);
345
+ }
346
+ break;
347
+ }
348
+
349
+ case 'checkpoint': {
350
+ const flags = parseFlags(args.slice(1));
351
+ if (!flags.summary) {
352
+ console.error('Error: --summary <text> is required');
353
+ process.exit(1);
354
+ }
355
+ const receipt = checkpointService.captureCheckpoint({
356
+ workspacePath: cwd,
357
+ taskId: flags.task,
358
+ summary: flags.summary,
359
+ goals: flags.goals,
360
+ constraints: flags.constraints,
361
+ decisions: flags.decisions,
362
+ nextSteps: flags.nextSteps,
363
+ requestedDocuments: flags.docs,
364
+ includeGit: flags.includeGit,
365
+ authorName: process.env.USER || 'developer',
366
+ authorHarness: 'cli'
367
+ });
368
+ console.log(`Checkpoint saved: ${receipt.reference_id} [${receipt.status}]`);
369
+ if (receipt.warnings.length > 0) {
370
+ for (const w of receipt.warnings) console.warn(`Warning: ${w}`);
371
+ }
372
+ break;
373
+ }
374
+
375
+ case 'resume': {
376
+ const flags = parseFlags(args.slice(1));
377
+ const result = briefingService.generateBriefing({
378
+ workspacePath: cwd,
379
+ taskId: flags.task,
380
+ checkpointId: flags.checkpoint,
381
+ destination: flags.dest || 'cli'
382
+ });
383
+ console.log(result.briefingText);
384
+ break;
385
+ }
386
+
387
+ case 'drift': {
388
+ const flags = parseFlags(args.slice(1));
389
+ const result = briefingService.generateBriefing({
390
+ workspacePath: cwd,
391
+ taskId: flags.task,
392
+ checkpointId: flags.checkpoint,
393
+ destination: 'cli'
394
+ });
395
+ if (result.driftDetected) {
396
+ console.log('āš ļø DRIFT DETECTED:');
397
+ for (const w of result.warnings) console.log(`- ${w}`);
398
+ } else {
399
+ console.log('āœ” No drift detected: workspace and document state match recorded checkpoint.');
400
+ }
401
+ break;
402
+ }
403
+
404
+ case 'reason':
405
+ case 'continue': {
406
+ const flags = parseFlags(args.slice(1));
407
+ if (flags.raw) {
408
+ const raw = briefingService.generateBriefing({
409
+ workspacePath: cwd,
410
+ taskId: flags.task,
411
+ checkpointId: flags.checkpoint,
412
+ destination: flags.dest || 'cli'
413
+ });
414
+ console.log(raw.briefingText);
415
+ break;
416
+ }
417
+ if (llmProviderError) {
418
+ console.warn(`Warning: LLM configuration issue: ${llmProviderError}`);
419
+ }
420
+ const result = await reasoningService.reasonAboutTask({
421
+ workspacePath: cwd,
422
+ taskId: flags.task,
423
+ checkpointId: flags.checkpoint,
424
+ destination: flags.dest || 'cli',
425
+ focus: flags.focus,
426
+ model: flags.model,
427
+ maxTokens: flags.maxTokens ? parseInt(flags.maxTokens, 10) : 4000,
428
+ includeCode: flags.includeCode !== false
429
+ });
430
+ console.log(result.narrativeText);
431
+ if (result.degraded) {
432
+ console.error(`\n⚠ AI reasoning unavailable${result.error ? ` (${result.error})` : ''} — showing deterministic briefing.`);
433
+ } else {
434
+ console.error(`\n— reasoned by ${result.provider}/${result.model} Ā· ${result.codeFilesConsidered} code file(s) considered`);
435
+ }
436
+ break;
437
+ }
438
+
439
+ case 'export':
440
+ case 'export-handoff': {
441
+ const flags = parseFlags(args.slice(1));
442
+ if (!flags.task) {
443
+ console.error('Error: --task <id> is required for handoff export.');
444
+ process.exit(1);
445
+ }
446
+ if (!flags.recipient) {
447
+ console.error('Error: --recipient <name> is required for handoff export.');
448
+ process.exit(1);
449
+ }
450
+ const result = handoffService.exportHandoff({
451
+ workspacePath: cwd,
452
+ taskId: flags.task,
453
+ checkpointId: flags.checkpoint,
454
+ recipient: flags.recipient,
455
+ destinationHarness: flags.dest || 'any',
456
+ preview: flags.preview === true,
457
+ outPath: flags.outPath
458
+ });
459
+ if (result.preview) {
460
+ console.log(`\nāœ” Handoff Preview for [${flags.task}] to ${flags.recipient}:`);
461
+ console.log(JSON.stringify(result.handoff, null, 2));
462
+ } else {
463
+ console.log(`\nāœ” Handoff exported successfully: ${result.package_id}`);
464
+ console.log(` Bundle Path: ${result.bundlePath}`);
465
+ console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
466
+ }
467
+ break;
468
+ }
469
+
470
+ case 'import':
471
+ case 'import-handoff': {
472
+ const bundlePath = args[1];
473
+ if (!bundlePath || bundlePath.startsWith('--')) {
474
+ console.error('Error: Package path required: vf import <package_path> [--task <id>]');
475
+ process.exit(1);
476
+ }
477
+ const flags = parseFlags(args.slice(2));
478
+ const result = handoffService.importHandoff({
479
+ workspacePath: cwd,
480
+ bundlePath,
481
+ targetTaskId: flags.task
482
+ });
483
+ console.log(`\nāœ” Handoff imported successfully into task [${result.task.task_id}]`);
484
+ console.log(` Imported Checkpoint: ${result.importedCheckpointId}`);
485
+ console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
486
+ break;
487
+ }
488
+
489
+ case 'mcp': {
490
+ const server = new McpServer({ store, projectService, checkpointService, briefingService, handoffService, reasoningService }, cwd);
491
+ server.connect(process.stdin, process.stdout);
492
+ process.on('SIGINT', () => {
493
+ store.close();
494
+ process.exit(0);
495
+ });
496
+ process.on('SIGTERM', () => {
497
+ store.close();
498
+ process.exit(0);
499
+ });
500
+ // Keep process alive for stdio stream; do not execute finally store.close()
501
+ return;
502
+ }
503
+
504
+ case 'config': {
505
+ const sub = args[1];
506
+ if (sub === 'install' || sub === 'setup') {
507
+ const target = (args[2] || 'all').toLowerCase();
508
+ const installed = [];
509
+ if (target === 'antigravity' || target === 'all') {
510
+ const p = HarnessConfig.installAntigravity(cwd);
511
+ installed.push(`Antigravity -> ${p}`);
512
+ }
513
+ if (target === 'command-code' || target === 'cmd' || target === 'all') {
514
+ const p = HarnessConfig.installCommandCode(cwd);
515
+ installed.push(`Command Code -> ${p}`);
516
+ }
517
+ if (target === 'muse' || target === 'muse-code' || target === 'all') {
518
+ const p = HarnessConfig.installMuseCode(cwd);
519
+ installed.push(`Muse Code -> ${p}`);
520
+ }
521
+ if (target === 'codex' || target === 'all') {
522
+ const p = HarnessConfig.installCodex(cwd);
523
+ installed.push(`Codex -> ${p}`);
524
+ }
525
+ console.log(`\nāœ” Configured harness integration(s):\n - ${installed.join('\n - ')}`);
526
+ break;
527
+ }
528
+
529
+ if (sub === 'uninstall' || sub === 'remove') {
530
+ const target = (args[2] || 'all').toLowerCase();
531
+ const uninstalled = [];
532
+ if (target === 'antigravity' || target === 'all') {
533
+ if (HarnessConfig.uninstallAntigravity(cwd)) uninstalled.push('Antigravity');
534
+ }
535
+ if (target === 'command-code' || target === 'cmd' || target === 'all') {
536
+ if (HarnessConfig.uninstallCommandCode(cwd)) uninstalled.push('Command Code');
537
+ }
538
+ if (target === 'muse' || target === 'muse-code' || target === 'all') {
539
+ if (HarnessConfig.uninstallMuseCode(cwd)) uninstalled.push('Muse Code');
540
+ }
541
+ if (target === 'codex' || target === 'all') {
542
+ if (HarnessConfig.uninstallCodex(cwd)) uninstalled.push('Codex');
543
+ }
544
+ console.log(`\nāœ” Removed harness integration(s): ${uninstalled.length ? uninstalled.join(', ') : 'none found'}`);
545
+ break;
546
+ }
547
+
548
+ if (sub === 'set') {
549
+ const key = args[2];
550
+ const val = args[3];
551
+ if (!key || val === undefined) {
552
+ console.error('Usage: node bin/vf.js config set <KEY> <VALUE>');
553
+ process.exit(1);
554
+ }
555
+ const vfDir = join(cwd, '.vectorforce');
556
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
557
+ const cfgPath = join(vfDir, 'config.json');
558
+ let existing = {};
559
+ if (existsSync(cfgPath)) {
560
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
561
+ }
562
+ existing[key] = val;
563
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
564
+ console.log(`\nāœ” Saved configuration [${key}] in ${cfgPath}`);
565
+ break;
566
+ }
567
+
568
+ if (sub === 'openrouter') {
569
+ const key = args[2];
570
+ const model = args[3] || 'deepseek/deepseek-v4.1-flash-1017';
571
+ if (!key) {
572
+ console.error('Usage: node bin/vf.js config openrouter <API_KEY> [MODEL]');
573
+ process.exit(1);
574
+ }
575
+ const vfDir = join(cwd, '.vectorforce');
576
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
577
+ const cfgPath = join(vfDir, 'config.json');
578
+ let existing = {};
579
+ if (existsSync(cfgPath)) {
580
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
581
+ }
582
+ existing.VF_LLM_PROVIDER = 'openrouter';
583
+ existing.OPENROUTER_API_KEY = key;
584
+ existing.VF_OPENROUTER_MODEL = model;
585
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
586
+ console.log(`\nāœ” Configured OpenRouter in ${cfgPath}`);
587
+ console.log(` Provider: openrouter`);
588
+ console.log(` Model: ${model}`);
589
+ console.log(` Base URL: https://openrouter.ai/api/v1`);
590
+ break;
591
+ }
592
+
593
+ if (sub === 'muse') {
594
+ const key = args[2];
595
+ const model = args[3] || 'muse-spark-1.3';
596
+ if (!key) {
597
+ console.error('Usage: node bin/vf.js config muse <API_KEY> [MODEL]');
598
+ process.exit(1);
599
+ }
600
+ const vfDir = join(cwd, '.vectorforce');
601
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
602
+ const cfgPath = join(vfDir, 'config.json');
603
+ let existing = {};
604
+ if (existsSync(cfgPath)) {
605
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
606
+ }
607
+ existing.VF_LLM_PROVIDER = 'muse';
608
+ existing.MODEL_API_KEY = key;
609
+ existing.VF_MUSE_MODEL = model;
610
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
611
+ console.log(`\nāœ” Configured Muse in ${cfgPath}`);
612
+ console.log(` Provider: muse`);
613
+ console.log(` Model: ${model}`);
614
+ console.log(` Base URL: https://api.meta.ai/v1`);
615
+ break;
616
+ }
617
+
618
+ if (sub === 'show' || sub === 'status') {
619
+ const cfg = resolveLlmConfig(process.env, {}, cwd);
620
+ console.log('\nVectorForce AI Reasoning Configuration:');
621
+ console.log(` Provider: ${cfg.provider}`);
622
+ console.log(` Model: ${cfg.model}`);
623
+ console.log(` Base URL: ${cfg.baseUrl}`);
624
+ console.log(` API Key: ${cfg.apiKey ? 'configured (hidden)' : 'none (falling back to local)'}`);
625
+ break;
626
+ }
627
+
628
+ const binPath = resolve(process.argv[1]);
629
+ console.log(`
630
+ VectorForce Harness Configuration:
631
+
632
+ 1. Google Antigravity Configuration:
633
+ File: .agents/mcp_config.json (or ~/.gemini/config/mcp_config.json)
634
+ Content:
635
+ {
636
+ "mcpServers": {
637
+ "vectorforce": {
638
+ "command": "node",
639
+ "args": ["${binPath}", "mcp"]
640
+ }
641
+ }
642
+ }
643
+
644
+ 2. Meta Muse Code (muse-code) Configuration:
645
+ File: .muse/mcp_config.json (or musecode.json)
646
+ Content:
647
+ {
648
+ "mcpServers": {
649
+ "vectorforce": {
650
+ "command": "node",
651
+ "args": ["${binPath}", "mcp"],
652
+ "env": { "VF_HARNESS": "muse-code" }
653
+ }
654
+ }
655
+ }
656
+
657
+ 3. Command Code (command-code) Configuration:
658
+ Run command:
659
+ cmd mcp add --transport stdio vectorforce -- node ${binPath} mcp
660
+
661
+ 4. OpenAI Codex (codex) Configuration:
662
+ File: .codex/config.toml (or codex.json)
663
+ Content:
664
+ [mcp_servers.vectorforce]
665
+ command = "node"
666
+ args = ["${binPath}", "mcp"]
667
+ [mcp_servers.vectorforce.env]
668
+ VF_HARNESS = "codex"
669
+
670
+ Or run command:
671
+ codex mcp add vectorforce -- node ${binPath} mcp
672
+
673
+ Automated Setup:
674
+ node bin/vf.js config install codex
675
+ node bin/vf.js config install all
676
+
677
+ 5. AI Reasoning (vf reason / reason_about_task):
678
+ Local Ollama (default, 100% private):
679
+ VF_LLM_PROVIDER=ollama
680
+ VF_OLLAMA_BASE_URL=http://127.0.0.1:11434
681
+ VF_OLLAMA_MODEL=llama3.1:8b
682
+
683
+ Cloud Meta Muse (Responses API):
684
+ VF_LLM_PROVIDER=muse
685
+ MODEL_API_KEY=<your-key>
686
+ VF_MUSE_MODEL=muse-spark-1.3 # default
687
+ VF_MUSE_BASE_URL=https://api.meta.ai/v1 # default
688
+ # Or: node bin/vf.js config muse <API_KEY> [MODEL]
689
+
690
+ Cloud OpenAI Codex / GPT:
691
+ VF_LLM_PROVIDER=codex
692
+ VF_CODEX_BASE_URL=https://api.openai.com/v1
693
+ VF_CODEX_API_KEY=<your-key>
694
+ VF_CODEX_MODEL=o3-mini
695
+
696
+ Cloud OpenRouter (DeepSeek v4.1 Flash / Multi-Model):
697
+ VF_LLM_PROVIDER=openrouter
698
+ OPENROUTER_API_KEY=<your-key> # or VF_OPENROUTER_API_KEY
699
+ VF_OPENROUTER_MODEL=deepseek/deepseek-v4.1-flash-1017 # default
700
+
701
+ Privacy: set VF_LLM_ALLOW_CODE=false (or run with --no-code) to keep source out of the prompt.
702
+ `);
703
+ break;
704
+ }
705
+
706
+ case 'studio':
707
+ case 'ui': {
708
+ const flags = parseFlags(args.slice(1));
709
+ const port = flags.port || 3210;
710
+ const studio = createStudioServer({
711
+ dbPath: getDbPath(),
712
+ workspacePath: cwd,
713
+ port
714
+ });
715
+ const url = await studio.start();
716
+ console.log(`\nšŸš€ VectorForce Studio is running at: ${url}`);
717
+ console.log(` Connected to SQLite: ${getDbPath()}`);
718
+ console.log(` Interactive Topology & AI Memory: ${url}`);
719
+ console.log(` Press Ctrl+C to stop.\n`);
720
+
721
+ try {
722
+ const { exec } = await import('node:child_process');
723
+ exec(`open ${url}`);
724
+ } catch {}
725
+
726
+ process.on('SIGINT', async () => {
727
+ await studio.stop();
728
+ process.exit(0);
729
+ });
730
+ process.on('SIGTERM', async () => {
731
+ await studio.stop();
732
+ process.exit(0);
733
+ });
734
+ return;
735
+ }
736
+
737
+ default:
738
+ console.error(`Unknown command: ${command}`);
739
+ printUsage();
740
+ process.exit(1);
741
+ }
742
+ } catch (err) {
743
+ if (err instanceof VectorForceError) {
744
+ console.error(`VectorForce Error [${err.code}]: ${err.message}`);
745
+ } else {
746
+ console.error(`Error: ${err.message}`);
747
+ }
748
+ process.exit(1);
749
+ } finally {
750
+ if (command !== 'mcp' && command !== 'studio' && command !== 'ui') {
751
+ store.close();
752
+ }
753
+ }
754
+ }
755
+
756
+ main();