voyageai-cli 1.30.0 → 1.30.2
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 +4 -4
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/commands/about.js +3 -3
- package/src/commands/chat.js +32 -11
- package/src/commands/code-search.js +751 -0
- package/src/commands/doctor.js +1 -1
- package/src/commands/export.js +124 -0
- package/src/commands/import.js +195 -0
- package/src/commands/index-workspace.js +243 -0
- package/src/commands/mcp-server.js +113 -3
- package/src/commands/playground.js +120 -4
- package/src/commands/quickstart.js +4 -4
- package/src/commands/workflow.js +132 -65
- package/src/lib/catalog.js +4 -2
- package/src/lib/code-search.js +315 -0
- package/src/lib/codegen.js +1 -1
- package/src/lib/explanations.js +3 -3
- package/src/lib/export/contexts/benchmark-export.js +27 -0
- package/src/lib/export/contexts/chat-export.js +41 -0
- package/src/lib/export/contexts/explore-export.js +22 -0
- package/src/lib/export/contexts/search-export.js +54 -0
- package/src/lib/export/contexts/workflow-export.js +80 -0
- package/src/lib/export/formats/clipboard-export.js +29 -0
- package/src/lib/export/formats/csv-export.js +45 -0
- package/src/lib/export/formats/json-export.js +50 -0
- package/src/lib/export/formats/markdown-export.js +189 -0
- package/src/lib/export/formats/mermaid-export.js +274 -0
- package/src/lib/export/formats/pdf-export.js +117 -0
- package/src/lib/export/formats/png-export.js +96 -0
- package/src/lib/export/formats/svg-export.js +116 -0
- package/src/lib/export/index.js +175 -0
- package/src/lib/github.js +226 -0
- package/src/lib/template-engine.js +154 -20
- package/src/lib/workflow-builder.js +753 -0
- package/src/lib/workflow-formatters.js +454 -0
- package/src/lib/workflow-input-cache.js +111 -0
- package/src/lib/workflow-scaffold.js +1 -1
- package/src/lib/workflow.js +297 -28
- package/src/mcp/install.js +280 -7
- package/src/mcp/schemas/index.js +170 -0
- package/src/mcp/server.js +19 -4
- package/src/mcp/tools/authoring.js +662 -0
- package/src/mcp/tools/code-search.js +620 -0
- package/src/mcp/tools/ingest.js +2 -5
- package/src/mcp/tools/retrieval.js +2 -15
- package/src/mcp/tools/workspace.js +452 -0
- package/src/mcp/utils.js +20 -0
- package/src/playground/announcements.md +52 -5
- package/src/playground/help/workflow-nodes.js +127 -2
- package/src/playground/index.html +17109 -12438
- package/src/playground/vendor/mermaid.min.js +2811 -0
- package/src/workflows/code-review.json +110 -0
- package/src/workflows/cost-analysis.json +5 -0
- package/src/workflows/rag-chat.json +165 -0
- package/src/workflows/tests/code-review.fresh-index.test.json +83 -0
- package/src/workflows/tests/code-review.happy-path.test.json +121 -0
- package/src/workflows/tests/code-review.no-question.test.json +70 -0
- package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
- package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
- package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
- package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
- package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
- package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
- package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
- package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
- package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
- package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
- package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
- package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
- package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
- package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
- package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
- package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
- package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
- package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
- package/src/playground/assets/announcements/appstore.jpg +0 -0
- package/src/playground/assets/announcements/circuits.jpg +0 -0
- package/src/playground/assets/announcements/csvingest.jpg +0 -0
- package/src/playground/assets/announcements/green-wave.jpg +0 -0
|
@@ -53,21 +53,35 @@ function registerMcpServer(program) {
|
|
|
53
53
|
// Subcommand: install
|
|
54
54
|
cmd
|
|
55
55
|
.command('install [targets...]')
|
|
56
|
-
.description('Install vai MCP server into AI tool configs (claude, claude-code, cursor, windsurf, vscode, or "all")')
|
|
56
|
+
.description('Install vai MCP server into AI tool configs (claude, claude-code, cursor, windsurf, vscode, vscode-insiders, or "all")')
|
|
57
57
|
.option('--force', 'Overwrite existing vai entry', false)
|
|
58
58
|
.option('--transport <mode>', 'Transport mode: stdio or http', 'stdio')
|
|
59
59
|
.option('--port <number>', 'HTTP port (http transport only)', (v) => parseInt(v, 10))
|
|
60
60
|
.option('--api-key <key>', 'Voyage API key to embed in config')
|
|
61
|
+
.option('--mongodb-uri <uri>', 'MongoDB URI to embed in config')
|
|
62
|
+
.option('--db <name>', 'Default database name')
|
|
63
|
+
.option('--collection <name>', 'Default collection name')
|
|
64
|
+
.option('--workspace-path <path>', 'Workspace path for workspace-level config')
|
|
65
|
+
.option('--verbose', 'Enable verbose MCP logging')
|
|
66
|
+
.option('--show-tips', 'Show integration tips after install', true)
|
|
61
67
|
.action((targets, opts) => {
|
|
62
68
|
const { TARGETS, installTarget } = require('../mcp/install');
|
|
63
69
|
|
|
64
70
|
if (!targets.length) {
|
|
65
71
|
console.log('Usage: vai mcp install <target|all>');
|
|
66
|
-
console.log(
|
|
72
|
+
console.log(`\nAvailable targets:`);
|
|
73
|
+
for (const [key, target] of Object.entries(TARGETS)) {
|
|
74
|
+
const note = target.requiresWorkspace ? ' (workspace-level)' : '';
|
|
75
|
+
console.log(` ${key.padEnd(18)} ${target.name}${note}`);
|
|
76
|
+
}
|
|
77
|
+
console.log(` ${'all'.padEnd(18)} Install to all global targets`);
|
|
67
78
|
return;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
|
|
81
|
+
// Filter out workspace-only targets for 'all'
|
|
82
|
+
const keys = targets.includes('all')
|
|
83
|
+
? Object.entries(TARGETS).filter(([_, t]) => !t.requiresWorkspace).map(([k]) => k)
|
|
84
|
+
: targets;
|
|
71
85
|
|
|
72
86
|
for (const key of keys) {
|
|
73
87
|
try {
|
|
@@ -76,8 +90,22 @@ function registerMcpServer(program) {
|
|
|
76
90
|
transport: opts.transport,
|
|
77
91
|
port: opts.port,
|
|
78
92
|
apiKey: opts.apiKey,
|
|
93
|
+
mongodbUri: opts.mongodbUri,
|
|
94
|
+
db: opts.db,
|
|
95
|
+
collection: opts.collection,
|
|
96
|
+
workspacePath: opts.workspacePath,
|
|
97
|
+
verbose: opts.verbose,
|
|
79
98
|
});
|
|
80
99
|
console.log(result.installed ? `✅ ${result.message}` : `⚠️ ${result.message}`);
|
|
100
|
+
|
|
101
|
+
// Show tips if available and requested
|
|
102
|
+
if (result.tips && opts.showTips) {
|
|
103
|
+
console.log('');
|
|
104
|
+
for (const tip of result.tips) {
|
|
105
|
+
console.log(` ${tip}`);
|
|
106
|
+
}
|
|
107
|
+
console.log('');
|
|
108
|
+
}
|
|
81
109
|
} catch (err) {
|
|
82
110
|
console.error(`❌ ${key}: ${err.message}`);
|
|
83
111
|
}
|
|
@@ -123,6 +151,88 @@ function registerMcpServer(program) {
|
|
|
123
151
|
}
|
|
124
152
|
console.log('');
|
|
125
153
|
});
|
|
154
|
+
|
|
155
|
+
// Subcommand: diagnose
|
|
156
|
+
cmd
|
|
157
|
+
.command('diagnose [target]')
|
|
158
|
+
.description('Diagnose MCP installation issues for a specific target')
|
|
159
|
+
.action((target) => {
|
|
160
|
+
const { diagnose, TARGETS } = require('../mcp/install');
|
|
161
|
+
|
|
162
|
+
if (!target) {
|
|
163
|
+
console.log('Usage: vai mcp diagnose <target>');
|
|
164
|
+
console.log(`Available targets: ${Object.keys(TARGETS).join(', ')}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`\nvai MCP Diagnostics — ${target}\n`);
|
|
169
|
+
|
|
170
|
+
const results = diagnose(target);
|
|
171
|
+
for (const r of results) {
|
|
172
|
+
const icon = r.level === 'ok' ? '✅' : r.level === 'warning' ? '⚠️ ' : '❌';
|
|
173
|
+
console.log(` ${icon} ${r.message}`);
|
|
174
|
+
}
|
|
175
|
+
console.log('');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Subcommand: sample-config
|
|
179
|
+
cmd
|
|
180
|
+
.command('sample-config <target>')
|
|
181
|
+
.description('Generate sample MCP config for a target (cursor, vscode, etc.)')
|
|
182
|
+
.option('--transport <mode>', 'Transport mode: stdio or http', 'stdio')
|
|
183
|
+
.option('--port <number>', 'HTTP port', (v) => parseInt(v, 10))
|
|
184
|
+
.action((target, opts) => {
|
|
185
|
+
const { generateSampleConfig, TARGETS } = require('../mcp/install');
|
|
186
|
+
|
|
187
|
+
if (!TARGETS[target]) {
|
|
188
|
+
console.error(`Unknown target: ${target}. Available: ${Object.keys(TARGETS).join(', ')}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const sample = generateSampleConfig(target, {
|
|
193
|
+
transport: opts.transport,
|
|
194
|
+
port: opts.port,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!sample) {
|
|
198
|
+
console.error('Failed to generate sample config');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`\n# Sample MCP config for ${TARGETS[target].name}`);
|
|
203
|
+
console.log(`# Add this to your config file:\n`);
|
|
204
|
+
console.log(sample);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Subcommand: info
|
|
208
|
+
cmd
|
|
209
|
+
.command('info <target>')
|
|
210
|
+
.description('Show detailed information about a target')
|
|
211
|
+
.action((target) => {
|
|
212
|
+
const { getTargetInfo } = require('../mcp/install');
|
|
213
|
+
|
|
214
|
+
const info = getTargetInfo(target);
|
|
215
|
+
if (!info) {
|
|
216
|
+
console.error(`Unknown target: ${target}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`\n${info.name}\n`);
|
|
221
|
+
console.log(` Config path: ${info.configPath || 'N/A (workspace-level only)'}`);
|
|
222
|
+
if (info.workspaceConfigPath) {
|
|
223
|
+
console.log(` Workspace path: ${info.workspaceConfigPath}`);
|
|
224
|
+
}
|
|
225
|
+
if (info.requiresWorkspace) {
|
|
226
|
+
console.log(` Note: Requires --workspace-path option`);
|
|
227
|
+
}
|
|
228
|
+
if (info.tips.length > 0) {
|
|
229
|
+
console.log(`\n Tips:`);
|
|
230
|
+
for (const tip of info.tips) {
|
|
231
|
+
console.log(` ${tip}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
console.log('');
|
|
235
|
+
});
|
|
126
236
|
}
|
|
127
237
|
|
|
128
238
|
module.exports = { registerMcpServer };
|
|
@@ -134,7 +134,7 @@ function registerPlayground(program) {
|
|
|
134
134
|
*/
|
|
135
135
|
function createPlaygroundServer() {
|
|
136
136
|
const { getApiBase, requireApiKey, generateEmbeddings } = require('../lib/api');
|
|
137
|
-
const { MODEL_CATALOG } = require('../lib/catalog');
|
|
137
|
+
const { MODEL_CATALOG, BENCHMARK_SCORES } = require('../lib/catalog');
|
|
138
138
|
const { cosineSimilarity } = require('../lib/math');
|
|
139
139
|
const { getConfigValue } = require('../lib/config');
|
|
140
140
|
|
|
@@ -247,6 +247,24 @@ function createPlaygroundServer() {
|
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
// Serve vendor assets (bundled JS libraries)
|
|
251
|
+
const vendorMatch = req.url.match(/^\/vendor\/([a-zA-Z0-9_.-]+\.js)$/);
|
|
252
|
+
if (req.method === 'GET' && vendorMatch) {
|
|
253
|
+
const vendorPath = path.join(__dirname, '..', 'playground', 'vendor', vendorMatch[1]);
|
|
254
|
+
if (fs.existsSync(vendorPath)) {
|
|
255
|
+
const data = fs.readFileSync(vendorPath);
|
|
256
|
+
res.writeHead(200, {
|
|
257
|
+
'Content-Type': 'application/javascript; charset=utf-8',
|
|
258
|
+
'Cache-Control': 'public, max-age=86400',
|
|
259
|
+
});
|
|
260
|
+
res.end(data);
|
|
261
|
+
} else {
|
|
262
|
+
res.writeHead(404);
|
|
263
|
+
res.end('Vendor asset not found');
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
// API: Models
|
|
251
269
|
if (req.method === 'GET' && req.url === '/api/models') {
|
|
252
270
|
const models = MODEL_CATALOG.filter(m => !m.legacy && !m.local && !m.unreleased);
|
|
@@ -255,6 +273,13 @@ function createPlaygroundServer() {
|
|
|
255
273
|
return;
|
|
256
274
|
}
|
|
257
275
|
|
|
276
|
+
// API: Full Model Catalog (for Models tab)
|
|
277
|
+
if (req.method === 'GET' && req.url === '/api/models/catalog') {
|
|
278
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
279
|
+
res.end(JSON.stringify({ models: MODEL_CATALOG, benchmarks: BENCHMARK_SCORES }));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
258
283
|
// API: Generate code
|
|
259
284
|
if (req.method === 'POST' && req.url === '/api/generate') {
|
|
260
285
|
let body = '';
|
|
@@ -460,6 +485,62 @@ function createPlaygroundServer() {
|
|
|
460
485
|
return;
|
|
461
486
|
}
|
|
462
487
|
|
|
488
|
+
// API: MCP status — installation status across all tools
|
|
489
|
+
if (req.method === 'GET' && req.url === '/api/mcp/status') {
|
|
490
|
+
try {
|
|
491
|
+
const { statusAll } = require('../mcp/install');
|
|
492
|
+
const results = statusAll();
|
|
493
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
494
|
+
res.end(JSON.stringify(results));
|
|
495
|
+
} catch (err) {
|
|
496
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
497
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
498
|
+
}
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// API: MCP install — install vai into a target tool
|
|
503
|
+
if (req.method === 'POST' && req.url === '/api/mcp/install') {
|
|
504
|
+
try {
|
|
505
|
+
const body = await readBody(req);
|
|
506
|
+
const { target, force } = JSON.parse(body);
|
|
507
|
+
if (!target) {
|
|
508
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
509
|
+
res.end(JSON.stringify({ error: 'target is required' }));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const { installTarget } = require('../mcp/install');
|
|
513
|
+
const result = installTarget(target, { force: force || false });
|
|
514
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
515
|
+
res.end(JSON.stringify(result));
|
|
516
|
+
} catch (err) {
|
|
517
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
518
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// API: MCP uninstall — remove vai from a target tool
|
|
524
|
+
if (req.method === 'POST' && req.url === '/api/mcp/uninstall') {
|
|
525
|
+
try {
|
|
526
|
+
const body = await readBody(req);
|
|
527
|
+
const { target } = JSON.parse(body);
|
|
528
|
+
if (!target) {
|
|
529
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
530
|
+
res.end(JSON.stringify({ error: 'target is required' }));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const { uninstallTarget } = require('../mcp/install');
|
|
534
|
+
const result = uninstallTarget(target);
|
|
535
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
536
|
+
res.end(JSON.stringify(result));
|
|
537
|
+
} catch (err) {
|
|
538
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
539
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
463
544
|
// API: Settings origins — where each config value comes from
|
|
464
545
|
if (req.method === 'GET' && req.url === '/api/settings/origins') {
|
|
465
546
|
const { resolveLLMConfig } = require('../lib/llm');
|
|
@@ -1420,15 +1501,23 @@ function createPlaygroundServer() {
|
|
|
1420
1501
|
// API: Validate a workflow definition
|
|
1421
1502
|
if (req.url === '/api/workflows/validate') {
|
|
1422
1503
|
const { validateWorkflow } = require('../lib/workflow');
|
|
1423
|
-
const { definition } = parsed;
|
|
1504
|
+
const { definition, mode } = parsed;
|
|
1424
1505
|
if (!definition) {
|
|
1425
1506
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1426
1507
|
res.end(JSON.stringify({ error: 'definition is required' }));
|
|
1427
1508
|
return;
|
|
1428
1509
|
}
|
|
1429
|
-
const
|
|
1510
|
+
const validationMode = mode || 'strict';
|
|
1511
|
+
const result = validateWorkflow(definition, { mode: validationMode });
|
|
1512
|
+
|
|
1430
1513
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1431
|
-
|
|
1514
|
+
|
|
1515
|
+
if (validationMode === 'draft') {
|
|
1516
|
+
res.end(JSON.stringify(result));
|
|
1517
|
+
} else {
|
|
1518
|
+
// Backward compatible format for strict mode
|
|
1519
|
+
res.end(JSON.stringify({ valid: result.length === 0, errors: result }));
|
|
1520
|
+
}
|
|
1432
1521
|
return;
|
|
1433
1522
|
}
|
|
1434
1523
|
|
|
@@ -1522,6 +1611,7 @@ function createPlaygroundServer() {
|
|
|
1522
1611
|
totalTimeMs: result.totalTimeMs,
|
|
1523
1612
|
layers: result.layers,
|
|
1524
1613
|
steps: result.steps,
|
|
1614
|
+
formatters: result.formatters || null,
|
|
1525
1615
|
})}\n\n`);
|
|
1526
1616
|
} catch (err) {
|
|
1527
1617
|
res.write(`event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`);
|
|
@@ -1532,6 +1622,32 @@ function createPlaygroundServer() {
|
|
|
1532
1622
|
}
|
|
1533
1623
|
}
|
|
1534
1624
|
|
|
1625
|
+
// ── Export API endpoints ──
|
|
1626
|
+
const exportMatch = req.url.match(/^\/api\/export\/(workflow|chat|search|benchmark)$/);
|
|
1627
|
+
if (req.method === 'POST' && exportMatch) {
|
|
1628
|
+
const context = exportMatch[1];
|
|
1629
|
+
try {
|
|
1630
|
+
const body = JSON.parse(await readBody(req));
|
|
1631
|
+
const { exportArtifact } = require('../lib/export');
|
|
1632
|
+
const result = await exportArtifact({
|
|
1633
|
+
context,
|
|
1634
|
+
format: body.format || 'json',
|
|
1635
|
+
data: body.data || {},
|
|
1636
|
+
options: body.options || {},
|
|
1637
|
+
});
|
|
1638
|
+
const isBinary = Buffer.isBuffer(result.content);
|
|
1639
|
+
res.writeHead(200, {
|
|
1640
|
+
'Content-Type': result.mimeType,
|
|
1641
|
+
'Content-Disposition': `attachment; filename="${result.suggestedFilename}"`,
|
|
1642
|
+
});
|
|
1643
|
+
res.end(isBinary ? result.content : result.content);
|
|
1644
|
+
} catch (err) {
|
|
1645
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1646
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1647
|
+
}
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1535
1651
|
// 404
|
|
1536
1652
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1537
1653
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -83,7 +83,7 @@ We'll embed these ${SAMPLE_DOCS.length} sample documents:
|
|
|
83
83
|
console.log(pc.bold('\nStep 2: Embedding Documents'));
|
|
84
84
|
console.log(pc.dim('─'.repeat(40)));
|
|
85
85
|
console.log(`
|
|
86
|
-
Running: ${pc.cyan('vai embed --model voyage-
|
|
86
|
+
Running: ${pc.cyan('vai embed --model voyage-4-lite')}
|
|
87
87
|
`);
|
|
88
88
|
|
|
89
89
|
let embeddings;
|
|
@@ -91,7 +91,7 @@ Running: ${pc.cyan('vai embed --model voyage-3-lite')}
|
|
|
91
91
|
process.stdout.write(' Embedding documents... ');
|
|
92
92
|
const result = await embed({
|
|
93
93
|
texts: SAMPLE_DOCS,
|
|
94
|
-
model: 'voyage-
|
|
94
|
+
model: 'voyage-4-lite',
|
|
95
95
|
inputType: 'document',
|
|
96
96
|
});
|
|
97
97
|
embeddings = result.embeddings;
|
|
@@ -99,7 +99,7 @@ Running: ${pc.cyan('vai embed --model voyage-3-lite')}
|
|
|
99
99
|
console.log(`
|
|
100
100
|
${pc.green('✓')} Created ${embeddings.length} embeddings
|
|
101
101
|
${pc.dim(` Dimensions: ${embeddings[0].length}`)}
|
|
102
|
-
${pc.dim(` Model: voyage-
|
|
102
|
+
${pc.dim(` Model: voyage-4-lite`)}
|
|
103
103
|
`);
|
|
104
104
|
} catch (err) {
|
|
105
105
|
console.log(pc.red('✗'));
|
|
@@ -123,7 +123,7 @@ Query: "${pc.cyan(query)}"
|
|
|
123
123
|
process.stdout.write(' Embedding query... ');
|
|
124
124
|
const queryResult = await embed({
|
|
125
125
|
texts: [query],
|
|
126
|
-
model: 'voyage-
|
|
126
|
+
model: 'voyage-4-lite',
|
|
127
127
|
inputType: 'query',
|
|
128
128
|
});
|
|
129
129
|
const queryEmbedding = queryResult.embeddings[0];
|
package/src/commands/workflow.js
CHANGED
|
@@ -48,8 +48,13 @@ async function promptForInputs(definition, existingInputs) {
|
|
|
48
48
|
const { buildInputSteps } = require('../lib/workflow');
|
|
49
49
|
const { createCLIRenderer } = require('../lib/wizard-cli');
|
|
50
50
|
const { runWizard } = require('../lib/wizard');
|
|
51
|
+
const { loadInputCache } = require('../lib/workflow-input-cache');
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
// Load cached inputs from last run to use as defaults
|
|
54
|
+
const workflowName = definition.name || '';
|
|
55
|
+
const cachedInputs = loadInputCache(workflowName);
|
|
56
|
+
|
|
57
|
+
const allSteps = buildInputSteps(definition, cachedInputs);
|
|
53
58
|
// Only prompt for inputs not already provided
|
|
54
59
|
const steps = allSteps.filter(s => !(s.id in existingInputs));
|
|
55
60
|
if (steps.length === 0) return existingInputs;
|
|
@@ -93,6 +98,7 @@ function registerWorkflow(program) {
|
|
|
93
98
|
.option('--db <name>', 'Override default database')
|
|
94
99
|
.option('--collection <name>', 'Override default collection')
|
|
95
100
|
.option('--json', 'Output results as JSON', false)
|
|
101
|
+
.option('-o, --output <format>', 'Output format: json, table, markdown, text, csv, value:<path>')
|
|
96
102
|
.option('--quiet', 'Suppress progress output', false)
|
|
97
103
|
.option('--dry-run', 'Show execution plan without running', false)
|
|
98
104
|
.option('--verbose', 'Show step details', false)
|
|
@@ -232,33 +238,24 @@ function registerWorkflow(program) {
|
|
|
232
238
|
|
|
233
239
|
wfDone();
|
|
234
240
|
|
|
241
|
+
// Cache inputs for next run
|
|
242
|
+
try {
|
|
243
|
+
const { saveInputCache } = require('../lib/workflow-input-cache');
|
|
244
|
+
saveInputCache(workflowName, opts.input);
|
|
245
|
+
} catch { /* non-critical, don't fail the run */ }
|
|
246
|
+
|
|
235
247
|
// Output
|
|
236
|
-
|
|
248
|
+
const { formatWorkflowOutput, autoDetectFormat } = require('../lib/workflow-formatters');
|
|
249
|
+
const fmtHints = definition.formatters || {};
|
|
250
|
+
|
|
251
|
+
if (opts.json || opts.output === 'json') {
|
|
237
252
|
console.log(JSON.stringify(result.output, null, 2));
|
|
253
|
+
} else if (opts.output) {
|
|
254
|
+
console.log(formatWorkflowOutput(result.output, opts.output, fmtHints));
|
|
238
255
|
} else if (result.output) {
|
|
239
|
-
//
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
const top = output.results.slice(0, 5);
|
|
243
|
-
console.log(pc.bold('Top results:'));
|
|
244
|
-
for (let i = 0; i < top.length; i++) {
|
|
245
|
-
const r = top[i];
|
|
246
|
-
const source = r.source || r.text?.slice(0, 50) || `result ${i + 1}`;
|
|
247
|
-
const score = r.score != null ? ` (${r.score.toFixed(2)})` : '';
|
|
248
|
-
console.log(` ${pc.dim(`[${i + 1}]`)} ${source}${pc.dim(score)}`);
|
|
249
|
-
}
|
|
250
|
-
} else if (output.summary) {
|
|
251
|
-
console.log(output.summary);
|
|
252
|
-
} else if (output.comparison) {
|
|
253
|
-
console.log(pc.bold('Cost comparison:'));
|
|
254
|
-
for (const item of output.comparison) {
|
|
255
|
-
if (item && item.model) {
|
|
256
|
-
console.log(` ${pc.cyan(item.model)}: $${item.totalCost} total (embed: $${item.embeddingCost}, queries: $${item.monthlyQueryCost}/mo)`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
console.log(JSON.stringify(output, null, 2));
|
|
261
|
-
}
|
|
256
|
+
// Auto-detect best format from output shape and workflow hints
|
|
257
|
+
const bestFormat = autoDetectFormat(result.output, fmtHints);
|
|
258
|
+
console.log(formatWorkflowOutput(result.output, bestFormat, fmtHints));
|
|
262
259
|
}
|
|
263
260
|
} catch (err) {
|
|
264
261
|
console.error(ui.error(err.message));
|
|
@@ -862,8 +859,9 @@ function registerWorkflow(program) {
|
|
|
862
859
|
// ── workflow create ──
|
|
863
860
|
wfCmd
|
|
864
861
|
.command('create')
|
|
865
|
-
.description('
|
|
862
|
+
.description('Interactive workflow builder -- scaffold a validated, publish-ready workflow package')
|
|
866
863
|
.option('--from <file>', 'Existing workflow JSON to package')
|
|
864
|
+
.option('--from-description <desc>', 'Generate a workflow skeleton from a text description')
|
|
867
865
|
.option('--name <name>', 'Package name (without vai-workflow- prefix)')
|
|
868
866
|
.option('--author <name>', 'Author name')
|
|
869
867
|
.option('--description <desc>', 'Package description')
|
|
@@ -871,8 +869,9 @@ function registerWorkflow(program) {
|
|
|
871
869
|
.option('--scope <scope>', 'Package scope (e.g. "vaicli" for @vaicli/vai-workflow-*)')
|
|
872
870
|
.option('--output <dir>', 'Output directory')
|
|
873
871
|
.action(async (opts) => {
|
|
874
|
-
const { scaffoldPackage, toPackageName, CATEGORIES
|
|
875
|
-
const { loadWorkflow } = require('../lib/workflow');
|
|
872
|
+
const { scaffoldPackage, toPackageName, CATEGORIES } = require('../lib/workflow-scaffold');
|
|
873
|
+
const { loadWorkflow, validateWorkflow, buildExecutionPlan } = require('../lib/workflow');
|
|
874
|
+
const { runInteractiveBuilder, workflowFromDescription } = require('../lib/workflow-builder');
|
|
876
875
|
|
|
877
876
|
let definition;
|
|
878
877
|
let name = opts.name;
|
|
@@ -894,50 +893,85 @@ function registerWorkflow(program) {
|
|
|
894
893
|
if (!description) {
|
|
895
894
|
description = definition.description;
|
|
896
895
|
}
|
|
897
|
-
} else if (
|
|
898
|
-
//
|
|
896
|
+
} else if (opts.fromDescription) {
|
|
897
|
+
// Generate from text description
|
|
899
898
|
try {
|
|
899
|
+
definition = workflowFromDescription(opts.fromDescription);
|
|
900
|
+
name = name || definition.name;
|
|
901
|
+
description = description || definition.description;
|
|
902
|
+
|
|
900
903
|
const p = require('@clack/prompts');
|
|
901
|
-
p.intro(pc.bold('
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
904
|
+
p.intro(pc.bold('Generated workflow from description'));
|
|
905
|
+
|
|
906
|
+
// Show what was generated
|
|
907
|
+
p.log.info(`Name: ${pc.cyan(definition.name)}`);
|
|
908
|
+
p.log.info(`Steps: ${definition.steps.map(s => `${pc.cyan(s.id)} (${s.tool})`).join(' -> ')}`);
|
|
909
|
+
p.log.info(`Inputs: ${Object.keys(definition.inputs).join(', ') || 'none'}`);
|
|
910
|
+
|
|
911
|
+
// Show execution plan
|
|
912
|
+
try {
|
|
913
|
+
const layers = buildExecutionPlan(definition.steps);
|
|
914
|
+
p.log.info(pc.bold('Execution plan:'));
|
|
915
|
+
for (let i = 0; i < layers.length; i++) {
|
|
916
|
+
p.log.message(` Layer ${i + 1}: ${layers[i].join(', ')}`);
|
|
917
|
+
}
|
|
918
|
+
} catch (e) { /* skip */ }
|
|
919
|
+
|
|
920
|
+
// Validate
|
|
921
|
+
const errors = validateWorkflow(definition);
|
|
922
|
+
if (errors.length > 0) {
|
|
923
|
+
p.log.warn('Validation issues:');
|
|
924
|
+
for (const err of errors) {
|
|
925
|
+
p.log.warn(` ${err}`);
|
|
926
|
+
}
|
|
927
|
+
} else {
|
|
928
|
+
p.log.success('Workflow validates successfully!');
|
|
916
929
|
}
|
|
917
930
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
931
|
+
// Ask for category if not provided
|
|
932
|
+
if (!category) {
|
|
933
|
+
const { guessCategory, extractTools } = require('../lib/workflow-scaffold');
|
|
934
|
+
category = guessCategory(extractTools(definition));
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Ask for author if not provided
|
|
938
|
+
if (!author) {
|
|
939
|
+
author = getGitAuthor();
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Confirm or edit
|
|
943
|
+
const proceed = await p.confirm({ message: 'Scaffold this workflow as a package?', initialValue: true });
|
|
944
|
+
if (p.isCancel(proceed) || !proceed) {
|
|
945
|
+
// Write just the workflow.json for manual editing
|
|
946
|
+
const fs = require('fs');
|
|
947
|
+
const filename = `${definition.name}.vai-workflow.json`;
|
|
948
|
+
fs.writeFileSync(filename, JSON.stringify(definition, null, 2) + '\n');
|
|
949
|
+
p.log.info(`Wrote ${pc.cyan(filename)} for manual editing.`);
|
|
950
|
+
p.outro('Edit the file and run `vai workflow create --from <file>` when ready.');
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
935
953
|
} catch (err) {
|
|
954
|
+
console.error(ui.error(`Generation failed: ${err.message}`));
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
} else if (process.stdin.isTTY) {
|
|
958
|
+
// Full interactive builder
|
|
959
|
+
try {
|
|
960
|
+
const result = await runInteractiveBuilder();
|
|
961
|
+
definition = result.definition;
|
|
962
|
+
name = name || result.name;
|
|
963
|
+
description = description || result.description;
|
|
964
|
+
category = category || result.category;
|
|
965
|
+
author = author || result.author;
|
|
966
|
+
} catch (err) {
|
|
967
|
+
if (err.message && err.message.includes('cancelled')) {
|
|
968
|
+
process.exit(0);
|
|
969
|
+
}
|
|
936
970
|
console.error(ui.error(`Interactive mode failed: ${err.message}`));
|
|
937
971
|
process.exit(1);
|
|
938
972
|
}
|
|
939
973
|
} else {
|
|
940
|
-
console.error(ui.error('Provide --from <file
|
|
974
|
+
console.error(ui.error('Provide --from <file>, --from-description "text", or run interactively (TTY required).'));
|
|
941
975
|
process.exit(1);
|
|
942
976
|
}
|
|
943
977
|
|
|
@@ -965,8 +999,8 @@ function registerWorkflow(program) {
|
|
|
965
999
|
}
|
|
966
1000
|
console.log();
|
|
967
1001
|
console.log('Next steps:');
|
|
968
|
-
console.log(` 1. ${opts.from ? '
|
|
969
|
-
console.log(` 2.
|
|
1002
|
+
console.log(` 1. ${opts.from ? 'Review README.md' : `cd ${pkgName} && review workflow.json`}`);
|
|
1003
|
+
console.log(` 2. ${pc.dim('vai workflow validate')} ${pkgName}/workflow.json`);
|
|
970
1004
|
console.log(` 3. npm publish`);
|
|
971
1005
|
console.log();
|
|
972
1006
|
} catch (err) {
|
|
@@ -1021,6 +1055,39 @@ function registerWorkflow(program) {
|
|
|
1021
1055
|
console.log(` ${pc.dim('Run with:')} vai workflow run ${filename} --input query="your question"`);
|
|
1022
1056
|
console.log(` ${pc.dim('Validate:')} vai workflow validate ${filename}`);
|
|
1023
1057
|
});
|
|
1058
|
+
|
|
1059
|
+
// ── workflow clear-cache [name] ──
|
|
1060
|
+
wfCmd
|
|
1061
|
+
.command('clear-cache [name]')
|
|
1062
|
+
.description('Clear cached workflow inputs (from previous runs)')
|
|
1063
|
+
.action((name) => {
|
|
1064
|
+
const { clearInputCache, loadInputCache, slugify, CACHE_PATH } = require('../lib/workflow-input-cache');
|
|
1065
|
+
const fs = require('fs');
|
|
1066
|
+
|
|
1067
|
+
if (name) {
|
|
1068
|
+
const cached = loadInputCache(name);
|
|
1069
|
+
const keys = Object.keys(cached);
|
|
1070
|
+
if (keys.length === 0) {
|
|
1071
|
+
console.log(pc.dim(`No cached inputs for "${name}".`));
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
clearInputCache(name);
|
|
1075
|
+
console.log(ui.success(`Cleared cached inputs for "${name}" (${keys.length} field${keys.length === 1 ? '' : 's'}).`));
|
|
1076
|
+
} else {
|
|
1077
|
+
// Clear all
|
|
1078
|
+
let count = 0;
|
|
1079
|
+
try {
|
|
1080
|
+
const raw = fs.readFileSync(CACHE_PATH, 'utf-8');
|
|
1081
|
+
count = Object.keys(JSON.parse(raw)).length;
|
|
1082
|
+
} catch { /* no file */ }
|
|
1083
|
+
if (count === 0) {
|
|
1084
|
+
console.log(pc.dim('No cached workflow inputs.'));
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
clearInputCache();
|
|
1088
|
+
console.log(ui.success(`Cleared cached inputs for ${count} workflow${count === 1 ? '' : 's'}.`));
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1024
1091
|
}
|
|
1025
1092
|
|
|
1026
1093
|
/**
|
package/src/lib/catalog.js
CHANGED
|
@@ -39,8 +39,10 @@ const MODEL_CATALOG = [
|
|
|
39
39
|
{ name: 'voyage-4-nano', type: 'embedding', context: '32K', dimensions: '512 (default), 128, 256', price: 'Open-weight (free)', pricePerMToken: 0, bestFor: 'Open-weight / edge / local', shortFor: 'Open / edge', local: true, unreleased: true, family: 'voyage-4', architecture: 'dense', sharedSpace: 'voyage-4', huggingface: 'https://huggingface.co/voyageai/voyage-4-nano', rtebScore: null },
|
|
40
40
|
// Legacy models
|
|
41
41
|
{ name: 'voyage-3-large', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.18/1M tokens', pricePerMToken: 0.18, bestFor: 'Previous gen quality', shortFor: 'Previous gen quality', legacy: true, rtebScore: null },
|
|
42
|
-
{ name: 'voyage-3
|
|
43
|
-
{ name: 'voyage-3
|
|
42
|
+
{ name: 'voyage-3', type: 'embedding', context: '32K', dimensions: '1024', price: '$0.06/1M tokens', pricePerMToken: 0.06, bestFor: 'Previous gen balanced', shortFor: 'Previous gen balanced', legacy: true, rtebScore: null },
|
|
43
|
+
{ name: 'voyage-3-lite', type: 'embedding', context: '32K', dimensions: '512', price: '$0.02/1M tokens', pricePerMToken: 0.02, bestFor: 'Previous gen budget', shortFor: 'Previous gen budget', legacy: true, rtebScore: null },
|
|
44
|
+
{ name: 'voyage-3.5', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.06/1M tokens', pricePerMToken: 0.06, bestFor: 'Previous gen balanced (3.5)', shortFor: 'Previous gen 3.5', legacy: true, rtebScore: null },
|
|
45
|
+
{ name: 'voyage-3.5-lite', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.02/1M tokens', pricePerMToken: 0.02, bestFor: 'Previous gen budget (3.5)', shortFor: 'Previous gen 3.5-lite', legacy: true, rtebScore: null },
|
|
44
46
|
{ name: 'voyage-code-2', type: 'embedding', context: '16K', dimensions: '1536', price: '$0.12/1M tokens', pricePerMToken: 0.12, bestFor: 'Legacy code', shortFor: 'Legacy code', legacy: true },
|
|
45
47
|
{ name: 'voyage-multimodal-3', type: 'embedding-multimodal', context: '32K', dimensions: '1024', price: '$0.12/1M tokens', pricePerMToken: 0.12, bestFor: 'Legacy multimodal', shortFor: 'Legacy multimodal', legacy: true, multimodal: true },
|
|
46
48
|
{ name: 'rerank-2', type: 'reranking', context: '16K', dimensions: '—', price: '$0.05/1M tokens', pricePerMToken: 0.05, bestFor: 'Legacy reranker', shortFor: 'Legacy reranker', legacy: true },
|