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.
- package/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/commands/chat.js +32 -11
- package/src/commands/export.js +124 -0
- package/src/commands/import.js +195 -0
- package/src/commands/index-workspace.js +239 -0
- package/src/commands/mcp-server.js +113 -3
- package/src/commands/playground.js +111 -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/workflow.js +206 -27
- package/src/mcp/install.js +280 -7
- package/src/mcp/schemas/index.js +40 -0
- package/src/mcp/server.js +2 -0
- package/src/mcp/tools/workspace.js +463 -0
- package/src/playground/announcements.md +52 -5
- package/src/playground/index.html +11125 -7796
- package/src/playground/vendor/mermaid.min.js +2811 -0
- package/src/workflows/rag-chat.json +165 -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 };
|
|
@@ -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);
|
|
@@ -460,6 +478,62 @@ function createPlaygroundServer() {
|
|
|
460
478
|
return;
|
|
461
479
|
}
|
|
462
480
|
|
|
481
|
+
// API: MCP status — installation status across all tools
|
|
482
|
+
if (req.method === 'GET' && req.url === '/api/mcp/status') {
|
|
483
|
+
try {
|
|
484
|
+
const { statusAll } = require('../mcp/install');
|
|
485
|
+
const results = statusAll();
|
|
486
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
487
|
+
res.end(JSON.stringify(results));
|
|
488
|
+
} catch (err) {
|
|
489
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
490
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// API: MCP install — install vai into a target tool
|
|
496
|
+
if (req.method === 'POST' && req.url === '/api/mcp/install') {
|
|
497
|
+
try {
|
|
498
|
+
const body = await readBody(req);
|
|
499
|
+
const { target, force } = JSON.parse(body);
|
|
500
|
+
if (!target) {
|
|
501
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
502
|
+
res.end(JSON.stringify({ error: 'target is required' }));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const { installTarget } = require('../mcp/install');
|
|
506
|
+
const result = installTarget(target, { force: force || false });
|
|
507
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
508
|
+
res.end(JSON.stringify(result));
|
|
509
|
+
} catch (err) {
|
|
510
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
511
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// API: MCP uninstall — remove vai from a target tool
|
|
517
|
+
if (req.method === 'POST' && req.url === '/api/mcp/uninstall') {
|
|
518
|
+
try {
|
|
519
|
+
const body = await readBody(req);
|
|
520
|
+
const { target } = JSON.parse(body);
|
|
521
|
+
if (!target) {
|
|
522
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
523
|
+
res.end(JSON.stringify({ error: 'target is required' }));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const { uninstallTarget } = require('../mcp/install');
|
|
527
|
+
const result = uninstallTarget(target);
|
|
528
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
529
|
+
res.end(JSON.stringify(result));
|
|
530
|
+
} catch (err) {
|
|
531
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
532
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
463
537
|
// API: Settings origins — where each config value comes from
|
|
464
538
|
if (req.method === 'GET' && req.url === '/api/settings/origins') {
|
|
465
539
|
const { resolveLLMConfig } = require('../lib/llm');
|
|
@@ -1420,15 +1494,23 @@ function createPlaygroundServer() {
|
|
|
1420
1494
|
// API: Validate a workflow definition
|
|
1421
1495
|
if (req.url === '/api/workflows/validate') {
|
|
1422
1496
|
const { validateWorkflow } = require('../lib/workflow');
|
|
1423
|
-
const { definition } = parsed;
|
|
1497
|
+
const { definition, mode } = parsed;
|
|
1424
1498
|
if (!definition) {
|
|
1425
1499
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1426
1500
|
res.end(JSON.stringify({ error: 'definition is required' }));
|
|
1427
1501
|
return;
|
|
1428
1502
|
}
|
|
1429
|
-
const
|
|
1503
|
+
const validationMode = mode || 'strict';
|
|
1504
|
+
const result = validateWorkflow(definition, { mode: validationMode });
|
|
1505
|
+
|
|
1430
1506
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1431
|
-
|
|
1507
|
+
|
|
1508
|
+
if (validationMode === 'draft') {
|
|
1509
|
+
res.end(JSON.stringify(result));
|
|
1510
|
+
} else {
|
|
1511
|
+
// Backward compatible format for strict mode
|
|
1512
|
+
res.end(JSON.stringify({ valid: result.length === 0, errors: result }));
|
|
1513
|
+
}
|
|
1432
1514
|
return;
|
|
1433
1515
|
}
|
|
1434
1516
|
|
|
@@ -1532,6 +1614,32 @@ function createPlaygroundServer() {
|
|
|
1532
1614
|
}
|
|
1533
1615
|
}
|
|
1534
1616
|
|
|
1617
|
+
// ── Export API endpoints ──
|
|
1618
|
+
const exportMatch = req.url.match(/^\/api\/export\/(workflow|chat|search|benchmark)$/);
|
|
1619
|
+
if (req.method === 'POST' && exportMatch) {
|
|
1620
|
+
const context = exportMatch[1];
|
|
1621
|
+
try {
|
|
1622
|
+
const body = JSON.parse(await readBody(req));
|
|
1623
|
+
const { exportArtifact } = require('../lib/export');
|
|
1624
|
+
const result = await exportArtifact({
|
|
1625
|
+
context,
|
|
1626
|
+
format: body.format || 'json',
|
|
1627
|
+
data: body.data || {},
|
|
1628
|
+
options: body.options || {},
|
|
1629
|
+
});
|
|
1630
|
+
const isBinary = Buffer.isBuffer(result.content);
|
|
1631
|
+
res.writeHead(200, {
|
|
1632
|
+
'Content-Type': result.mimeType,
|
|
1633
|
+
'Content-Disposition': `attachment; filename="${result.suggestedFilename}"`,
|
|
1634
|
+
});
|
|
1635
|
+
res.end(isBinary ? result.content : result.content);
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1638
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1639
|
+
}
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1535
1643
|
// 404
|
|
1536
1644
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1537
1645
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize benchmark data for export.
|
|
5
|
+
* @param {object} data - Raw benchmark data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeBenchmark(data, options = {}) {
|
|
10
|
+
const results = data.results || data.rows || [];
|
|
11
|
+
const rows = results.map((r) => {
|
|
12
|
+
const row = { ...r };
|
|
13
|
+
return row;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
_context: 'benchmark',
|
|
18
|
+
name: data.name || data.title || 'Benchmark',
|
|
19
|
+
date: data.date || new Date().toISOString(),
|
|
20
|
+
results: rows,
|
|
21
|
+
rows, // alias for CSV renderer
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BENCHMARK_FORMATS = ['json', 'csv', 'markdown', 'svg', 'png', 'clipboard'];
|
|
26
|
+
|
|
27
|
+
module.exports = { normalizeBenchmark, BENCHMARK_FORMATS };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a chat session for export.
|
|
5
|
+
* @param {object} data - Raw chat session data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeChat(data, options = {}) {
|
|
10
|
+
const turns = (data.turns || data.messages || []).map((t) => {
|
|
11
|
+
const turn = {
|
|
12
|
+
role: t.role,
|
|
13
|
+
content: t.content,
|
|
14
|
+
timestamp: t.timestamp,
|
|
15
|
+
};
|
|
16
|
+
if (options.includeSources !== false && t.context) {
|
|
17
|
+
turn.context = t.context;
|
|
18
|
+
}
|
|
19
|
+
if (options.includeMetadata && t.metadata) {
|
|
20
|
+
turn.metadata = t.metadata;
|
|
21
|
+
}
|
|
22
|
+
if (options.includeContextChunks && t.contextChunks) {
|
|
23
|
+
turn.contextChunks = t.contextChunks;
|
|
24
|
+
}
|
|
25
|
+
return turn;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
_context: 'chat',
|
|
30
|
+
sessionId: data.sessionId || data.id,
|
|
31
|
+
startedAt: data.startedAt,
|
|
32
|
+
provider: data.provider,
|
|
33
|
+
model: data.model,
|
|
34
|
+
collection: data.collection,
|
|
35
|
+
turns,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CHAT_FORMATS = ['json', 'markdown', 'pdf', 'clipboard'];
|
|
40
|
+
|
|
41
|
+
module.exports = { normalizeChat, CHAT_FORMATS };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize explore/visualization data for export.
|
|
5
|
+
* Phase 1 stub — only JSON supported.
|
|
6
|
+
* @param {object} data
|
|
7
|
+
* @param {object} options
|
|
8
|
+
* @returns {object} normalized
|
|
9
|
+
*/
|
|
10
|
+
function normalizeExplore(data, options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
_context: 'explore',
|
|
13
|
+
points: data.points || [],
|
|
14
|
+
labels: data.labels || [],
|
|
15
|
+
dimensions: data.dimensions || 2,
|
|
16
|
+
method: data.method || 'pca',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const EXPLORE_FORMATS = ['json', 'svg', 'png'];
|
|
21
|
+
|
|
22
|
+
module.exports = { normalizeExplore, EXPLORE_FORMATS };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize search results for export.
|
|
5
|
+
* @param {object} data - Raw search results data
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @returns {object} normalized
|
|
8
|
+
*/
|
|
9
|
+
function normalizeSearch(data, options = {}) {
|
|
10
|
+
const results = (data.results || []).map((r, i) => {
|
|
11
|
+
const item = {
|
|
12
|
+
rank: r.rank || i + 1,
|
|
13
|
+
score: r.score,
|
|
14
|
+
source: r.source || r.path || '',
|
|
15
|
+
};
|
|
16
|
+
if (r.rerankedScore !== undefined) item.rerankedScore = r.rerankedScore;
|
|
17
|
+
if (options.includeFullText) {
|
|
18
|
+
item.text = r.text || '';
|
|
19
|
+
} else {
|
|
20
|
+
item.text = (r.text || '').slice(0, 200);
|
|
21
|
+
}
|
|
22
|
+
if (options.includeMetadata !== false && r.metadata) {
|
|
23
|
+
item.metadata = r.metadata;
|
|
24
|
+
}
|
|
25
|
+
return item;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const normalized = {
|
|
29
|
+
_context: 'search',
|
|
30
|
+
results,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
if (options.includeQuery !== false && data.query) {
|
|
34
|
+
normalized.query = data.query;
|
|
35
|
+
normalized._exportMeta = { query: data.query };
|
|
36
|
+
}
|
|
37
|
+
if (data.collection) normalized.collection = data.collection;
|
|
38
|
+
if (data.model) normalized.model = data.model;
|
|
39
|
+
|
|
40
|
+
// Flat rows for CSV
|
|
41
|
+
normalized.rows = results.map((r) => ({
|
|
42
|
+
rank: r.rank,
|
|
43
|
+
score: r.score,
|
|
44
|
+
reranked_score: r.rerankedScore ?? '',
|
|
45
|
+
source: r.source,
|
|
46
|
+
text_excerpt: (r.text || '').slice(0, 200),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SEARCH_FORMATS = ['json', 'jsonl', 'csv', 'markdown', 'clipboard'];
|
|
53
|
+
|
|
54
|
+
module.exports = { normalizeSearch, SEARCH_FORMATS };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { buildDependencyGraph } = require('../../workflow');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a workflow definition for export.
|
|
7
|
+
* @param {object} workflow - Raw workflow JSON
|
|
8
|
+
* @param {object} options
|
|
9
|
+
* @param {boolean} [options.includeExecution=false]
|
|
10
|
+
* @param {boolean} [options.includeMetadata=false]
|
|
11
|
+
* @returns {object} normalized
|
|
12
|
+
*/
|
|
13
|
+
function normalizeWorkflow(workflow, options = {}) {
|
|
14
|
+
const normalized = {
|
|
15
|
+
_context: 'workflow',
|
|
16
|
+
name: workflow.name,
|
|
17
|
+
description: workflow.description,
|
|
18
|
+
version: workflow.version,
|
|
19
|
+
inputs: workflow.inputs || {},
|
|
20
|
+
defaults: workflow.defaults,
|
|
21
|
+
steps: workflow.steps || [],
|
|
22
|
+
output: workflow.output,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Compute dependency map for markdown rendering (Map<string, Set> → plain obj)
|
|
26
|
+
const depGraphMap = buildDependencyGraph(workflow.steps || []);
|
|
27
|
+
const depGraph = {};
|
|
28
|
+
for (const [id, deps] of depGraphMap) {
|
|
29
|
+
depGraph[id] = [...deps];
|
|
30
|
+
}
|
|
31
|
+
normalized._dependencyMap = depGraph;
|
|
32
|
+
|
|
33
|
+
// Count execution layers
|
|
34
|
+
const layerCount = computeLayerCount(workflow.steps || [], depGraph);
|
|
35
|
+
normalized._executionLayers = layerCount;
|
|
36
|
+
|
|
37
|
+
if (options.includeExecution && workflow._execution) {
|
|
38
|
+
normalized._execution = workflow._execution;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.includeMetadata) {
|
|
42
|
+
normalized._metadata = {
|
|
43
|
+
_exportedAt: new Date().toISOString(),
|
|
44
|
+
_source: workflow._source || 'local',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function computeLayerCount(steps, depGraph) {
|
|
52
|
+
if (steps.length === 0) return 0;
|
|
53
|
+
const inDegree = {};
|
|
54
|
+
const ids = steps.map((s) => s.id);
|
|
55
|
+
for (const id of ids) inDegree[id] = (depGraph[id] || []).length;
|
|
56
|
+
const remaining = new Set(ids);
|
|
57
|
+
let layers = 0;
|
|
58
|
+
while (remaining.size > 0) {
|
|
59
|
+
const layer = [];
|
|
60
|
+
for (const id of remaining) {
|
|
61
|
+
if ((inDegree[id] || 0) === 0) layer.push(id);
|
|
62
|
+
}
|
|
63
|
+
if (layer.length === 0) break;
|
|
64
|
+
layers++;
|
|
65
|
+
for (const id of layer) {
|
|
66
|
+
remaining.delete(id);
|
|
67
|
+
for (const [depId, deps] of Object.entries(depGraph)) {
|
|
68
|
+
if (remaining.has(depId) && deps.includes(id)) {
|
|
69
|
+
inDegree[depId]--;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return layers;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Supported export formats for workflows */
|
|
78
|
+
const WORKFLOW_FORMATS = ['json', 'markdown', 'mermaid', 'svg', 'png', 'clipboard'];
|
|
79
|
+
|
|
80
|
+
module.exports = { normalizeWorkflow, WORKFLOW_FORMATS };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Copy text content to the system clipboard.
|
|
8
|
+
* @param {string} content - Text to copy
|
|
9
|
+
* @returns {boolean} success
|
|
10
|
+
*/
|
|
11
|
+
function copyToClipboard(content) {
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
try {
|
|
14
|
+
if (platform === 'darwin') {
|
|
15
|
+
execSync('pbcopy', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
16
|
+
} else if (platform === 'linux') {
|
|
17
|
+
execSync('xclip -selection clipboard', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
18
|
+
} else if (platform === 'win32') {
|
|
19
|
+
execSync('clip', { input: content, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
20
|
+
} else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { copyToClipboard };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Escape a CSV field value.
|
|
5
|
+
* Wraps in quotes if the value contains commas, quotes, or newlines.
|
|
6
|
+
* @param {*} value
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
function escapeField(value) {
|
|
10
|
+
if (value === null || value === undefined) return '';
|
|
11
|
+
const str = String(value);
|
|
12
|
+
if (str.includes('"') || str.includes(',') || str.includes('\n') || str.includes('\r')) {
|
|
13
|
+
return '"' + str.replace(/"/g, '""') + '"';
|
|
14
|
+
}
|
|
15
|
+
return str;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Render an array of objects as CSV.
|
|
20
|
+
* @param {object} normalized - Must have a `rows` or `results` array of flat objects
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {string[]} [options.columns] - Explicit column order; auto-detected if omitted
|
|
23
|
+
* @returns {{ content: string, mimeType: string }}
|
|
24
|
+
*/
|
|
25
|
+
function renderCsv(normalized, options = {}) {
|
|
26
|
+
const rows = normalized.rows || normalized.results || [];
|
|
27
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
28
|
+
return { content: '', mimeType: 'text/csv' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Determine columns
|
|
32
|
+
const columns = options.columns || Object.keys(rows[0]);
|
|
33
|
+
|
|
34
|
+
const header = columns.map(escapeField).join(',');
|
|
35
|
+
const body = rows.map((row) =>
|
|
36
|
+
columns.map((col) => escapeField(row[col])).join(',')
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [header, ...body].join('\n'),
|
|
41
|
+
mimeType: 'text/csv',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { renderCsv, escapeField };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const pkg = require(path.resolve(__dirname, '..', '..', '..', '..', 'package.json'));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render normalized data as JSON.
|
|
8
|
+
* @param {object} normalized
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @returns {{ content: string, mimeType: string }}
|
|
11
|
+
*/
|
|
12
|
+
function renderJson(normalized, options = {}) {
|
|
13
|
+
const output = { ...normalized };
|
|
14
|
+
if (options.includeMetadata !== false) {
|
|
15
|
+
output._exportMeta = {
|
|
16
|
+
exportedAt: new Date().toISOString(),
|
|
17
|
+
vaiVersion: pkg.version,
|
|
18
|
+
...(normalized._exportMeta || {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Remove internal _exportMeta from source if we rebuilt it
|
|
22
|
+
if (normalized._exportMeta && output._exportMeta !== normalized._exportMeta) {
|
|
23
|
+
delete output._exportMeta;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
content: JSON.stringify(output, null, 2),
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Render normalized data as JSONL (one record per line).
|
|
33
|
+
* Expects normalized.results or normalized.items to be an array.
|
|
34
|
+
* @param {object} normalized
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* @returns {{ content: string, mimeType: string }}
|
|
37
|
+
*/
|
|
38
|
+
function renderJsonl(normalized, options = {}) {
|
|
39
|
+
const records = normalized.results || normalized.items || [];
|
|
40
|
+
if (!Array.isArray(records)) {
|
|
41
|
+
throw new Error('JSONL export requires an array of records (results or items)');
|
|
42
|
+
}
|
|
43
|
+
const lines = records.map((r) => JSON.stringify(r));
|
|
44
|
+
return {
|
|
45
|
+
content: lines.join('\n'),
|
|
46
|
+
mimeType: 'application/x-ndjson',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { renderJson, renderJsonl };
|