voyageai-cli 1.29.0 โ†’ 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +82 -8
  2. package/package.json +1 -1
  3. package/src/commands/benchmark.js +22 -8
  4. package/src/commands/chat.js +18 -0
  5. package/src/commands/chunk.js +10 -0
  6. package/src/commands/demo.js +4 -0
  7. package/src/commands/embed.js +13 -0
  8. package/src/commands/estimate.js +3 -0
  9. package/src/commands/eval.js +6 -0
  10. package/src/commands/explain.js +2 -0
  11. package/src/commands/generate.js +2 -0
  12. package/src/commands/ingest.js +4 -0
  13. package/src/commands/init.js +2 -0
  14. package/src/commands/mcp-server.js +2 -0
  15. package/src/commands/models.js +2 -0
  16. package/src/commands/ping.js +7 -0
  17. package/src/commands/pipeline.js +15 -0
  18. package/src/commands/playground.js +52 -6
  19. package/src/commands/query.js +16 -0
  20. package/src/commands/rerank.js +12 -0
  21. package/src/commands/scaffold.js +2 -0
  22. package/src/commands/search.js +11 -0
  23. package/src/commands/similarity.js +9 -0
  24. package/src/commands/store.js +4 -0
  25. package/src/commands/workflow.js +286 -0
  26. package/src/lib/capability-report.js +134 -0
  27. package/src/lib/chat.js +32 -1
  28. package/src/lib/config.js +2 -0
  29. package/src/lib/cost-display.js +107 -0
  30. package/src/lib/explanations.js +6 -0
  31. package/src/lib/llm.js +125 -18
  32. package/src/lib/quality-audit.js +71 -0
  33. package/src/lib/security/blocked-domains.json +17 -0
  34. package/src/lib/security-audit.js +198 -0
  35. package/src/lib/telemetry.js +23 -1
  36. package/src/lib/workflow-scaffold.js +61 -0
  37. package/src/lib/workflow-test-runner.js +208 -0
  38. package/src/lib/workflow.js +128 -2
  39. package/src/playground/announcements.md +9 -0
  40. package/src/playground/assets/announcements/appstore.jpg +0 -0
  41. package/src/playground/assets/announcements/circuits.jpg +0 -0
  42. package/src/playground/assets/announcements/csvingest.jpg +0 -0
  43. package/src/playground/assets/announcements/green-wave.jpg +0 -0
  44. package/src/playground/help/workflow-nodes.js +472 -0
  45. package/src/playground/index.html +1482 -184
package/README.md CHANGED
@@ -38,27 +38,34 @@ vai quickstart # Interactive tutorial โ€” zero to semantic search in 2 minute
38
38
 
39
39
  <table>
40
40
  <tr>
41
- <td align="center" width="33%">
41
+ <td align="center" width="25%">
42
42
  <h3>๐Ÿ–ฅ๏ธ CLI</h3>
43
43
  <code>vai</code><br/><br/>
44
44
  22 commands ยท 5 chunking strategies<br/>
45
45
  End-to-end RAG pipeline from your terminal<br/><br/>
46
- <code>npm install -g voyageai-cli</code>
46
+ <code>curl -fsSL https://vaicli.com/install.sh | sh</code>
47
47
  </td>
48
- <td align="center" width="33%">
48
+ <td align="center" width="25%">
49
49
  <h3>๐ŸŒ Web Playground</h3>
50
50
  <code>vai playground</code><br/><br/>
51
- 7 interactive tabs for embedding,<br/>
52
- comparing, searching, and benchmarking<br/><br/>
51
+ 7 interactive tabs + Workflow Store<br/>
52
+ with 20+ installable workflow packages<br/><br/>
53
53
  <em>Launches in your browser</em>
54
54
  </td>
55
- <td align="center" width="33%">
55
+ <td align="center" width="25%">
56
56
  <h3>๐Ÿ’ป Desktop App</h3>
57
57
  Standalone Electron app<br/><br/>
