voyageai-cli 1.30.0 → 1.30.1

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 (55) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +6 -0
  3. package/src/commands/chat.js +32 -11
  4. package/src/commands/export.js +124 -0
  5. package/src/commands/import.js +195 -0
  6. package/src/commands/index-workspace.js +239 -0
  7. package/src/commands/mcp-server.js +113 -3
  8. package/src/commands/playground.js +111 -3
  9. package/src/lib/export/contexts/benchmark-export.js +27 -0
  10. package/src/lib/export/contexts/chat-export.js +41 -0
  11. package/src/lib/export/contexts/explore-export.js +22 -0
  12. package/src/lib/export/contexts/search-export.js +54 -0
  13. package/src/lib/export/contexts/workflow-export.js +80 -0
  14. package/src/lib/export/formats/clipboard-export.js +29 -0
  15. package/src/lib/export/formats/csv-export.js +45 -0
  16. package/src/lib/export/formats/json-export.js +50 -0
  17. package/src/lib/export/formats/markdown-export.js +189 -0
  18. package/src/lib/export/formats/mermaid-export.js +274 -0
  19. package/src/lib/export/formats/pdf-export.js +117 -0
  20. package/src/lib/export/formats/png-export.js +96 -0
  21. package/src/lib/export/formats/svg-export.js +116 -0
  22. package/src/lib/export/index.js +175 -0
  23. package/src/lib/workflow.js +206 -27
  24. package/src/mcp/install.js +280 -7
  25. package/src/mcp/schemas/index.js +40 -0
  26. package/src/mcp/server.js +2 -0
  27. package/src/mcp/tools/workspace.js +463 -0
  28. package/src/playground/announcements.md +52 -5
  29. package/src/playground/index.html +11125 -7796
  30. package/src/playground/vendor/mermaid.min.js +2811 -0
  31. package/src/workflows/rag-chat.json +165 -0
  32. package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
  33. package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
  34. package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
  35. package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
  36. package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
  37. package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
  38. package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
  39. package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
  40. package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
  41. package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
  42. package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
  43. package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
  44. package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
  45. package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
  46. package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
  47. package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
  48. package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
  49. package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
  50. package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
  51. package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
  52. package/src/playground/assets/announcements/appstore.jpg +0 -0
  53. package/src/playground/assets/announcements/circuits.jpg +0 -0
  54. package/src/playground/assets/announcements/csvingest.jpg +0 -0
  55. package/src/playground/assets/announcements/green-wave.jpg +0 -0
@@ -19,11 +19,21 @@ const TARGETS = {
19
19
  'claude-code': {
20
20
  name: 'Claude Code',
21
21
  configPath: () => path.join(os.homedir(), '.claude', 'settings.json'),
22
- configKey: 'mcpServers', // same key but different file
22
+ configKey: 'mcpServers',
23
23
  },
24
24
  cursor: {
25
25
  name: 'Cursor',
26
26
  configPath: () => path.join(os.homedir(), '.cursor', 'mcp.json'),
27
+ // Cursor also supports workspace-level config
28
+ workspaceConfigPath: () => '.cursor/mcp.json',
29
+ postInstall: () => {
30
+ return [
31
+ 'Cursor MCP integration tips:',
32
+ ' • Restart Cursor to load the vai MCP server',
33
+ ' • Use @vai in Cursor Chat to invoke vai tools',
34
+ ' • Run "vai mcp status" to verify installation',
35
+ ];
36
+ },
27
37
  },
28
38
  windsurf: {
29
39
  name: 'Windsurf',
@@ -37,6 +47,39 @@ const TARGETS = {
37
47
  return path.join(os.homedir(), '.config', 'Code', 'User', 'settings.json');
38
48
  },
39
49
  configKey: 'mcp.servers',
50
+ // VS Code needs MCP extension or GitHub Copilot with MCP support
51
+ postInstall: () => {
52
+ return [
53
+ 'VS Code MCP integration tips:',
54
+ ' • Install the vai VS Code extension for native integration',
55
+ ' • Or use GitHub Copilot Chat with MCP support (requires Copilot subscription)',
56
+ ' • Or use the vai-vscode extension from the vscode-extension/ folder',
57
+ ' • Run "vai mcp-server --transport http" for HTTP-based integrations',
58
+ ];
59
+ },
60
+ },
61
+ 'vscode-insiders': {
62
+ name: 'VS Code Insiders',
63
+ configPath: () => {
64
+ if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'Code - Insiders', 'User', 'settings.json');
65
+ if (process.platform === 'win32') return path.join(process.env.APPDATA || '', 'Code - Insiders', 'User', 'settings.json');
66
+ return path.join(os.homedir(), '.config', 'Code - Insiders', 'User', 'settings.json');
67
+ },
68
+ configKey: 'mcp.servers',
69
+ postInstall: () => {
70
+ return [
71
+ 'VS Code Insiders MCP integration tips:',
72
+ ' • Insiders often has newer MCP features',
73
+ ' • Install the vai VS Code extension for native integration',
74
+ ' • Or use GitHub Copilot Chat with MCP support',
75
+ ];
76
+ },
77
+ },
78
+ 'cursor-workspace': {
79
+ name: 'Cursor (Workspace)',
80
+ configPath: () => null, // Requires workspace path
81
+ workspaceConfigPath: () => '.cursor/mcp.json',
82
+ requiresWorkspace: true,
40
83
  },
