voyageai-cli 1.23.1 → 1.26.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/README.md +64 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/about.js +1 -1
- package/src/commands/bug.js +1 -1
- package/src/commands/mcp-server.js +74 -0
- package/src/commands/playground.js +31 -0
- package/src/commands/scaffold.js +23 -1
- package/src/commands/workflow.js +336 -0
- package/src/lib/explanations.js +53 -0
- package/src/lib/scaffold-structure.js +8 -9
- package/src/lib/telemetry.js +1 -1
- package/src/lib/template-engine.js +240 -0
- package/src/lib/templates/nextjs/README.md.tpl +78 -55
- package/src/lib/templates/nextjs/favicon.svg.tpl +11 -0
- package/src/lib/templates/nextjs/footer.jsx.tpl +49 -0
- package/src/lib/templates/nextjs/layout.jsx.tpl +16 -10
- package/src/lib/templates/nextjs/lib-mongo.js.tpl +5 -5
- package/src/lib/templates/nextjs/lib-voyage.js.tpl +13 -8
- package/src/lib/templates/nextjs/navbar.jsx.tpl +98 -0
- package/src/lib/templates/nextjs/page-home.jsx.tpl +201 -0
- package/src/lib/templates/nextjs/page-search.jsx.tpl +184 -82
- package/src/lib/templates/nextjs/theme-registry.jsx.tpl +51 -0
- package/src/lib/templates/nextjs/theme.js.tpl +138 -65
- package/src/lib/templates/nextjs/vai-logo-256.png +0 -0
- package/src/lib/workflow-utils.js +65 -0
- package/src/lib/workflow.js +1259 -0
- package/src/mcp/install.js +201 -0
- package/src/mcp/tools/management.js +1 -60
- package/src/playground/icons/dark/128.png +0 -0
- package/src/playground/icons/dark/16.png +0 -0
- package/src/playground/icons/dark/256.png +0 -0
- package/src/playground/icons/dark/32.png +0 -0
- package/src/playground/icons/dark/64.png +0 -0
- package/src/playground/icons/light/128.png +0 -0
- package/src/playground/icons/light/16.png +0 -0
- package/src/playground/icons/light/256.png +0 -0
- package/src/playground/icons/light/32.png +0 -0
- package/src/playground/icons/light/64.png +0 -0
- package/src/playground/icons/watermark.png +0 -0
- package/src/playground/index.html +125 -73
- package/src/workflows/consistency-check.json +64 -0
- package/src/workflows/cost-analysis.json +69 -0
- package/src/workflows/multi-collection-search.json +80 -0
- package/src/workflows/research-and-summarize.json +46 -0
- package/src/workflows/smart-ingest.json +63 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Known MCP-compatible tools and their config file locations.
|
|
9
|
+
*/
|
|
10
|
+
const TARGETS = {
|
|
11
|
+
claude: {
|
|
12
|
+
name: 'Claude Desktop',
|
|
13
|
+
configPath: () => {
|
|
14
|
+
if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
15
|
+
if (process.platform === 'win32') return path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
|
|
16
|
+
return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
'claude-code': {
|
|
20
|
+
name: 'Claude Code',
|
|
21
|
+
configPath: () => path.join(os.homedir(), '.claude', 'settings.json'),
|
|
22
|
+
configKey: 'mcpServers', // same key but different file
|
|
23
|
+
},
|
|
24
|
+
cursor: {
|
|
25
|
+
name: 'Cursor',
|
|
26
|
+
configPath: () => path.join(os.homedir(), '.cursor', 'mcp.json'),
|
|
27
|
+
},
|
|
28
|
+
windsurf: {
|
|
29
|
+
name: 'Windsurf',
|
|
30
|
+
configPath: () => path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
31
|
+
},
|
|
32
|
+
vscode: {
|
|
33
|
+
name: 'VS Code',
|
|
34
|
+
configPath: () => {
|
|
35
|
+
if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json');
|
|
36
|
+
if (process.platform === 'win32') return path.join(process.env.APPDATA || '', 'Code', 'User', 'settings.json');
|
|
37
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'settings.json');
|
|
38
|
+
},
|
|
39
|
+
configKey: 'mcp.servers',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the vai MCP server entry.
|
|
45
|
+
*/
|
|
46
|
+
function buildVaiEntry(opts = {}) {
|
|
47
|
+
const entry = {
|
|
48
|
+
command: 'vai',
|
|
49
|
+
args: ['mcp-server'],
|
|
50
|
+
};
|
|
51
|
+
if (opts.transport === 'http') {
|
|
52
|
+
entry.args.push('--transport', 'http');
|
|
53
|
+
if (opts.port) entry.args.push('--port', String(opts.port));
|
|
54
|
+
}
|
|
55
|
+
const apiKey = opts.apiKey || process.env.VOYAGE_API_KEY || '';
|
|
56
|
+
if (apiKey) {
|
|
57
|
+
entry.env = { VOYAGE_API_KEY: apiKey };
|
|
58
|
+
}
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read a JSON config file, returning {} if it doesn't exist.
|
|
64
|
+
*/
|
|
65
|
+
function readConfig(filePath) {
|
|
66
|
+
if (!fs.existsSync(filePath)) return null;
|
|
67
|
+
try {
|
|
68
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw new Error(`Failed to parse ${filePath}: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Write a JSON config file, creating parent directories as needed.
|
|
77
|
+
*/
|
|
78
|
+
function writeConfig(filePath, config) {
|
|
79
|
+
const dir = path.dirname(filePath);
|
|
80
|
+
if (!fs.existsSync(dir)) {
|
|
81
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Set a nested key like "mcp.servers" on an object.
|
|
88
|
+
*/
|
|
89
|
+
function getNestedKey(obj, keyPath) {
|
|
90
|
+
const keys = keyPath.split('.');
|
|
91
|
+
let current = obj;
|
|
92
|
+
for (const k of keys) {
|
|
93
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
94
|
+
current = current[k];
|
|
95
|
+
}
|
|
96
|
+
return current;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function setNestedKey(obj, keyPath, value) {
|
|
100
|
+
const keys = keyPath.split('.');
|
|
101
|
+
let current = obj;
|
|
102
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
103
|
+
if (current[keys[i]] == null || typeof current[keys[i]] !== 'object') {
|
|
104
|
+
current[keys[i]] = {};
|
|
105
|
+
}
|
|
106
|
+
current = current[keys[i]];
|
|
107
|
+
}
|
|
108
|
+
current[keys[keys.length - 1]] = value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Install vai MCP entry into a target tool's config.
|
|
113
|
+
* Returns { installed: boolean, message: string }
|
|
114
|
+
*/
|
|
115
|
+
function installTarget(targetKey, opts = {}) {
|
|
116
|
+
const target = TARGETS[targetKey];
|
|
117
|
+
if (!target) {
|
|
118
|
+
return { installed: false, message: `Unknown target: ${targetKey}. Available: ${Object.keys(TARGETS).join(', ')}` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const configPath = target.configPath();
|
|
122
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
123
|
+
const config = readConfig(configPath) || {};
|
|
124
|
+
|
|
125
|
+
// Get or create the mcpServers object
|
|
126
|
+
let servers = getNestedKey(config, mcpKey);
|
|
127
|
+
if (servers == null || typeof servers !== 'object') {
|
|
128
|
+
servers = {};
|
|
129
|
+
setNestedKey(config, mcpKey, servers);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (servers.vai && !opts.force) {
|
|
133
|
+
return { installed: false, message: `${target.name}: vai already configured in ${configPath} — use --force to overwrite` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const existed = !!servers.vai;
|
|
137
|
+
servers.vai = buildVaiEntry(opts);
|
|
138
|
+
// Ensure the nested key points to our updated servers
|
|
139
|
+
setNestedKey(config, mcpKey, servers);
|
|
140
|
+
|
|
141
|
+
writeConfig(configPath, config);
|
|
142
|
+
return { installed: true, message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Uninstall vai MCP entry from a target tool's config.
|
|
147
|
+
*/
|
|
148
|
+
function uninstallTarget(targetKey) {
|
|
149
|
+
const target = TARGETS[targetKey];
|
|
150
|
+
if (!target) {
|
|
151
|
+
return { removed: false, message: `Unknown target: ${targetKey}` };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const configPath = target.configPath();
|
|
155
|
+
const config = readConfig(configPath);
|
|
156
|
+
if (!config) {
|
|
157
|
+
return { removed: false, message: `${target.name}: config not found at ${configPath}` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
161
|
+
const servers = getNestedKey(config, mcpKey);
|
|
162
|
+
if (!servers || !servers.vai) {
|
|
163
|
+
return { removed: false, message: `${target.name}: vai not configured` };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
delete servers.vai;
|
|
167
|
+
setNestedKey(config, mcpKey, servers);
|
|
168
|
+
writeConfig(configPath, config);
|
|
169
|
+
return { removed: true, message: `${target.name}: removed vai from ${configPath}` };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check status of vai MCP across all known tools.
|
|
174
|
+
*/
|
|
175
|
+
function statusAll() {
|
|
176
|
+
const results = [];
|
|
177
|
+
for (const [key, target] of Object.entries(TARGETS)) {
|
|
178
|
+
const configPath = target.configPath();
|
|
179
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
180
|
+
const config = readConfig(configPath);
|
|
181
|
+
|
|
182
|
+
let status;
|
|
183
|
+
if (!config) {
|
|
184
|
+
status = 'not found';
|
|
185
|
+
} else {
|
|
186
|
+
const servers = getNestedKey(config, mcpKey);
|
|
187
|
+
status = servers && servers.vai ? '✅ installed' : '⬚ not configured';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
results.push({ target: key, name: target.name, configPath, status });
|
|
191
|
+
}
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
TARGETS,
|
|
197
|
+
installTarget,
|
|
198
|
+
uninstallTarget,
|
|
199
|
+
statusAll,
|
|
200
|
+
buildVaiEntry,
|
|
201
|
+
};
|
|
@@ -2,66 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { MODEL_CATALOG } = require('../../lib/catalog');
|
|
4
4
|
const { loadProject } = require('../../lib/project');
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Introspect MongoDB collections — list collections with vector index info.
|
|
9
|
-
* @param {string} dbName
|
|
10
|
-
* @returns {Promise<Array<{ name: string, documentCount: number, hasVectorIndex: boolean, embeddingField?: string, dimensions?: number }>>}
|
|
11
|
-
*/
|
|
12
|
-
async function introspectCollections(dbName) {
|
|
13
|
-
const { MongoClient } = require('mongodb');
|
|
14
|
-
const uri = requireMongoUri();
|
|
15
|
-
const client = new MongoClient(uri);
|
|
16
|
-
await client.connect();
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const db = client.db(dbName);
|
|
20
|
-
const collections = await db.listCollections().toArray();
|
|
21
|
-
const results = [];
|
|
22
|
-
|
|
23
|
-
for (const collInfo of collections) {
|
|
24
|
-
if (collInfo.name.startsWith('system.')) continue;
|
|
25
|
-
const coll = db.collection(collInfo.name);
|
|
26
|
-
const documentCount = await coll.estimatedDocumentCount();
|
|
27
|
-
|
|
28
|
-
let hasVectorIndex = false;
|
|
29
|
-
let embeddingField;
|
|
30
|
-
let dimensions;
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const indexes = await coll.listSearchIndexes().toArray();
|
|
34
|
-
for (const idx of indexes) {
|
|
35
|
-
// Atlas Search index definitions vary; look for vector type
|
|
36
|
-
const fields = idx.latestDefinition?.fields || [];
|
|
37
|
-
for (const f of fields) {
|
|
38
|
-
if (f.type === 'vector') {
|
|
39
|
-
hasVectorIndex = true;
|
|
40
|
-
embeddingField = f.path;
|
|
41
|
-
dimensions = f.numDimensions;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (hasVectorIndex) break;
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// listSearchIndexes may not be available on non-Atlas deployments
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
results.push({
|
|
52
|
-
name: collInfo.name,
|
|
53
|
-
documentCount,
|
|
54
|
-
hasVectorIndex,
|
|
55
|
-
...(embeddingField && { embeddingField }),
|
|
56
|
-
...(dimensions && { dimensions }),
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return results;
|
|
61
|
-
} finally {
|
|
62
|
-
await client.close();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
5
|
+
const { introspectCollections } = require('../../lib/workflow-utils');
|
|
65
6
|
|
|
66
7
|
/**
|
|
67
8
|
* Register management tools: vai_collections, vai_models
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|