58
58
  Secure keychain storage, dark/light themes,<br/>
59
59
  MongoDB LeafyGreen design system<br/><br/>
60
60
  <a href="https://github.com/mrlynn/voyageai-cli/releases">Download from GitHub Releases</a>
61
61
  </td>
62
+ <td align="center" width="25%">
63
+ <h3>๐Ÿช Workflow Store</h3>
64
+ <code>vai store</code> or Playground<br/><br/>
65
+ Browse, install, and run pre-built<br/>
66
+ RAG workflows from npm<br/><br/>
67
+ <em>Official + community packages</em>
68
+ </td>
62
69
  </tr>
63
70
  </table>
64
71
 
@@ -81,6 +88,7 @@ MongoDB LeafyGreen design system<br/><br/>
81
88
  - [Environment & Auth](#environment--auth)
82
89
  - [Shell Completions](#shell-completions)
83
90
  - [All Commands](#all-commands)
91
+ - [Workflow Store](#workflow-store)
84
92
  - [MCP Server](#mcp-server)
85
93
  - [Screenshots](#screenshots)
86
94
  - [Requirements](#requirements)
@@ -115,6 +123,8 @@ Download the latest release for your platform from [GitHub Releases](https://git
115
123
  | Windows | `.exe` installer |
116
124
  | Linux | `.AppImage` / `.deb` |
117
125
 
126
+ > **Prefer the CLI?** Install with `curl -fsSL https://vaicli.com/install.sh | sh` or `brew install mrlynn/vai/vai`
127
+
118
128
  ---
119
129
 
120
130
  ## Web Playground
@@ -125,7 +135,7 @@ An interactive, browser-based interface for exploring Voyage AI embeddings witho
125
135
  vai playground
126
136
  ```
127
137
 
128
- Your default browser opens with a full-featured UI organized into **7 tabs**:
138
+ Your default browser opens with a full-featured UI:
129
139
 
130
140
  | Tab | What It Does |
131
141
  |-----|-------------|
@@ -134,6 +144,7 @@ Your default browser opens with a full-featured UI organized into **7 tabs**:
134
144
  | **Search** | Connect to MongoDB Atlas and run vector similarity searches with filters and reranking |
135
145
  | **Benchmark** | Compare model latency, cost, and quality across the Voyage 4 family on your own data |
136
146
  | **Explore** | Visualize embedding spaces with dimensionality reduction (PCA/t-SNE) and clustering |
147
+ | **Workflow Store** | Browse, install, and run 20+ official and community workflow packages |
137
148
  | **About** | Project info, links, and version details |
138
149
  | **Settings** | Configure API keys, MongoDB URI, default model, and preferences |
139
150
 
@@ -143,12 +154,19 @@ The playground connects to the same backend as the CLI. Any API keys or MongoDB
143
154
 
144
155
  ## CLI โ€” Quick Start
145
156
 
146
- **22 commands ยท 312 tests ยท 5 chunking strategies ยท End-to-end RAG pipeline**
157
+ **22 commands ยท 1,000+ tests ยท 5 chunking strategies ยท End-to-end RAG pipeline**
147
158
 
148
159
  ### Install
149
160
 
150
161
  ```bash
162
+ # Fastest โ€” one command, no dependencies
163
+ curl -fsSL https://vaicli.com/install.sh | sh
164
+
165
+ # Via npm
151
166
  npm install -g voyageai-cli
167
+
168
+ # Via Homebrew
169
+ brew install mrlynn/vai/vai
152
170
  ```
153
171
 
154
172
  ### 5-Minute RAG Pipeline
@@ -535,6 +553,11 @@ Covers all 22 commands, subcommands, flags, model names, and explain topics.
535
553
  | `vai eval` | Evaluate retrieval quality (MRR, nDCG, Recall) |
536
554
  | `vai eval compare` | Compare configurations side-by-side |
537
555
  | `vai benchmark` | 8 subcommands for model comparison |
556
+ | **Workflow Store** | |
557
+ | `vai store list` | Browse available workflows (official + community) |
558
+ | `vai store install` | Install a workflow package from npm |
559
+ | `vai store run` | Run an installed workflow |
560
+ | `vai store uninstall` | Remove an installed workflow |
538
561
  | **MCP Server** | |
539
562
  | `vai mcp` | Start the MCP server (expose vai tools to AI agents) |
540
563
  | `vai mcp install` | Install vai into AI tool configs (Claude, Cursor, etc.) |
@@ -554,6 +577,57 @@ Covers all 22 commands, subcommands, flags, model names, and explain topics.
554
577
 
555
578
  ---
556
579
 
580
+ ## Workflow Store
581
+
582
+ Browse, install, and run pre-built RAG workflows โ€” from the Playground UI or the CLI. The Workflow Store features **20 official workflows** and a growing ecosystem of community packages published on npm.
583
+
584
+ ### Browse & Install
585
+
586
+ ```bash
587
+ # Open the visual Workflow Store in the Playground
588
+ vai playground # Click the grid icon โ†’ Store
589
+
590
+ # Or from the CLI
591
+ vai store list # Browse available workflows
592
+ vai store install model-shootout # Install a workflow
593
+ vai store run model-shootout # Run it
594
+ ```
595
+
596
+ ### Official Workflows
597
+
598
+ | Workflow | Category | What It Does |
599
+ |----------|----------|-------------|
600
+ | **model-shootout** | Utility | Compare voyage-4-large, voyage-4, and voyage-4-lite side-by-side on your data |
601
+ | **asymmetric-search** | Retrieval | Embed with voyage-4-large, query with voyage-4-lite โ€” ~83% cost reduction |
602
+ | **cost-optimizer** | Utility | Quantify exact cost savings of asymmetric retrieval |
603
+ | **question-decomposition** | Retrieval | Break complex questions into sub-queries, search in parallel, merge & rerank |
604
+ | **knowledge-base-bootstrap** | Integration | End-to-end onboarding: ingest โ†’ verify โ†’ test query โ†’ status report |
605
+ | **hybrid-precision-search** | Retrieval | Three retrieval strategies in parallel, merged and reranked |
606
+ | **embedding-drift-detector** | Analysis | Monitor embedding quality over time |
607
+ | **multilingual-search** | Retrieval | Translate queries into multiple languages, search each in parallel |
608
+
609
+ Plus 12 more covering code migration, financial risk scanning, clinical protocol matching, meeting action items, and more.
610
+
611
+ ### Community Packages
612
+
613
+ Anyone can publish a workflow to npm. Tag your package with `vai-workflow` and add a `vai-workflow` field to your `package.json`:
614
+
615
+ ```json
616
+ {
617
+ "name": "vai-workflow-my-pipeline",
618
+ "keywords": ["vai-workflow"],
619
+ "vai-workflow": {
620
+ "category": "retrieval",
621
+ "tags": ["custom", "my-use-case"],
622
+ "tools": ["query", "rerank", "generate"]
623
+ }
624
+ }
625
+ ```
626
+
627
+ Community workflows appear automatically in the Store alongside official packages.
628
+
629
+ ---
630
+
557
631
  ## MCP Server
558
632
 
559
633
  Expose vai's RAG tools to any MCP-compatible AI agent โ€” Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, and more. **11 tools** for embedding, retrieval, reranking, ingestion, and learning โ€” all accessible without writing code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voyageai-cli",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search",
5
5
  "bin": {
6
6
  "vai": "./src/cli.js"
@@ -1112,6 +1112,20 @@ function formatBytes(bytes) {
1112
1112
  * Register the benchmark command tree on a Commander program.
1113
1113
  * @param {import('commander').Command} program
1114
1114
  */
1115
+ function withTelemetry(subcommand, fn) {
1116
+ return async (...args) => {
1117
+ const telemetry = require('../lib/telemetry');
1118
+ const done = telemetry.timer('cli_benchmark', { subcommand });
1119
+ try {
1120
+ await fn(...args);
1121
+ done();
1122
+ } catch (err) {
1123
+ telemetry.send('cli_error', { command: 'benchmark', errorType: err.constructor.name });
1124
+ throw err;
1125
+ }
1126
+ };
1127
+ }
1128
+
1115
1129
  function registerBenchmark(program) {
1116
1130
  const bench = program
1117
1131
  .command('benchmark')
@@ -1131,7 +1145,7 @@ function registerBenchmark(program) {
1131
1145
  .option('--json', 'Machine-readable JSON output')
1132
1146
  .option('-q, --quiet', 'Suppress non-essential output')
1133
1147
  .option('-s, --save [path]', 'Save results to JSON file')
1134
- .action(benchmarkEmbed);
1148
+ .action(withTelemetry("embed", benchmarkEmbed));
1135
1149
 
1136
1150
  // โ”€โ”€ benchmark rerank โ”€โ”€
1137
1151
  bench
@@ -1145,7 +1159,7 @@ function registerBenchmark(program) {
1145
1159
  .option('--json', 'Machine-readable JSON output')
1146
1160
  .option('-q, --quiet', 'Suppress non-essential output')
1147
1161
  .option('-s, --save [path]', 'Save results to JSON file')
1148
- .action(benchmarkRerank);
1162
+ .action(withTelemetry("rerank", benchmarkRerank));
1149
1163
 
1150
1164
  // โ”€โ”€ benchmark similarity โ”€โ”€
1151
1165
  bench
@@ -1158,7 +1172,7 @@ function registerBenchmark(program) {
1158
1172
  .option('-d, --dimensions <n>', 'Output dimensions')
1159
1173
  .option('--json', 'Machine-readable JSON output')
1160
1174
  .option('-q, --quiet', 'Suppress non-essential output')
1161
- .action(benchmarkSimilarity);
1175
+ .action(withTelemetry("similarity", benchmarkSimilarity));
1162
1176
 
1163
1177
  // โ”€โ”€ benchmark cost โ”€โ”€
1164
1178
  bench
@@ -1170,7 +1184,7 @@ function registerBenchmark(program) {
1170
1184
  .option('--volumes <list>', 'Comma-separated daily query volumes', '100,1000,10000,100000')
1171
1185
  .option('--json', 'Machine-readable JSON output')
1172
1186
  .option('-q, --quiet', 'Suppress non-essential output')
1173
- .action(benchmarkCost);
1187
+ .action(withTelemetry("cost", benchmarkCost));
1174
1188
 
1175
1189
  // โ”€โ”€ benchmark batch โ”€โ”€
1176
1190
  bench
@@ -1182,7 +1196,7 @@ function registerBenchmark(program) {
1182
1196
  .option('-f, --file <path>', 'Load texts from file')
1183
1197
  .option('--json', 'Machine-readable JSON output')
1184
1198
  .option('-q, --quiet', 'Suppress non-essential output')
1185
- .action(benchmarkBatch);
1199
+ .action(withTelemetry("batch", benchmarkBatch));
1186
1200
 
1187
1201
  // โ”€โ”€ benchmark quantization โ”€โ”€
1188
1202
  bench
@@ -1198,7 +1212,7 @@ function registerBenchmark(program) {
1198
1212
  .option('--json', 'Machine-readable JSON output')
1199
1213
  .option('-q, --quiet', 'Suppress non-essential output')
1200
1214
  .option('-s, --save [path]', 'Save results to JSON file')
1201
- .action(benchmarkQuantization);
1215
+ .action(withTelemetry("quantization", benchmarkQuantization));
1202
1216
 
1203
1217
  // โ”€โ”€ benchmark asymmetric โ”€โ”€
1204
1218
  bench
@@ -1211,7 +1225,7 @@ function registerBenchmark(program) {
1211
1225
  .option('-k, --top-k <n>', 'Show top K results', '5')
1212
1226
  .option('--json', 'Machine-readable JSON output')
1213
1227
  .option('-q, --quiet', 'Suppress non-essential output')
1214
- .action(benchmarkAsymmetric);
1228
+ .action(withTelemetry("asymmetric", benchmarkAsymmetric));
1215
1229
 
1216
1230
  // โ”€โ”€ benchmark space โ”€โ”€
1217
1231
  bench
@@ -1223,7 +1237,7 @@ function registerBenchmark(program) {
1223
1237
  .option('-d, --dimensions <n>', 'Output dimensions (must be supported by all models)')
1224
1238
  .option('--json', 'Machine-readable JSON output')
1225
1239
  .option('-q, --quiet', 'Suppress non-essential output')
1226
- .action(benchmarkSpace);
1240
+ .action(withTelemetry("space", benchmarkSpace));
1227
1241
  }
1228
1242
 
1229
1243
  /**
@@ -223,6 +223,11 @@ async function runChat(opts) {
223
223
  }
224
224
  }
225
225
 
226
+ // Telemetry: track session
227
+ const telemetry = require('../lib/telemetry');
228
+ const chatStartTime = Date.now();
229
+ let turnCount = 0;
230
+
226
231
  // Track tool calls from last agent response (for /tools and /export-workflow)
227
232
  let lastToolCalls = [];
228
233
 
@@ -276,6 +281,7 @@ async function runChat(opts) {
276
281
  }
277
282
 
278
283
  // Execute chat turn
284
+ turnCount++;
279
285
  try {
280
286
  if (isAgent) {
281
287
  lastToolCalls = await handleAgentTurn(input, {
@@ -296,13 +302,25 @@ async function runChat(opts) {
296
302
  rl.prompt();
297
303
  });
298
304
 
305
+ function sendChatTelemetry() {
306
+ telemetry.send('cli_chat', {
307
+ provider: llmConfig.provider,
308
+ llmModel: llmConfig.model,
309
+ embeddingModel: proj.model || undefined,
310
+ turnCount,
311
+ durationMs: Date.now() - chatStartTime,
312
+ });
313
+ }
314
+
299
315
  rl.on('close', async () => {
316
+ sendChatTelemetry();
300
317
  await cleanup(historyMongo);
301
318
  process.exit(0);
302
319
  });
303
320
 
304
321
  // Handle Ctrl+C gracefully
305
322
  rl.on('SIGINT', async () => {
323
+ sendChatTelemetry();
306
324
  console.log('');
307
325
  await cleanup(historyMongo);
308
326
  process.exit(0);
@@ -51,6 +51,7 @@ function registerChunk(program) {
51
51
  .option('--json', 'Machine-readable JSON output')
52
52
  .option('-q, --quiet', 'Suppress non-essential output')
53
53
  .action(async (input, opts) => {
54
+ const telemetry = require('../lib/telemetry');
54
55
  try {
55
56
  // Load project config, merge with CLI opts
56
57
  const { config: projectConfig } = loadProject();
@@ -62,6 +63,12 @@ function registerChunk(program) {
62
63
  const minSize = opts.minSize || chunkConfig.minSize || DEFAULTS.minSize;
63
64
  const textField = opts.textField || 'text';
64
65
 
66
+ const done = telemetry.timer('cli_chunk', {
67
+ strategy,
68
+ chunkSize,
69
+ overlap,
70
+ });
71
+
65
72
  if (!STRATEGIES.includes(strategy)) {
66
73
  console.error(ui.error(`Unknown strategy: "${strategy}". Available: ${STRATEGIES.join(', ')}`));
67
74
  process.exit(1);
@@ -227,7 +234,10 @@ function registerChunk(program) {
227
234
  console.log(ui.label('Est. cost', ui.dim(`~$${cost < 0.01 ? cost.toFixed(4) : cost.toFixed(2)} with voyage-4-large`)));
228
235
  }
229
236
  }
237
+
238
+ done({ chunkCount: allChunks.length });
230
239
  } catch (err) {
240
+ telemetry.send('cli_error', { command: 'chunk', errorType: err.constructor.name });
231
241
  console.error(ui.error(err.message));
232
242
  process.exit(1);
233
243
  }
@@ -105,6 +105,8 @@ function registerDemo(program) {
105
105
  .option('--skip-pipeline', 'Skip the full pipeline step (Step 5)')
106
106
  .option('--keep', 'Keep the demo collection after pipeline step')
107
107
  .action(async (opts) => {
108
+ const telemetry = require('../lib/telemetry');
109
+ const demoStart = Date.now();
108
110
  const noPause = !opts.pause;
109
111
 
110
112
  // โ”€โ”€ Preflight: check API key โ”€โ”€
@@ -425,6 +427,8 @@ function registerDemo(program) {
425
427
  console.log('');
426
428
  console.log(' Happy searching! ๐Ÿš€');
427
429
  console.log('');
430
+
431
+ telemetry.send('cli_demo', { durationMs: Date.now() - demoStart });
428
432
  });
429
433
  }
430
434
 
@@ -4,6 +4,7 @@ const { getDefaultModel } = require('../lib/catalog');
4
4
  const { generateEmbeddings } = require('../lib/api');
5
5
  const { resolveTextInput } = require('../lib/input');
6
6
  const ui = require('../lib/ui');
7
+ const { showCostSummary } = require('../lib/cost-display');
7
8
 
8
9
  /**
9
10
  * Register the embed command on a Commander program.
@@ -26,6 +27,7 @@ function registerEmbed(program) {
26
27
  .option('-q, --quiet', 'Suppress non-essential output')
27
28
  .action(async (text, opts) => {
28
29
  try {
30
+ const telemetry = require('../lib/telemetry');
29
31
  const texts = await resolveTextInput(text, opts.file);
30
32
 
31
33
  // --estimate: show cost comparison, optionally switch model
@@ -45,6 +47,13 @@ function registerEmbed(program) {
45
47
  console.error(ui.dim('โ„น Tip: Use --input-type query or --input-type document for better retrieval accuracy.'));
46
48
  }
47
49
 
50
+ const done = telemetry.timer('cli_embed', {
51
+ model: opts.model,
52
+ inputType: opts.inputType || undefined,
53
+ textCount: texts.length,
54
+ outputDtype: opts.outputDtype,
55
+ });
56
+
48
57
  let spin;
49
58
  if (useSpinner) {
50
59
  spin = ui.spinner('Generating embeddings...');
@@ -91,6 +100,7 @@ function registerEmbed(program) {
91
100
  console.log(ui.label('Tokens', ui.dim(String(result.usage.total_tokens))));
92
101
  }
93
102
  console.log(ui.label('Dimensions', ui.bold(String(result.data[0]?.embedding?.length || 'N/A'))));
103
+ showCostSummary(result.model || model, result.usage?.total_tokens || 0, opts);
94
104
  console.log('');
95
105
  }
96
106
 
@@ -101,7 +111,10 @@ function registerEmbed(program) {
101
111
 
102
112
  console.log('');
103
113
  console.log(ui.success('Embeddings generated'));
114
+
115
+ done({ dimensions: result.data[0]?.embedding?.length });
104
116
  } catch (err) {
117
+ telemetry.send('cli_error', { command: 'embed', errorType: err.constructor.name });
105
118
  console.error(ui.error(err.message));
106
119
  process.exit(1);
107
120
  }
@@ -67,6 +67,7 @@ function registerEstimate(program) {
67
67
  .option('--json', 'Machine-readable JSON output')
68
68
  .option('-q, --quiet', 'Suppress non-essential output')
69
69
  .action((opts) => {
70
+ const telemetry = require('../lib/telemetry');
70
71
  const numDocs = parseShorthand(opts.docs);
71
72
  const numQueries = parseShorthand(opts.queries);
72
73
  const docTokens = parseInt(opts.docTokens, 10) || DEFAULT_DOC_TOKENS;
@@ -92,6 +93,8 @@ function registerEstimate(program) {
92
93
  process.exit(1);
93
94
  }
94
95
 
96
+ telemetry.send('cli_estimate', { model: opts.docModel, tokenCount: numDocs * docTokens });
97
+
95
98
  const docTotalTokens = numDocs * docTokens;
96
99
  const queryTotalTokensPerMonth = numQueries * queryTokens;
97
100
 
@@ -192,9 +192,13 @@ function registerEval(program) {
192
192
  .option('--json', 'Machine-readable JSON output')
193
193
  .option('-q, --quiet', 'Suppress non-essential output')
194
194
  .action(async (opts) => {
195
+ const telemetry = require('../lib/telemetry');
196
+ const done = telemetry.timer('cli_eval');
197
+
195
198
  // Dispatch to rerank eval mode
196
199
  if (opts.mode === 'rerank') {
197
200
  await evalRerank(opts);
201
+ done();
198
202
  return;
199
203
  }
200
204
 
@@ -438,7 +442,9 @@ function registerEval(program) {
438
442
  console.log(ui.dim(' ๐Ÿ’ก Low recall? Try: increasing --limit, different chunking strategy, or review your test set'));
439
443
  }
440
444
  console.log(ui.dim(' ๐Ÿ’ก Evaluate reranking quality: vai eval --mode rerank --test-set rerank-test.jsonl'));
445
+ done();
441
446
  } catch (err) {
447
+ telemetry.send('cli_error', { command: 'eval', errorType: err.constructor.name });
442
448
  console.error(ui.error(err.message));
443
449
  process.exit(1);
444
450
  } finally {
@@ -127,6 +127,8 @@ function registerExplain(program) {
127
127
  .description('Learn about embeddings, reranking, vector search, and more')
128
128
  .option('--json', 'Output in JSON format')
129
129
  .action((concept, opts) => {
130
+ const telemetry = require('../lib/telemetry');
131
+ telemetry.send('cli_explain', { topic: concept || 'list' });
130
132
  if (!concept) {
131
133
  // Show topic list
132
134
  if (opts.json) {
@@ -123,7 +123,9 @@ function registerGenerate(program) {
123
123
  .option('-l, --list', 'List available components')
124
124
  .option('-q, --quiet', 'Suppress hints and metadata')
125
125
  .action(async (component, opts) => {
126
+ const telemetry = require('../lib/telemetry');
126
127
  try {
128
+ telemetry.send('cli_generate', { provider: opts.target, model: opts.model });
127
129
  // Determine target
128
130
  const target = opts.target || detectTarget();
129
131
 
@@ -208,6 +208,8 @@ function registerIngest(program) {
208
208
  .option('--json', 'Machine-readable JSON output')
209
209
  .option('-q, --quiet', 'Suppress progress, show only final summary')
210
210
  .action(async (opts) => {
211
+ const telemetry = require('../lib/telemetry');
212
+ const done = telemetry.timer('cli_ingest');
211
213
  const startTime = Date.now();
212
214
 
213
215
  // Validate file exists
@@ -394,7 +396,9 @@ function registerIngest(program) {
394
396
  }
395
397
  }
396
398
  }
399
+ done({ format, docCount: succeeded });
397
400
  } catch (err) {
401
+ telemetry.send('cli_error', { command: 'ingest', errorType: err.constructor.name });
398
402
  console.error(ui.error(err.message));
399
403
  process.exit(1);
400
404
  } finally {
@@ -21,6 +21,8 @@ function registerInit(program) {
21
21
  .option('--json', 'Output created config as JSON (non-interactive)')
22
22
  .option('-q, --quiet', 'Suppress non-essential output')
23
23
  .action(async (opts) => {
24
+ const telemetry = require('../lib/telemetry');
25
+ telemetry.send('cli_init');
24
26
  // Check for existing config
25
27
  const existing = findProjectFile();
26
28
  if (existing && !opts.force) {
@@ -17,6 +17,8 @@ function registerMcpServer(program) {
17
17
  .option('--no-sse', 'Disable SSE transport (SSE is enabled by default for HTTP)')
18
18
  .option('--verbose', 'Enable debug logging to stderr')
19
19
  .action(async (opts) => {
20
+ const telemetry = require('../lib/telemetry');
21
+ telemetry.send('cli_mcp_start', { transport: opts.transport });
20
22
  if (opts.verbose) {
21
23
  process.env.VAI_MCP_VERBOSE = '1';
22
24
  }
@@ -46,6 +46,8 @@ function registerModels(program) {
46
46
  .option('--json', 'Machine-readable JSON output')
47
47
  .option('-q, --quiet', 'Suppress non-essential output')
48
48
  .action((opts) => {
49
+ const telemetry = require('../lib/telemetry');
50
+ telemetry.send('cli_models', { category: opts.type });
49
51
  let models = MODEL_CATALOG;
50
52
 
51
53
  // Separate current and legacy models
@@ -15,6 +15,7 @@ function registerPing(program) {
15
15
  .option('-q, --quiet', 'Suppress non-essential output')
16
16
  .option('--mask', 'Mask sensitive info (cluster hostnames, endpoints) in output. Also enabled by VAI_MASK=1 env var.')
17
17
  .action(async (opts) => {
18
+ const telemetry = require('../lib/telemetry');
18
19
  // Support env var so all recordings are masked without remembering the flag
19
20
  if (process.env.VAI_MASK === '1' || process.env.VAI_MASK === 'true') {
20
21
  opts.mask = true;
@@ -242,6 +243,12 @@ function registerPing(program) {
242
243
  }
243
244
  }
244
245
 
246
+ // Telemetry
247
+ telemetry.send('cli_ping', {
248
+ voyageOk: !!results.voyage?.ok,
249
+ mongoOk: !!results.mongodb?.ok,
250
+ });
251
+
245
252
  // Emit JSON at the end with all results
246
253
  if (opts.json) {
247
254
  console.log(JSON.stringify({ ok: true, ...results }, null, 2));
@@ -67,6 +67,7 @@ function registerPipeline(program) {
67
67
  .option('-q, --quiet', 'Suppress non-essential output')
68
68
  .action(async (input, opts) => {
69
69
  let client;
70
+ const telemetry = require('../lib/telemetry');
70
71
  try {
71
72
  // Merge project config
72
73
  const { config: proj } = loadProject();
@@ -89,6 +90,13 @@ function registerPipeline(program) {
89
90
  process.exit(1);
90
91
  }
91
92
 
93
+ const done = telemetry.timer('cli_pipeline', {
94
+ model,
95
+ chunkStrategy: strategy,
96
+ chunkSize,
97
+ createIndex: !!opts.createIndex,
98
+ });
99
+
92
100
  if (!STRATEGIES.includes(strategy)) {
93
101
  console.error(ui.error(`Unknown strategy: "${strategy}". Available: ${STRATEGIES.join(', ')}`));
94
102
  process.exit(1);
@@ -313,7 +321,14 @@ function registerPipeline(program) {
313
321
  console.log('');
314
322
  console.log(ui.dim(' Next: vai query "your search" --db ' + db + ' --collection ' + collection));
315
323
  }
324
+
325
+ done({
326
+ fileCount: files.length,
327
+ chunkCount: allChunks.length,
328
+ docCount: insertResult.insertedCount,
329
+ });
316
330
  } catch (err) {
331
+ telemetry.send('cli_error', { command: 'pipeline', errorType: err.constructor.name });
317
332
  console.error(ui.error(err.message));
318
333
  process.exit(1);
319
334
  } finally {