41
84
  };
42
85
 
@@ -48,14 +91,57 @@ function buildVaiEntry(opts = {}) {
48
91
  command: 'vai',
49
92
  args: ['mcp-server'],
50
93
  };
94
+
95
+ // Transport configuration
51
96
  if (opts.transport === 'http') {
52
97
  entry.args.push('--transport', 'http');
53
98
  if (opts.port) entry.args.push('--port', String(opts.port));
99
+ if (opts.sse === false) entry.args.push('--no-sse');
54
100
  }
101
+
102
+ // Environment variables
103
+ const env = {};
55
104
  const apiKey = opts.apiKey || process.env.VOYAGE_API_KEY || '';
56
- if (apiKey) {
57
- entry.env = { VOYAGE_API_KEY: apiKey };
105
+ const mongoUri = opts.mongodbUri || process.env.MONGODB_URI || '';
106
+
107
+ if (apiKey) env.VOYAGE_API_KEY = apiKey;
108
+ if (mongoUri) env.MONGODB_URI = mongoUri;
109
+ if (opts.db) env.VAI_DEFAULT_DB = opts.db;
110
+ if (opts.collection) env.VAI_DEFAULT_COLLECTION = opts.collection;
111
+ if (opts.verbose) env.VAI_MCP_VERBOSE = '1';
112
+
113
+ if (Object.keys(env).length > 0) {
114
+ entry.env = env;
58
115
  }
116
+
117
+ return entry;
118
+ }
119
+
120
+ /**
121
+ * Build an extended vai MCP entry with tool descriptions for Cursor/VS Code.
122
+ * This helps the AI understand what tools are available.
123
+ */
124
+ function buildVaiEntryWithMetadata(opts = {}) {
125
+ const entry = buildVaiEntry(opts);
126
+
127
+ // Add metadata for better AI tool discovery
128
+ entry.metadata = {
129
+ name: 'vai',
130
+ description: 'Voyage AI semantic search and RAG tools for MongoDB Atlas Vector Search',
131
+ tools: [
132
+ { name: 'vai_query', description: 'Full RAG query with reranking' },
133
+ { name: 'vai_search', description: 'Raw vector similarity search' },
134
+ { name: 'vai_rerank', description: 'Rerank documents by relevance' },
135
+ { name: 'vai_embed', description: 'Generate embeddings for text' },
136
+ { name: 'vai_similarity', description: 'Compare text similarity' },
137
+ { name: 'vai_ingest', description: 'Chunk and store documents' },
138
+ { name: 'vai_collections', description: 'List MongoDB collections' },
139
+ { name: 'vai_models', description: 'List available Voyage AI models' },
140
+ { name: 'vai_explain', description: 'Explain embedding concepts' },
141
+ { name: 'vai_estimate', description: 'Estimate embedding costs' },
142
+ ],
143
+ };
144
+
59
145
  return entry;
60
146
  }
61
147
 
