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.
Files changed (37) hide show
  1. package/.agents/agents/AGENTS.md +12 -0
  2. package/.agents/agents/vectorforce-architect.md +35 -0
  3. package/.agents/agents/vectorforce-auditor.md +25 -0
  4. package/.agents/agents/vectorforce-samples-builder.md +30 -0
  5. package/.agents/mcp_config.json +11 -0
  6. package/.codex/config.toml +5 -0
  7. package/AGENTS.md +115 -0
  8. package/LICENSE +21 -0
  9. package/README.md +198 -2
  10. package/bin/vf.js +1159 -0
  11. package/codex.json +14 -0
  12. package/commandcode.json +11 -0
  13. package/musecode.json +14 -0
  14. package/package.json +40 -4
  15. package/src/core/errors.js +45 -0
  16. package/src/core/hash.js +56 -0
  17. package/src/core/types.js +194 -0
  18. package/src/index.js +17 -0
  19. package/src/mcp/harness-config.js +546 -0
  20. package/src/mcp/mcp-server.js +507 -0
  21. package/src/operations/briefing-service.js +313 -0
  22. package/src/operations/checkpoint-service.js +120 -0
  23. package/src/operations/handoff-service.js +343 -0
  24. package/src/operations/project-service.js +132 -0
  25. package/src/operations/reasoning-service.js +356 -0
  26. package/src/policy/authorizer.js +147 -0
  27. package/src/providers/code-index-provider.js +285 -0
  28. package/src/providers/document-provider.js +105 -0
  29. package/src/providers/git-provider.js +98 -0
  30. package/src/providers/llm-provider.js +425 -0
  31. package/src/storage/sqlite-store.js +387 -0
  32. package/src/studio/server.js +764 -0
  33. package/studio/dist/assets/index-C0_eVpV1.js +9 -0
  34. package/studio/dist/assets/index-CxTK-2ty.css +2 -0
  35. package/studio/dist/favicon.svg +1 -0
  36. package/studio/dist/icons.svg +24 -0
  37. package/studio/dist/index.html +17 -0