@@ -110,7 +196,7 @@ function setNestedKey(obj, keyPath, value) {
110
196
 
111
197
  /**
112
198
  * Install vai MCP entry into a target tool's config.
113
- * Returns { installed: boolean, message: string }
199
+ * Returns { installed: boolean, message: string, tips?: string[] }
114
200
  */
115
201
  function installTarget(targetKey, opts = {}) {
116
202
  const target = TARGETS[targetKey];
@@ -118,7 +204,19 @@ function installTarget(targetKey, opts = {}) {
118
204
  return { installed: false, message: `Unknown target: ${targetKey}. Available: ${Object.keys(TARGETS).join(', ')}` };
119
205
  }
120
206
 
207
+ // Handle workspace-level installs
208
+ if (target.requiresWorkspace) {
209
+ if (!opts.workspacePath) {
210
+ return { installed: false, message: `${target.name}: requires --workspace-path option` };
211
+ }
212
+ return installWorkspaceConfig(target, opts);
213
+ }
214
+
121
215
  const configPath = target.configPath();
216
+ if (!configPath) {
217
+ return { installed: false, message: `${target.name}: config path not available` };
218
+ }
219
+
122
220
  const mcpKey = target.configKey || 'mcpServers';
123
221
  const config = readConfig(configPath) || {};
124
222
 
@@ -134,12 +232,63 @@ function installTarget(targetKey, opts = {}) {
134
232
  }
135
233
 
136
234
  const existed = !!servers.vai;
137
- servers.vai = buildVaiEntry(opts);
235
+
236
+ // Use extended entry with metadata for Cursor/VS Code
237
+ if (targetKey === 'cursor' || targetKey.startsWith('vscode')) {
238
+ servers.vai = buildVaiEntryWithMetadata(opts);
239
+ } else {
240
+ servers.vai = buildVaiEntry(opts);
241
+ }
242
+
138
243
  // Ensure the nested key points to our updated servers
139
244
  setNestedKey(config, mcpKey, servers);
140
245
 
141
246
  writeConfig(configPath, config);
142
- return { installed: true, message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}` };
247
+
248
+ const result = {
249
+ installed: true,
250
+ message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}`,
251
+ };
252
+
253
+ // Add post-install tips if available
254
+ if (target.postInstall) {
255
+ result.tips = target.postInstall();
256
+ }
257
+
258
+ return result;
259
+ }
260
+
261
+ /**
262
+ * Install vai MCP config at workspace level.
263
+ */
264
+ function installWorkspaceConfig(target, opts) {
265
+ const workspacePath = opts.workspacePath;
266
+ const relativeConfigPath = target.workspaceConfigPath();
267
+ const configPath = path.join(workspacePath, relativeConfigPath);
268
+
269
+ const config = readConfig(configPath) || {};
270
+ const mcpKey = target.configKey || 'mcpServers';
271
+
272
+ let servers = getNestedKey(config, mcpKey);
273
+ if (servers == null || typeof servers !== 'object') {
274
+ servers = {};
275
+ setNestedKey(config, mcpKey, servers);
276
+ }
277
+
278
+ if (servers.vai && !opts.force) {
279
+ return { installed: false, message: `${target.name}: vai already configured in ${configPath} — use --force to overwrite` };
280
+ }
281
+
282
+ const existed = !!servers.vai;
283
+ servers.vai = buildVaiEntryWithMetadata(opts);
284
+ setNestedKey(config, mcpKey, servers);
285
+
286
+ writeConfig(configPath, config);
287
+ return {
288
+ installed: true,
289
+ message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}`,
290
+ tips: ['Workspace-level config will be used when opening this folder in Cursor'],
291
+ };
143
292
  }
144
293
 
145
294
  /**
@@ -175,7 +324,17 @@ function uninstallTarget(targetKey) {
175
324
  function statusAll() {
176
325
  const results = [];
177
326
  for (const [key, target] of Object.entries(TARGETS)) {
178
- const configPath = target.configPath();
327
+ // Skip workspace-only targets in global status
328
+ if (target.requiresWorkspace) {
329
+ continue;
330
+ }
331
+
332
+ const configPath = target.configPath?.();
333
+ if (!configPath) {
334
+ results.push({ target: key, name: target.name, configPath: 'N/A', status: 'workspace-only' });
335
+ continue;
336
+ }
337
+
179
338
  const mcpKey = target.configKey || 'mcpServers';
180
339
  const config = readConfig(configPath);
181
340
 
@@ -192,10 +351,124 @@ function statusAll() {
192
351
  return results;
193
352
  }
194
353
 
354
+ /**
355
+ * Get detailed information about a target for help/docs.
356
+ */
357
+ function getTargetInfo(targetKey) {
358
+ const target = TARGETS[targetKey];
359
+ if (!target) return null;
360
+
361
+ return {
362
+ key: targetKey,
363
+ name: target.name,
364
+ configPath: target.configPath?.() || null,
365
+ workspaceConfigPath: target.workspaceConfigPath?.() || null,
366
+ requiresWorkspace: target.requiresWorkspace || false,
367
+ tips: target.postInstall?.() || [],
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Generate a sample .cursor/mcp.json or .vscode/settings.json for documentation.
373
+ */
374
+ function generateSampleConfig(targetKey, opts = {}) {
375
+ const target = TARGETS[targetKey];
376
+ if (!target) return null;
377
+
378
+ const mcpKey = target.configKey || 'mcpServers';
379
+ const entry = buildVaiEntryWithMetadata(opts);
380
+
381
+ const config = {};
382
+ setNestedKey(config, mcpKey, { vai: entry });
383
+
384
+ return JSON.stringify(config, null, 2);
385
+ }
386
+
387
+ /**
388
+ * Verify that vai is accessible in PATH.
389
+ */
390
+ function verifyVaiInstallation() {
391
+ const { execSync } = require('child_process');
392
+ try {
393
+ const version = execSync('vai --version', { encoding: 'utf8', timeout: 5000 }).trim();
394
+ return { installed: true, version };
395
+ } catch {
396
+ return { installed: false, version: null };
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Diagnose MCP installation issues.
402
+ */
403
+ function diagnose(targetKey) {
404
+ const results = [];
405
+
406
+ // Check vai installation
407
+ const vaiStatus = verifyVaiInstallation();
408
+ if (!vaiStatus.installed) {
409
+ results.push({ level: 'error', message: 'vai CLI not found in PATH. Run: npm install -g voyageai-cli' });
410
+ } else {
411
+ results.push({ level: 'ok', message: `vai ${vaiStatus.version} installed` });
412
+ }
413
+
414
+ // Check target config
415
+ const target = TARGETS[targetKey];
416
+ if (!target) {
417
+ results.push({ level: 'error', message: `Unknown target: ${targetKey}` });
418
+ return results;
419
+ }
420
+
421
+ const configPath = target.configPath?.();
422
+ if (!configPath) {
423
+ results.push({ level: 'warning', message: `${target.name}: No global config path (may require workspace config)` });
424
+ } else if (!fs.existsSync(configPath)) {
425
+ results.push({ level: 'warning', message: `${target.name}: Config file not found at ${configPath}` });
426
+ } else {
427
+ const config = readConfig(configPath);
428
+ const mcpKey = target.configKey || 'mcpServers';
429
+ const servers = getNestedKey(config, mcpKey);
430
+
431
+ if (servers?.vai) {
432
+ results.push({ level: 'ok', message: `${target.name}: vai configured in ${configPath}` });
433
+
434
+ // Validate the entry
435
+ const entry = servers.vai;
436
+ if (entry.command !== 'vai') {
437
+ results.push({ level: 'warning', message: `${target.name}: command should be "vai", found "${entry.command}"` });
438
+ }
439
+ if (!entry.args?.includes('mcp-server')) {
440
+ results.push({ level: 'warning', message: `${target.name}: args should include "mcp-server"` });
441
+ }
442
+ } else {
443
+ results.push({ level: 'warning', message: `${target.name}: vai not configured. Run: vai mcp install ${targetKey}` });
444
+ }
445
+ }
446
+
447
+ // Check environment variables
448
+ if (process.env.VOYAGE_API_KEY) {
449
+ results.push({ level: 'ok', message: 'VOYAGE_API_KEY environment variable set' });
450
+ } else {
451
+ results.push({ level: 'warning', message: 'VOYAGE_API_KEY not set (required for embeddings)' });
452
+ }
453
+
454
+ if (process.env.MONGODB_URI) {
455
+ results.push({ level: 'ok', message: 'MONGODB_URI environment variable set' });
456
+ } else {
457
+ results.push({ level: 'warning', message: 'MONGODB_URI not set (required for vector search)' });
458
+ }
459
+
460
+ return results;
461
+ }
462
+
195
463
  module.exports = {
196
464
  TARGETS,
197
465
  installTarget,
198
466
  uninstallTarget,
199
467
  statusAll,
200
468
  buildVaiEntry,
469
+ buildVaiEntryWithMetadata,
470
+ getTargetInfo,
471
+ generateSampleConfig,
472
+ verifyVaiInstallation,
473
+ diagnose,
201
474
  };
@@ -87,6 +87,43 @@ const ingestSchema = {
87
87
  model: z.string().default('voyage-4-large').describe('Voyage AI embedding model'),
88
88
  };
89
89
 
90
+ /** vai_index_workspace input schema */
91
+ const indexWorkspaceSchema = {
92
+ path: z.string().optional().describe('Workspace directory path. Defaults to current working directory.'),
93
+ db: z.string().optional().describe('MongoDB database name'),
94
+ collection: z.string().optional().describe('Collection to store indexed documents'),
95
+ contentType: z.enum(['code', 'docs', 'config', 'all']).default('code')
96
+ .describe('Type of content to index: code (source files), docs (markdown/text), config (json/yaml), or all'),
97
+ model: z.string().default('voyage-4-large').describe('Voyage AI embedding model'),
98
+ maxFiles: z.number().int().min(1).max(10000).default(1000).describe('Maximum number of files to index'),
99
+ maxFileSize: z.number().int().min(1000).max(1000000).default(100000).describe('Maximum file size in bytes'),
100
+ chunkSize: z.number().int().min(100).max(4000).default(512).describe('Target chunk size in characters'),
101
+ chunkOverlap: z.number().int().min(0).max(500).default(50).describe('Overlap between chunks in characters'),
102
+ batchSize: z.number().int().min(1).max(50).default(10).describe('Number of files to process per batch'),
103
+ };
104
+
105
+ /** vai_search_code input schema */
106
+ const searchCodeSchema = {
107
+ query: z.string().min(1).max(5000).describe('Semantic search query for code'),
108
+ db: z.string().optional().describe('MongoDB database name'),
109
+ collection: z.string().optional().describe('Collection with indexed code'),
110
+ limit: z.number().int().min(1).max(50).default(10).describe('Maximum number of results'),
111
+ language: z.string().optional().describe('Filter by programming language (e.g., "js", "py", "go")'),
112
+ category: z.enum(['code', 'docs', 'config']).optional().describe('Filter by content category'),
113
+ model: z.string().optional().describe('Voyage AI embedding model'),
114
+ filter: z.record(z.string(), z.unknown()).optional().describe('Additional MongoDB filter'),
115
+ };
116
+
117
+ /** vai_explain_code input schema */
118
+ const explainCodeSchema = {
119
+ code: z.string().min(1).max(10000).describe('Code snippet to explain'),
120
+ language: z.string().optional().describe('Programming language of the code'),
121
+ db: z.string().optional().describe('MongoDB database name'),
122
+ collection: z.string().optional().describe('Collection with indexed documentation'),
123
+ contextLimit: z.number().int().min(1).max(20).default(5).describe('Number of context documents to retrieve'),
124
+ model: z.string().optional().describe('Voyage AI embedding model'),
125
+ };
126
+
90
127
  module.exports = {
91
128
  querySchema,
92
129
  searchSchema,
@@ -99,4 +136,7 @@ module.exports = {
99
136
  explainSchema,
100
137
  estimateSchema,
101
138
  ingestSchema,
139
+ indexWorkspaceSchema,
140
+ searchCodeSchema,
141
+ explainCodeSchema,
102
142
  };
package/src/mcp/server.js CHANGED
@@ -8,6 +8,7 @@ const { registerEmbeddingTools } = require('./tools/embedding');
8
8
  const { registerManagementTools } = require('./tools/management');
9
9
  const { registerUtilityTools } = require('./tools/utility');
10
10
  const { registerIngestTool } = require('./tools/ingest');
11
+ const { registerWorkspaceTools } = require('./tools/workspace');
11
12
 
12
13
  const VERSION = require('../../package.json').version;
13
14
 
@@ -27,6 +28,7 @@ function createServer() {
27
28
  registerManagementTools(server, schemas);
28
29
  registerUtilityTools(server, schemas);
29
30
  registerIngestTool(server, schemas);
31
+ registerWorkspaceTools(server, schemas);
30
32
 
31
33
  return server;
32
34
  }