package/bin/vf.js ADDED
@@ -0,0 +1,1159 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { homedir, hostname } from 'node:os';
4
+ import { resolve, join, dirname, basename } 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.slice(2).join(':') || 'Approved architecture decision'
236
+ });
237
+ } else if (arg === '--harness' && args[i + 1]) {
238
+ flags.harness = args[++i];
239
+ } else if (arg === '--author' && args[i + 1]) {
240
+ flags.author = args[++i];
241
+ } else if (arg === '--next' && args[i + 1]) {
242
+ flags.nextSteps.push(args[++i]);
243
+ } else if (arg === '--doc' && args[i + 1]) {
244
+ flags.docs.push(args[++i]);
245
+ } else if (arg === '--model' && args[i + 1]) {
246
+ flags.model = args[++i];
247
+ } else if (arg === '--focus' && args[i + 1]) {
248
+ flags.focus = args[++i];
249
+ } else if (arg === '--no-code') {
250
+ flags.includeCode = false;
251
+ } else if (arg === '--raw') {
252
+ flags.raw = true;
253
+ } else if (arg === '--no-git') {
254
+ flags.includeGit = false;
255
+ } else if (arg === '--recipient' && args[i + 1]) {
256
+ flags.recipient = args[++i];
257
+ } else if (arg === '--preview') {
258
+ flags.preview = true;
259
+ } else if (arg === '--out' && args[i + 1]) {
260
+ flags.outPath = args[++i];
261
+ } else if (arg === '--port' && args[i + 1]) {
262
+ flags.port = parseInt(args[++i], 10);
263
+ } else if (arg === '--workspace' && args[i + 1]) {
264
+ flags.workspace = resolve(args[++i]);
265
+ } else if (arg === '--db' && args[i + 1]) {
266
+ flags.db = resolve(args[++i]);
267
+ } else if ((arg === '--url' || arg === '--remote') && args[i + 1]) {
268
+ flags.url = args[++i];
269
+ } else if (arg === '--key' && args[i + 1]) {
270
+ flags.key = args[++i];
271
+ } else if (arg === '--project' && args[i + 1]) {
272
+ flags.project = args[++i];
273
+ }
274
+ }
275
+ return flags;
276
+ }
277
+
278
+ function getRemoteConfig(cwd) {
279
+ const defaultUrl = process.env.VF_CLOUD_URL || 'https://omprakash-pce2011--vectorforce-cloud-vectorforce-cloud-web.modal.run';
280
+ const defaultKey = process.env.VF_SUBSCRIPTION_KEY || 'vf_live_sub_98a7bc65f012e4d9';
281
+ const cfgPath = join(cwd, '.vectorforce', 'config.json');
282
+ try {
283
+ if (existsSync(cfgPath)) {
284
+ const parsed = JSON.parse(readFileSync(cfgPath, 'utf8'));
285
+ return {
286
+ remoteUrl: (parsed.remoteUrl || defaultUrl).replace(/\/+$/, ''),
287
+ subscriptionKey: parsed.subscriptionKey || defaultKey,
288
+ remoteProjectId: parsed.remoteProjectId || 'proj-bistro-pos'
289
+ };
290
+ }
291
+ } catch {}
292
+ return {
293
+ remoteUrl: defaultUrl.replace(/\/+$/, ''),
294
+ subscriptionKey: defaultKey,
295
+ remoteProjectId: 'proj-bistro-pos'
296
+ };
297
+ }
298
+
299
+ function saveRemoteConfig(cwd, updates) {
300
+ const vfDir = join(cwd, '.vectorforce');
301
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
302
+ const cfgPath = join(vfDir, 'config.json');
303
+ let existing = {};
304
+ try {
305
+ if (existsSync(cfgPath)) {
306
+ existing = JSON.parse(readFileSync(cfgPath, 'utf8'));
307
+ }
308
+ } catch {}
309
+ const merged = { ...existing, ...updates };
310
+ writeFileSync(cfgPath, JSON.stringify(merged, null, 2), 'utf8');
311
+ return merged;
312
+ }
313
+
314
+ async function main() {
315
+ const args = process.argv.slice(2);
316
+ const command = args[0] || 'help';
317
+
318
+ if (command === 'help' || command === '--help' || command === '-h') {
319
+ printUsage();
320
+ process.exit(0);
321
+ }
322
+
323
+ const flags = parseFlags(args.slice(1));
324
+ const cwd = flags.workspace || getWorkspacePath(process.cwd());
325
+ const dbPath = flags.db || getDbPath(cwd);
326
+ const { store, projectService, checkpointService, briefingService, handoffService, reasoningService, llmProviderError } = getServices(dbPath, cwd);
327
+
328
+ try {
329
+ switch (command) {
330
+ case 'init': {
331
+ const name = args[1] && !args[1].startsWith('--') ? args[1] : null;
332
+ const project = projectService.initProject(cwd, name);
333
+ HarnessConfig.installAntigravity(cwd);
334
+ HarnessConfig.installCommandCode(cwd);
335
+ HarnessConfig.installMuseCode(cwd);
336
+ HarnessConfig.installCodex(cwd);
337
+ const agentsMdPath = join(cwd, 'AGENTS.md');
338
+ if (!existsSync(agentsMdPath)) {
339
+ writeFileSync(
340
+ agentsMdPath,
341
+ `# Agent Continuity Protocol — ${project.name}\n\nThis repository uses **VectorForce** ("Git for AI Context") across Google Antigravity, Meta Muse, Claude Code, Command Code, and OpenAI Codex.\n\n## Workflow\n1. **Start/Resume Task**: Run \`vf tasks active\`, \`vf drift --task <id>\`, and \`vf resume --task <id> --dest <harness>\` (or MCP tools \`list_tasks\`, \`check_workspace_drift\`, \`get_resume_briefing\`, \`reason_about_task\`).\n2. **Save Checkpoint Before Switching IDE**: Run \`vf checkpoint --task <id> --summary "<summary>" --goal "<goal>" --constraint "<constraint>" --decision "<ID>:<Title>:<Rationale>" --next "<next>" --doc "AGENTS.md"\` (or MCP tool \`save_checkpoint\`).\n`,
342
+ 'utf8'
343
+ );
344
+ }
345
+ console.log(`Initialized VectorForce project: ${project.name} (${project.project_id})`);
346
+ break;
347
+ }
348
+
349
+ case 'task': {
350
+ const sub = args[1];
351
+ if (sub === 'complete' || sub === 'done') {
352
+ const taskId = args[2];
353
+ if (!taskId) {
354
+ console.error('Error: task id required (e.g. vf task complete phase-5-pilot)');
355
+ process.exit(1);
356
+ }
357
+ const task = projectService.updateTaskStatus(cwd, taskId, 'completed');
358
+ console.log(`Task marked as completed: ${task.title} (${task.task_id}) [${task.status}]`);
359
+ break;
360
+ }
361
+ const isCreateSub = sub === 'create' || sub === 'add';
362
+ const rawArgs = isCreateSub ? args.slice(2) : args.slice(1);
363
+ const taskId = rawArgs[0];
364
+ if (!taskId) {
365
+ console.error('Error: task id required (e.g. vf task feat-sqlite "Implement SQLite store")');
366
+ process.exit(1);
367
+ }
368
+ const title = rawArgs.slice(1).join(' ') || taskId;
369
+ const task = projectService.createTask(cwd, taskId, title);
370
+ console.log(`Task registered: ${task.title} (${task.task_id}) [${task.status}]`);
371
+ break;
372
+ }
373
+
374
+ case 'tasks': {
375
+ const filter = args[1] || 'active';
376
+ const project = projectService.getProjectForWorkspace(cwd);
377
+ const tasks = store.listTasks(project.project_id, filter);
378
+ console.log(`Tasks for ${project.name} [${filter}]:`);
379
+ for (const t of tasks) {
380
+ console.log(`- [${t.status}] ${t.task_id}: ${t.title}`);
381
+ }
382
+ break;
383
+ }
384
+
385
+ case 'policy': {
386
+ const sub = args[1];
387
+ const project = projectService.getProjectForWorkspace(cwd);
388
+ if (sub === 'list') {
389
+ const list = store.getDocumentPolicy(project.project_id);
390
+ console.log(`Document allowlist for ${project.name}:`);
391
+ if (list.length === 0) console.log(' (no documents allowlisted)');
392
+ for (const p of list) console.log(` - ${p}`);
393
+ } else if (sub === 'add') {
394
+ const docPath = args[2];
395
+ if (!docPath) {
396
+ console.error('Error: relative document path required');
397
+ process.exit(1);
398
+ }
399
+ const current = store.getDocumentPolicy(project.project_id);
400
+ const updated = store.setDocumentPolicy(project.project_id, [...current, docPath]);
401
+ console.log(`Updated allowlist: ${updated.join(', ')}`);
402
+ } else {
403
+ console.error('Unknown policy subcommand. Use "vf policy list" or "vf policy add <path>"');
404
+ process.exit(1);
405
+ }
406
+ break;
407
+ }
408
+
409
+ case 'checkpoint': {
410
+ const flags = parseFlags(args.slice(1));
411
+ if (!flags.summary) {
412
+ console.error('Error: --summary <text> is required');
413
+ process.exit(1);
414
+ }
415
+ const receipt = checkpointService.captureCheckpoint({
416
+ workspacePath: cwd,
417
+ taskId: flags.task,
418
+ summary: flags.summary,
419
+ goals: flags.goals,
420
+ constraints: flags.constraints,
421
+ decisions: flags.decisions,
422
+ nextSteps: flags.nextSteps,
423
+ requestedDocuments: flags.docs,
424
+ includeGit: flags.includeGit,
425
+ authorName: flags.author || process.env.VF_AUTHOR || process.env.USER || 'developer',
426
+ authorHarness: flags.harness || process.env.VF_HARNESS || 'cli'
427
+ });
428
+ console.log(`Checkpoint saved: ${receipt.reference_id} [${receipt.status}]`);
429
+ if (receipt.warnings.length > 0) {
430
+ for (const w of receipt.warnings) console.warn(`Warning: ${w}`);
431
+ }
432
+ break;
433
+ }
434
+
435
+ case 'resume': {
436
+ const flags = parseFlags(args.slice(1));
437
+ const result = briefingService.generateBriefing({
438
+ workspacePath: cwd,
439
+ taskId: flags.task,
440
+ checkpointId: flags.checkpoint,
441
+ destination: flags.dest || 'cli'
442
+ });
443
+ console.log(result.briefingText);
444
+ break;
445
+ }
446
+
447
+ case 'drift': {
448
+ const flags = parseFlags(args.slice(1));
449
+ const result = briefingService.generateBriefing({
450
+ workspacePath: cwd,
451
+ taskId: flags.task,
452
+ checkpointId: flags.checkpoint,
453
+ destination: 'cli'
454
+ });
455
+ if (result.driftDetected) {
456
+ console.log('āš ļø DRIFT DETECTED:');
457
+ for (const w of result.warnings) console.log(`- ${w}`);
458
+ } else {
459
+ console.log('āœ” No drift detected: workspace and document state match recorded checkpoint.');
460
+ }
461
+ break;
462
+ }
463
+
464
+ case 'reason':
465
+ case 'continue': {
466
+ const flags = parseFlags(args.slice(1));
467
+ if (flags.raw) {
468
+ const raw = briefingService.generateBriefing({
469
+ workspacePath: cwd,
470
+ taskId: flags.task,
471
+ checkpointId: flags.checkpoint,
472
+ destination: flags.dest || 'cli'
473
+ });
474
+ console.log(raw.briefingText);
475
+ break;
476
+ }
477
+ if (llmProviderError) {
478
+ console.warn(`Warning: LLM configuration issue: ${llmProviderError}`);
479
+ }
480
+ const result = await reasoningService.reasonAboutTask({
481
+ workspacePath: cwd,
482
+ taskId: flags.task,
483
+ checkpointId: flags.checkpoint,
484
+ destination: flags.dest || 'cli',
485
+ focus: flags.focus,
486
+ model: flags.model,
487
+ maxTokens: flags.maxTokens ? parseInt(flags.maxTokens, 10) : 4000,
488
+ includeCode: flags.includeCode !== false
489
+ });
490
+ console.log(result.narrativeText);
491
+ if (result.degraded) {
492
+ console.error(`\n⚠ AI reasoning unavailable${result.error ? ` (${result.error})` : ''} — showing deterministic briefing.`);
493
+ } else {
494
+ console.error(`\n— reasoned by ${result.provider}/${result.model} Ā· ${result.codeFilesConsidered} code file(s) considered`);
495
+ }
496
+ break;
497
+ }
498
+
499
+ case 'export':
500
+ case 'export-handoff': {
501
+ const flags = parseFlags(args.slice(1));
502
+ if (!flags.task) {
503
+ console.error('Error: --task <id> is required for handoff export.');
504
+ process.exit(1);
505
+ }
506
+ if (!flags.recipient) {
507
+ console.error('Error: --recipient <name> is required for handoff export.');
508
+ process.exit(1);
509
+ }
510
+ const result = handoffService.exportHandoff({
511
+ workspacePath: cwd,
512
+ taskId: flags.task,
513
+ checkpointId: flags.checkpoint,
514
+ recipient: flags.recipient,
515
+ destinationHarness: flags.dest || 'any',
516
+ preview: flags.preview === true,
517
+ outPath: flags.outPath
518
+ });
519
+ if (result.preview) {
520
+ console.log(`\nāœ” Handoff Preview for [${flags.task}] to ${flags.recipient}:`);
521
+ console.log(JSON.stringify(result.handoff, null, 2));
522
+ } else {
523
+ console.log(`\nāœ” Handoff exported successfully: ${result.package_id}`);
524
+ console.log(` Bundle Path: ${result.bundlePath}`);
525
+ console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
526
+ }
527
+ break;
528
+ }
529
+
530
+ case 'import':
531
+ case 'import-handoff': {
532
+ const bundlePath = args[1];
533
+ if (!bundlePath || bundlePath.startsWith('--')) {
534
+ console.error('Error: Package path required: vf import <package_path> [--task <id>]');
535
+ process.exit(1);
536
+ }
537
+ const flags = parseFlags(args.slice(2));
538
+ const result = handoffService.importHandoff({
539
+ workspacePath: cwd,
540
+ bundlePath,
541
+ targetTaskId: flags.task
542
+ });
543
+ console.log(`\nāœ” Handoff imported successfully into task [${result.task.task_id}]`);
544
+ console.log(` Imported Checkpoint: ${result.importedCheckpointId}`);
545
+ console.log(` Receipt: ${result.receipt.operation_id} [${result.receipt.status}]`);
546
+ break;
547
+ }
548
+
549
+ case 'mcp': {
550
+ const server = new McpServer({ store, projectService, checkpointService, briefingService, handoffService, reasoningService }, cwd);
551
+ server.connect(process.stdin, process.stdout);
552
+ process.on('SIGINT', () => {
553
+ store.close();
554
+ process.exit(0);
555
+ });
556
+ process.on('SIGTERM', () => {
557
+ store.close();
558
+ process.exit(0);
559
+ });
560
+ // Keep process alive for stdio stream; do not execute finally store.close()
561
+ return;
562
+ }
563
+
564
+ case 'config': {
565
+ const sub = args[1];
566
+ if (sub === 'install' || sub === 'setup') {
567
+ const target = (args[2] || 'all').toLowerCase();
568
+ const installed = [];
569
+ if (target === 'antigravity' || target === 'all') {
570
+ const p = HarnessConfig.installAntigravity(cwd);
571
+ installed.push(`Google Antigravity -> ${p} (+ ~/.gemini/antigravity/mcp_config.json)`);
572
+ }
573
+ if (target === 'command-code' || target === 'cmd' || target === 'claude' || target === 'claude-code' || target === 'all') {
574
+ const p = HarnessConfig.installCommandCode(cwd);
575
+ installed.push(`Command Code / Claude Code -> .mcp.json & ${p}`);
576
+ }
577
+ if (target === 'muse' || target === 'muse-code' || target === 'all') {
578
+ const p = HarnessConfig.installMuseCode(cwd);
579
+ installed.push(`Meta Muse -> ${p} (+ ~/.config/muse/settings.json)`);
580
+ }
581
+ if (target === 'codex' || target === 'all') {
582
+ const p = HarnessConfig.installCodex(cwd);
583
+ installed.push(`Codex -> ${p} (+ ~/.codex/config.toml)`);
584
+ }
585
+ console.log(`\nāœ” Configured harness integration(s):\n - ${installed.join('\n - ')}`);
586
+ console.log(`\nšŸ’” Next step: Restart or reload your AI agent, then ask in chat:\n "List active VectorForce tasks and check workspace drift"`);
587
+ break;
588
+ }
589
+
590
+ if (sub === 'doctor' || sub === 'check') {
591
+ const checks = [
592
+ { name: 'SQLite Database (.vectorforce/vectorforce.db)', ok: existsSync(getDbPath(cwd)), path: getDbPath(cwd) },
593
+ { name: 'Agent Protocol Rules (AGENTS.md)', ok: existsSync(join(cwd, 'AGENTS.md')), path: join(cwd, 'AGENTS.md') },
594
+ { name: 'Google Antigravity (.agents/mcp_config.json)', ok: existsSync(join(cwd, '.agents', 'mcp_config.json')), path: join(cwd, '.agents', 'mcp_config.json') },
595
+ { name: 'Claude Code & Command Code (.mcp.json)', ok: existsSync(join(cwd, '.mcp.json')), path: join(cwd, '.mcp.json') },
596
+ { name: 'Meta Muse (.muse/mcp_config.json)', ok: existsSync(join(cwd, '.muse', 'mcp_config.json')), path: join(cwd, '.muse', 'mcp_config.json') },
597
+ { name: 'OpenAI Codex (.codex/config.toml)', ok: existsSync(join(cwd, '.codex', 'config.toml')), path: join(cwd, '.codex', 'config.toml') }
598
+ ];
599
+ console.log(`\nVectorForce Workspace & MCP Health Check (${cwd}):\n`);
600
+ for (const c of checks) {
601
+ console.log(` ${c.ok ? 'āœ” [READY] ' : 'ā—‹ [MISSING]'} ${c.name}`);
602
+ }
603
+ console.log(`\nHow to verify inside your AI agent chat:`);
604
+ console.log(` Ask: "Check active VectorForce tasks and workspace drift"`);
605
+ console.log(` Your agent will automatically call MCP tools: list_tasks & check_workspace_drift\n`);
606
+ break;
607
+ }
608
+
609
+ if (sub === 'uninstall' || sub === 'remove') {
610
+ const target = (args[2] || 'all').toLowerCase();
611
+ const uninstalled = [];
612
+ if (target === 'antigravity' || target === 'all') {
613
+ if (HarnessConfig.uninstallAntigravity(cwd)) uninstalled.push('Antigravity');
614
+ }
615
+ if (target === 'command-code' || target === 'cmd' || target === 'all') {
616
+ if (HarnessConfig.uninstallCommandCode(cwd)) uninstalled.push('Command Code');
617
+ }
618
+ if (target === 'muse' || target === 'muse-code' || target === 'all') {
619
+ if (HarnessConfig.uninstallMuseCode(cwd)) uninstalled.push('Muse Code');
620
+ }
621
+ if (target === 'codex' || target === 'all') {
622
+ if (HarnessConfig.uninstallCodex(cwd)) uninstalled.push('Codex');
623
+ }
624
+ console.log(`\nāœ” Removed harness integration(s): ${uninstalled.length ? uninstalled.join(', ') : 'none found'}`);
625
+ break;
626
+ }
627
+
628
+ if (sub === 'set') {
629
+ const key = args[2];
630
+ const val = args[3];
631
+ if (!key || val === undefined) {
632
+ console.error('Usage: node bin/vf.js config set <KEY> <VALUE>');
633
+ process.exit(1);
634
+ }
635
+ const vfDir = join(cwd, '.vectorforce');
636
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
637
+ const cfgPath = join(vfDir, 'config.json');
638
+ let existing = {};
639
+ if (existsSync(cfgPath)) {
640
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
641
+ }
642
+ existing[key] = val;
643
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
644
+ console.log(`\nāœ” Saved configuration [${key}] in ${cfgPath}`);
645
+ break;
646
+ }
647
+
648
+ if (sub === 'openrouter') {
649
+ const key = args[2];
650
+ const model = args[3] || 'deepseek/deepseek-v4.1-flash-1017';
651
+ if (!key) {
652
+ console.error('Usage: node bin/vf.js config openrouter <API_KEY> [MODEL]');
653
+ process.exit(1);
654
+ }
655
+ const vfDir = join(cwd, '.vectorforce');
656
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
657
+ const cfgPath = join(vfDir, 'config.json');
658
+ let existing = {};
659
+ if (existsSync(cfgPath)) {
660
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
661
+ }
662
+ existing.VF_LLM_PROVIDER = 'openrouter';
663
+ existing.OPENROUTER_API_KEY = key;
664
+ existing.VF_OPENROUTER_MODEL = model;
665
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
666
+ console.log(`\nāœ” Configured OpenRouter in ${cfgPath}`);
667
+ console.log(` Provider: openrouter`);
668
+ console.log(` Model: ${model}`);
669
+ console.log(` Base URL: https://openrouter.ai/api/v1`);
670
+ break;
671
+ }
672
+
673
+ if (sub === 'fireworks') {
674
+ const key = args[2];
675
+ const model = args[3] || 'accounts/fireworks/models/muse-spark-open';
676
+ if (!key) {
677
+ console.error('Usage: node bin/vf.js config fireworks <API_KEY> [MODEL]');
678
+ process.exit(1);
679
+ }
680
+ const vfDir = join(cwd, '.vectorforce');
681
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
682
+ const cfgPath = join(vfDir, 'config.json');
683
+ let existing = {};
684
+ if (existsSync(cfgPath)) {
685
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
686
+ }
687
+ existing.VF_LLM_PROVIDER = 'fireworks';
688
+ existing.FIREWORKS_API_KEY = key;
689
+ existing.VF_FIREWORKS_MODEL = model;
690
+ existing.VF_FIREWORKS_BASE_URL = 'https://api.fireworks.ai/inference/v1';
691
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
692
+ console.log(`\nāœ” Configured Fireworks AI (Meta Muse Spark Open) in ${cfgPath}`);
693
+ console.log(` Provider: fireworks`);
694
+ console.log(` Model: ${model}`);
695
+ console.log(` Base URL: https://api.fireworks.ai/inference/v1`);
696
+ break;
697
+ }
698
+
699
+ if (sub === 'muse') {
700
+ const key = args[2];
701
+ const model = args[3] || 'muse-spark-1.3';
702
+ if (!key) {
703
+ console.error('Usage: node bin/vf.js config muse <API_KEY> [MODEL]');
704
+ process.exit(1);
705
+ }
706
+ const vfDir = join(cwd, '.vectorforce');
707
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
708
+ const cfgPath = join(vfDir, 'config.json');
709
+ let existing = {};
710
+ if (existsSync(cfgPath)) {
711
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
712
+ }
713
+ existing.VF_LLM_PROVIDER = 'muse';
714
+ existing.MODEL_API_KEY = key;
715
+ existing.VF_MUSE_MODEL = model;
716
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
717
+ console.log(`\nāœ” Configured Muse in ${cfgPath}`);
718
+ console.log(` Provider: muse`);
719
+ console.log(` Model: ${model}`);
720
+ console.log(` Base URL: https://api.meta.ai/v1`);
721
+ break;
722
+ }
723
+
724
+ if (sub === 'ollama') {
725
+ const model = args[2] || 'llama3.1:8b';
726
+ const baseUrl = args[3] || 'http://127.0.0.1:11434';
727
+ const vfDir = join(cwd, '.vectorforce');
728
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
729
+ const cfgPath = join(vfDir, 'config.json');
730
+ let existing = {};
731
+ if (existsSync(cfgPath)) {
732
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
733
+ }
734
+ existing.VF_LLM_PROVIDER = 'ollama';
735
+ existing.VF_OLLAMA_MODEL = model;
736
+ existing.VF_OLLAMA_BASE_URL = baseUrl;
737
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
738
+ console.log(`\nāœ” Configured Local Ollama in ${cfgPath}`);
739
+ console.log(` Provider: ollama (100% Local & Private)`);
740
+ console.log(` Model: ${model}`);
741
+ console.log(` Base URL: ${baseUrl}`);
742
+ break;
743
+ }
744
+
745
+ if (sub === 'openai' || sub === 'codex') {
746
+ const key = args[2];
747
+ const model = args[3] || 'o3-mini';
748
+ const baseUrl = args[4] || 'https://api.openai.com/v1';
749
+ if (!key) {
750
+ console.error('Usage: node bin/vf.js config openai <API_KEY> [MODEL] [BASE_URL]');
751
+ process.exit(1);
752
+ }
753
+ const vfDir = join(cwd, '.vectorforce');
754
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
755
+ const cfgPath = join(vfDir, 'config.json');
756
+ let existing = {};
757
+ if (existsSync(cfgPath)) {
758
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
759
+ }
760
+ existing.VF_LLM_PROVIDER = 'openai';
761
+ existing.VF_LLM_API_KEY = key;
762
+ existing.VF_LLM_MODEL = model;
763
+ existing.VF_LLM_BASE_URL = baseUrl;
764
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
765
+ console.log(`\nāœ” Configured OpenAI in ${cfgPath}`);
766
+ console.log(` Provider: openai`);
767
+ console.log(` Model: ${model}`);
768
+ console.log(` Base URL: ${baseUrl}`);
769
+ break;
770
+ }
771
+
772
+ if (sub === 'anthropic') {
773
+ const key = args[2];
774
+ const model = args[3] || 'claude-sonnet-4-20250514';
775
+ const baseUrl = args[4] || 'https://api.anthropic.com/v1';
776
+ if (!key) {
777
+ console.error('Usage: node bin/vf.js config anthropic <API_KEY> [MODEL] [BASE_URL]');
778
+ process.exit(1);
779
+ }
780
+ const vfDir = join(cwd, '.vectorforce');
781
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
782
+ const cfgPath = join(vfDir, 'config.json');
783
+ let existing = {};
784
+ if (existsSync(cfgPath)) {
785
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
786
+ }
787
+ existing.VF_LLM_PROVIDER = 'anthropic';
788
+ existing.VF_LLM_API_KEY = key;
789
+ existing.VF_LLM_MODEL = model;
790
+ existing.VF_LLM_BASE_URL = baseUrl;
791
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
792
+ console.log(`\nāœ” Configured Anthropic in ${cfgPath}`);
793
+ console.log(` Provider: anthropic`);
794
+ console.log(` Model: ${model}`);
795
+ console.log(` Base URL: ${baseUrl}`);
796
+ break;
797
+ }
798
+
799
+ if (sub === 'byok') {
800
+ const baseUrl = args[2];
801
+ const key = args[3];
802
+ const model = args[4] || 'llama-3.3-70b-versatile';
803
+ if (!baseUrl || !key) {
804
+ console.error('Usage: node bin/vf.js config byok <BASE_URL> <API_KEY> [MODEL]');
805
+ process.exit(1);
806
+ }
807
+ const vfDir = join(cwd, '.vectorforce');
808
+ mkdirSync(vfDir, { recursive: true, mode: 0o700 });
809
+ const cfgPath = join(vfDir, 'config.json');
810
+ let existing = {};
811
+ if (existsSync(cfgPath)) {
812
+ try { existing = JSON.parse(readFileSync(cfgPath, 'utf8')); } catch {}
813
+ }
814
+ existing.VF_LLM_PROVIDER = 'byok';
815
+ existing.VF_LLM_BASE_URL = baseUrl;
816
+ existing.VF_LLM_API_KEY = key;
817
+ existing.VF_LLM_MODEL = model;
818
+ writeFileSync(cfgPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
819
+ console.log(`\nāœ” Configured Custom BYOK Endpoint in ${cfgPath}`);
820
+ console.log(` Provider: byok`);
821
+ console.log(` Model: ${model}`);
822
+ console.log(` Base URL: ${baseUrl}`);
823
+ break;
824
+ }
825
+
826
+ if (sub === 'show' || sub === 'status') {
827
+ const cfg = resolveLlmConfig(process.env, {}, cwd);
828
+ console.log('\nVectorForce AI Reasoning Configuration:');
829
+ console.log(` Provider: ${cfg.provider}`);
830
+ console.log(` Model: ${cfg.model}`);
831
+ console.log(` Base URL: ${cfg.baseUrl}`);
832
+ console.log(` API Key: ${cfg.apiKey ? 'configured (hidden)' : 'none (falling back to local)'}`);
833
+ break;
834
+ }
835
+
836
+ const binPath = resolve(process.argv[1]);
837
+ console.log(`
838
+ VectorForce Harness Configuration:
839
+
840
+ 1. Google Antigravity Configuration:
841
+ File: .agents/mcp_config.json (or ~/.gemini/config/mcp_config.json)
842
+ Content:
843
+ {
844
+ "mcpServers": {
845
+ "vectorforce": {
846
+ "command": "node",
847
+ "args": ["${binPath}", "mcp"]
848
+ }
849
+ }
850
+ }
851
+
852
+ 2. Meta Muse Code (muse-code) Configuration:
853
+ File: .muse/mcp_config.json (or musecode.json)
854
+ Content:
855
+ {
856
+ "mcpServers": {
857
+ "vectorforce": {
858
+ "command": "node",
859
+ "args": ["${binPath}", "mcp"],
860
+ "env": { "VF_HARNESS": "muse-code" }
861
+ }
862
+ }
863
+ }
864
+
865
+ 3. Command Code (command-code) Configuration:
866
+ Run command:
867
+ cmd mcp add --transport stdio vectorforce -- node ${binPath} mcp
868
+
869
+ 4. OpenAI Codex (codex) Configuration:
870
+ File: .codex/config.toml (or codex.json)
871
+ Content:
872
+ [mcp_servers.vectorforce]
873
+ command = "node"
874
+ args = ["${binPath}", "mcp"]
875
+ [mcp_servers.vectorforce.env]
876
+ VF_HARNESS = "codex"
877
+
878
+ Or run command:
879
+ codex mcp add vectorforce -- node ${binPath} mcp
880
+
881
+ Automated Setup:
882
+ node bin/vf.js config install codex
883
+ node bin/vf.js config install all
884
+
885
+ 5. AI Reasoning (vf reason / reason_about_task):
886
+ Local Ollama (default, 100% private):
887
+ VF_LLM_PROVIDER=ollama
888
+ VF_OLLAMA_BASE_URL=http://127.0.0.1:11434
889
+ VF_OLLAMA_MODEL=llama3.1:8b
890
+
891
+ Cloud Meta Muse (Responses API):
892
+ VF_LLM_PROVIDER=muse
893
+ MODEL_API_KEY=<your-key>
894
+ VF_MUSE_MODEL=muse-spark-1.3 # default
895
+ VF_MUSE_BASE_URL=https://api.meta.ai/v1 # default
896
+ # Or: node bin/vf.js config muse <API_KEY> [MODEL]
897
+
898
+ Cloud OpenAI Codex / GPT:
899
+ VF_LLM_PROVIDER=codex
900
+ VF_CODEX_BASE_URL=https://api.openai.com/v1
901
+ VF_CODEX_API_KEY=<your-key>
902
+ VF_CODEX_MODEL=o3-mini
903
+
904
+ Cloud OpenRouter (DeepSeek v4.1 Flash / Multi-Model):
905
+ VF_LLM_PROVIDER=openrouter
906
+ OPENROUTER_API_KEY=<your-key> # or VF_OPENROUTER_API_KEY
907
+ VF_OPENROUTER_MODEL=deepseek/deepseek-v4.1-flash-1017 # default
908
+
909
+ Privacy: set VF_LLM_ALLOW_CODE=false (or run with --no-code) to keep source out of the prompt.
910
+ `);
911
+ break;
912
+ }
913
+
914
+ case 'remote': {
915
+ const sub = args[1] || 'show';
916
+ if (sub === 'set' || sub === 'add') {
917
+ const urlArg = args[2] && !args[2].startsWith('--') ? args[2] : flags.url;
918
+ if (!urlArg) {
919
+ console.error('Usage: vf remote set <CLOUD_URL> [--key <SUBSCRIPTION_KEY>] [--project <PROJECT_ID>]');
920
+ process.exit(1);
921
+ }
922
+ const saved = saveRemoteConfig(cwd, {
923
+ remoteUrl: urlArg.replace(/\/+$/, ''),
924
+ ...(flags.key ? { subscriptionKey: flags.key } : {}),
925
+ ...(flags.project ? { remoteProjectId: flags.project } : {})
926
+ });
927
+ console.log(`āœ” Remote VectorForce Cloud configured in .vectorforce/config.json:`);
928
+ console.log(` Remote URL: ${saved.remoteUrl}`);
929
+ console.log(` Project ID: ${saved.remoteProjectId || 'proj-bistro-pos'}`);
930
+ break;
931
+ }
932
+ const remote = getRemoteConfig(cwd);
933
+ console.log(`\nVectorForce Cloud Remote (${cwd}):`);
934
+ console.log(` Remote URL: ${remote.remoteUrl}`);
935
+ console.log(` Remote Project: ${remote.remoteProjectId}`);
936
+ console.log(` Subscription Key: ${remote.subscriptionKey.slice(0, 12)}...`);
937
+ break;
938
+ }
939
+
940
+ case 'push': {
941
+ const remote = getRemoteConfig(cwd);
942
+ const targetUrl = (flags.url || remote.remoteUrl).replace(/\/+$/, '');
943
+ const remoteProjectId = flags.project || remote.remoteProjectId || 'proj-bistro-pos';
944
+ const project = projectService.getProjectForWorkspace(cwd);
945
+ const tasks = store.listTasks(project.project_id);
946
+ const machineId = `${hostname()}-local`;
947
+
948
+ console.log(`šŸ”„ Pushing local context (${project.name}) -> ${targetUrl} ...`);
949
+
950
+ for (const t of tasks) {
951
+ if (flags.task && t.task_id !== flags.task) continue;
952
+ await fetch(`${targetUrl}/api/cloud/tasks`, {
953
+ method: 'POST',
954
+ headers: {
955
+ 'Content-Type': 'application/json',
956
+ Authorization: `Bearer ${remote.subscriptionKey}`
957
+ },
958
+ body: JSON.stringify({
959
+ projectId: remoteProjectId,
960
+ taskId: t.task_id,
961
+ title: t.title || t.task_id,
962
+ assignee: `${process.env.USER || 'developer'}@local-mac`,
963
+ role: `Local Workspace (${basename(cwd)})`,
964
+ priority: 'P0',
965
+ machineId,
966
+ harness: flags.harness || 'antigravity'
967
+ })
968
+ });
969
+ }
970
+
971
+ const checkpoints = store.getCheckpoints(project.project_id, { limit: 50 });
972
+ let pushedCount = 0;
973
+ let lastResp = null;
974
+
975
+ for (const cp of checkpoints) {
976
+ if (flags.task && cp.task_id !== flags.task) continue;
977
+ const res = await fetch(`${targetUrl}/api/cloud/sync/push`, {
978
+ method: 'POST',
979
+ headers: {
980
+ 'Content-Type': 'application/json',
981
+ Authorization: `Bearer ${remote.subscriptionKey}`
982
+ },
983
+ body: JSON.stringify({
984
+ projectId: remoteProjectId,
985
+ taskId: cp.task_id,
986
+ senderMachineId: machineId,
987
+ recipientMachineId: 'computer-2-meta-muse-cloud',
988
+ authorName: `${cp.author?.name || process.env.USER || 'developer'} (${basename(cwd)})`,
989
+ authorHarness: cp.author?.harness || 'antigravity',
990
+ summary: cp.summary,
991
+ goals: cp.claims?.goals || [],
992
+ constraints: cp.claims?.constraints || [],
993
+ decisions: cp.claims?.decisions || [],
994
+ nextSteps: cp.claims?.next_steps || []
995
+ })
996
+ });
997
+ lastResp = await res.json();
998
+ pushedCount++;
999
+ }
1000
+
1001
+ console.log(`āœ” Pushed ${pushedCount} checkpoint(s) [E2EE AES-256-GCM + X25519 + Ed25519] to ${targetUrl}`);
1002
+ if (lastResp?.checkpoint_id) {
1003
+ console.log(` Latest Cloud Checkpoint: ${lastResp.checkpoint_id}`);
1004
+ }
1005
+ break;
1006
+ }
1007
+
1008
+ case 'pull': {
1009
+ const remote = getRemoteConfig(cwd);
1010
+ const targetUrl = (flags.url || remote.remoteUrl).replace(/\/+$/, '');
1011
+ const remoteProjectId = flags.project || remote.remoteProjectId || 'proj-bistro-pos';
1012
+
1013
+ console.log(`šŸ”“ Pulling & Decrypting latest context from ${targetUrl} ...`);
1014
+ const res = await fetch(`${targetUrl}/api/cloud/sync/pull`, {
1015
+ method: 'POST',
1016
+ headers: {
1017
+ 'Content-Type': 'application/json',
1018
+ Authorization: `Bearer ${remote.subscriptionKey}`
1019
+ },
1020
+ body: JSON.stringify({
1021
+ projectId: remoteProjectId,
1022
+ taskId: flags.task || undefined
1023
+ })
1024
+ });
1025
+ const data = await res.json();
1026
+ if (!res.ok || data.verified === false) {
1027
+ console.error(`Error pulling from remote: ${data.error || res.statusText}`);
1028
+ process.exit(1);
1029
+ }
1030
+
1031
+ const payload = data.decrypted_on_computer_2?.payload;
1032
+ if (payload) {
1033
+ let project;
1034
+ try {
1035
+ project = projectService.getProjectForWorkspace(cwd);
1036
+ } catch {
1037
+ project = projectService.initProject(cwd, basename(cwd));
1038
+ }
1039
+ const targetTaskId = flags.task || payload.task_id || 'cloud-sync-task';
1040
+ if (!store.getTask(project.project_id, targetTaskId)) {
1041
+ store.createTask(project.project_id, targetTaskId, `Synced Task: ${targetTaskId}`);
1042
+ }
1043
+ const leaves = store.getLeafCheckpoints(project.project_id, targetTaskId);
1044
+ const parentId = leaves.length > 0 ? leaves[0].checkpoint_id : undefined;
1045
+
1046
+ const receipt = checkpointService.captureCheckpoint({
1047
+ workspacePath: cwd,
1048
+ taskId: targetTaskId,
1049
+ parentId,
1050
+ authorName: payload.author?.name || 'cloud-sync',
1051
+ authorHarness: payload.author?.harness || 'muse-code',
1052
+ summary: `[Pulled from Cloud] ${payload.summary}`,
1053
+ goals: payload.claims?.goals?.length ? payload.claims.goals : ['Continue cloud-synced task'],
1054
+ constraints: payload.claims?.constraints || [],
1055
+ decisions: payload.claims?.decisions || [],
1056
+ nextSteps: payload.claims?.next_steps?.length ? payload.claims.next_steps : ['Inspect pulled context'],
1057
+ requestedDocuments: ['AGENTS.md'],
1058
+ includeGit: true
1059
+ });
1060
+ console.log(`āœ” Verified Ed25519 signature, decrypted (X25519 + AES-256-GCM), and reconciled into local SQLite!`);
1061
+ console.log(` Local Checkpoint Committed: ${receipt.reference_id} [${receipt.status}]`);
1062
+ }
1063
+
1064
+ if (data.muse_reasoning) {
1065
+ console.log(`\n🧠 Cloud AI Briefing (${data.muse_reasoning.model}):`);
1066
+ console.log(` Summary: ${data.muse_reasoning.where_computer_1_left_off}`);
1067
+ for (const step of data.muse_reasoning.recommended_next_actions_for_computer_2 || []) {
1068
+ console.log(` Next -> ${step}`);
1069
+ }
1070
+ }
1071
+ break;
1072
+ }
1073
+
1074
+ case 'recap':
1075
+ case 'timeline': {
1076
+ const flags = parseFlags(args.slice(1));
1077
+ const days = flags.days ? parseInt(flags.days, 10) : 7;
1078
+ const recap = briefingService.generateRecap({
1079
+ workspacePath: cwd,
1080
+ days,
1081
+ taskId: flags.task,
1082
+ destination: flags.dest || 'cli'
1083
+ });
1084
+ console.log(recap.recapText);
1085
+ break;
1086
+ }
1087
+
1088
+ case 'doctor':
1089
+ case 'status': {
1090
+ const checks = [
1091
+ { name: 'SQLite Database (.vectorforce/vectorforce.db)', ok: existsSync(getDbPath(cwd)) },
1092
+ { name: 'Agent Protocol Rules (AGENTS.md)', ok: existsSync(join(cwd, 'AGENTS.md')) },
1093
+ { name: 'Google Antigravity (.agents/mcp_config.json)', ok: existsSync(join(cwd, '.agents', 'mcp_config.json')) },
1094
+ { name: 'Claude Code & Command Code (.mcp.json)', ok: existsSync(join(cwd, '.mcp.json')) },
1095
+ { name: 'Meta Muse (.muse/mcp_config.json or musecode.json)', ok: existsSync(join(cwd, '.muse', 'mcp_config.json')) || existsSync(join(cwd, 'musecode.json')) },
1096
+ { name: 'OpenAI Codex (.codex/config.toml)', ok: existsSync(join(cwd, '.codex', 'config.toml')) }
1097
+ ];
1098
+ console.log(`\nVectorForce Workspace & MCP Status (${cwd}):\n`);
1099
+ for (const c of checks) {
1100
+ console.log(` ${c.ok ? 'āœ” [READY] ' : 'ā—‹ [MISSING]'} ${c.name}`);
1101
+ }
1102
+ console.log(`\nšŸ’” How it works:`);
1103
+ console.log(` 1. You do NOT need to run "vf mcp" manually.`);
1104
+ console.log(` 2. Your AI agent (Antigravity, Claude Code, Command Code, Muse, Codex) reads its config file above and automatically spawns "vf mcp" over stdio.`);
1105
+ console.log(` 3. To verify in your AI chat, ask: "List active VectorForce tasks and check workspace drift"\n`);
1106
+ break;
1107
+ }
1108
+
1109
+ case 'studio':
1110
+ case 'ui': {
1111
+ const flags = parseFlags(args.slice(1));
1112
+ const port = flags.port || 3210;
1113
+ const studio = createStudioServer({
1114
+ dbPath: getDbPath(cwd),
1115
+ workspacePath: cwd,
1116
+ port
1117
+ });
1118
+ const url = await studio.start();
1119
+ console.log(`\nšŸš€ VectorForce Studio is running at: ${url}`);
1120
+ console.log(` Connected to SQLite: ${getDbPath(cwd)}`);
1121
+ console.log(` Interactive Topology & AI Memory: ${url}`);
1122
+ console.log(` Press Ctrl+C to stop.\n`);
1123
+
1124
+ try {
1125
+ const { exec } = await import('node:child_process');
1126
+ exec(`open ${url}`);
1127
+ } catch {}
1128
+
1129
+ process.on('SIGINT', async () => {
1130
+ await studio.stop();
1131
+ process.exit(0);
1132
+ });
1133
+ process.on('SIGTERM', async () => {
1134
+ await studio.stop();
1135
+ process.exit(0);
1136
+ });
1137
+ return;
1138
+ }
1139
+
1140
+ default:
1141
+ console.error(`Unknown command: ${command}`);
1142
+ printUsage();
1143
+ process.exit(1);
1144
+ }
1145
+ } catch (err) {
1146
+ if (err instanceof VectorForceError) {
1147
+ console.error(`VectorForce Error [${err.code}]: ${err.message}`);
1148
+ } else {
1149
+ console.error(`Error: ${err.message}`);
1150
+ }
1151
+ process.exit(1);
1152
+ } finally {
1153
+ if (command !== 'mcp' && command !== 'studio' && command !== 'ui') {
1154
+ store.close();
1155
+ }
1156
+ }
1157
+ }
1158
+
1159
+ main();