voyageai-cli 1.4.0 → 1.6.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 CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  CLI for [Voyage AI](https://www.mongodb.com/docs/voyageai/) embeddings, reranking, and [MongoDB Atlas Vector Search](https://www.mongodb.com/docs/atlas/atlas-vector-search/). Pure Node.js — no Python required.
6
6
 
7
+ <!-- TODO: Add demo GIF -->
8
+ <!-- ![vai demo](demo.gif) -->
9
+
7
10
  Generate embeddings, rerank search results, store vectors in Atlas, and run semantic search — all from the command line.
8
11
 
9
12
  > **⚠️ Disclaimer:** This is an independent, community-built tool. It is **not** an official product of MongoDB, Inc. or Voyage AI. It is not supported, endorsed, or maintained by either company. For official documentation, support, and products, visit:
@@ -248,6 +251,41 @@ vai config get
248
251
  - Use `echo "key" | vai config set api-key --stdin` or `vai config set api-key --stdin < keyfile` to avoid shell history exposure
249
252
  - The config file stores credentials in plaintext (similar to `~/.aws/credentials` and `~/.npmrc`) — protect your home directory accordingly
250
253
 
254
+ ## Shell Completions
255
+
256
+ `vai` supports tab completion for bash and zsh.
257
+
258
+ ### Bash
259
+
260
+ ```bash
261
+ # Add to ~/.bashrc (or ~/.bash_profile on macOS)
262
+ vai completions bash >> ~/.bashrc
263
+ source ~/.bashrc
264
+
265
+ # Or install system-wide (Linux)
266
+ vai completions bash > /etc/bash_completion.d/vai
267
+
268
+ # Or with Homebrew (macOS)
269
+ vai completions bash > $(brew --prefix)/etc/bash_completion.d/vai
270
+ ```
271
+
272
+ ### Zsh
273
+
274
+ ```bash
275
+ # Create completions directory
276
+ mkdir -p ~/.zsh/completions
277
+
278
+ # Add to fpath in ~/.zshrc (if not already there)
279
+ echo 'fpath=(~/.zsh/completions $fpath)' >> ~/.zshrc
280
+ echo 'autoload -Uz compinit && compinit' >> ~/.zshrc
281
+
282
+ # Generate the completion file
283
+ vai completions zsh > ~/.zsh/completions/_vai
284
+ source ~/.zshrc
285
+ ```
286
+
287
+ Completions cover all 14 commands, subcommands, flags, model names, and explain topics.
288
+
251
289
  ## Global Flags
252
290
 
253
291
  All commands support:
package/demo.gif ADDED
Binary file
package/demo.tape ADDED
@@ -0,0 +1,39 @@
1
+ # VHS demo tape for voyageai-cli
2
+ # Run: vhs demo.tape
3
+ # Requires: VOYAGE_API_KEY set in environment
4
+
5
+ Output demo.gif
6
+
7
+ Set FontSize 16
8
+ Set Width 900
9
+ Set Height 600
10
+ Set Theme "Catppuccin Mocha"
11
+ Set Padding 20
12
+
13
+ # Show version
14
+ Type "vai --version"
15
+ Enter
16
+ Sleep 1.5s
17
+
18
+ # List embedding models
19
+ Type "vai models --type embedding"
20
+ Enter
21
+ Sleep 3s
22
+
23
+ # Generate an embedding
24
+ Type 'vai embed "What is MongoDB Atlas?"'
25
+ Enter
26
+ Sleep 4s
27
+
28
+ # Explain embeddings (first few lines)
29
+ Type "vai explain embeddings"
30
+ Enter
31
+ Sleep 4s
32
+
33
+ # Compare similarity
34
+ Type 'vai similarity "MongoDB is great" "MongoDB Atlas is amazing"'
35
+ Enter
36
+ Sleep 4s
37
+
38
+ # Pause at end to show results
39
+ Sleep 2s
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "voyageai-cli",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search",
5
5
  "bin": {
6
6
  "vai": "./src/cli.js"
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # Record a demo GIF for voyageai-cli
3
+ # Requires: VOYAGE_API_KEY environment variable
4
+ #
5
+ # Usage:
6
+ # ./scripts/record-demo.sh # Uses vhs (preferred)
7
+ # ./scripts/record-demo.sh asciinema # Uses asciinema instead
8
+ #
9
+ # Output:
10
+ # demo.gif (vhs) or demo.cast (asciinema)
11
+
12
+ set -euo pipefail
13
+ cd "$(dirname "$0")/.."
14
+
15
+ METHOD="${1:-vhs}"
16
+
17
+ if [ "$METHOD" = "vhs" ]; then
18
+ if ! command -v vhs &>/dev/null; then
19
+ echo "❌ vhs not found. Install: brew install charmbracelet/tap/vhs"
20
+ echo " Or run: ./scripts/record-demo.sh asciinema"
21
+ exit 1
22
+ fi
23
+
24
+ if [ -z "${VOYAGE_API_KEY:-}" ]; then
25
+ echo "⚠️ VOYAGE_API_KEY not set. Commands that call the API will fail."
26
+ echo " Set it: export VOYAGE_API_KEY=your-key"
27
+ exit 1
28
+ fi
29
+
30
+ echo "🎬 Recording demo with vhs..."
31
+ vhs demo.tape
32
+ echo "✅ Demo GIF saved to demo.gif"
33
+
34
+ elif [ "$METHOD" = "asciinema" ]; then
35
+ if ! command -v asciinema &>/dev/null; then
36
+ echo "❌ asciinema not found. Install: brew install asciinema"
37
+ exit 1
38
+ fi
39
+
40
+ CAST_FILE="demo.cast"
41
+ echo "🎬 Recording demo with asciinema..."
42
+ echo " Run the following commands, then press Ctrl-D when done:"
43
+ echo ""
44
+ echo " vai --version"
45
+ echo " vai models --type embedding"
46
+ echo ' vai embed "What is MongoDB Atlas?"'
47
+ echo " vai explain embeddings"
48
+ echo ' vai similarity "MongoDB is great" "MongoDB Atlas is amazing"'
49
+ echo ""
50
+
51
+ asciinema rec "$CAST_FILE"
52
+ echo "✅ Recording saved to $CAST_FILE"
53
+ echo ""
54
+ echo "Convert to GIF with agg or svg-term-cli:"
55
+ echo " agg $CAST_FILE demo.gif"
56
+ echo " # or"
57
+ echo " npx svg-term-cli --in $CAST_FILE --out demo.svg --window"
58
+
59
+ else
60
+ echo "Unknown method: $METHOD"
61
+ echo "Usage: $0 [vhs|asciinema]"
62
+ exit 1
63
+ fi
package/src/cli.js CHANGED
@@ -17,6 +17,7 @@ const { registerDemo } = require('./commands/demo');
17
17
  const { registerExplain } = require('./commands/explain');
18
18
  const { registerSimilarity } = require('./commands/similarity');
19
19
  const { registerIngest } = require('./commands/ingest');
20
+ const { registerCompletions } = require('./commands/completions');
20
21
  const { showBanner, showQuickStart, getVersion } = require('./lib/banner');
21
22
 
22
23
  const version = getVersion();
@@ -38,6 +39,7 @@ registerDemo(program);
38
39
  registerExplain(program);
39
40
  registerSimilarity(program);
40
41
  registerIngest(program);
42
+ registerCompletions(program);
41
43
 
42
44
  // Append disclaimer to all help output
43
45
  program.addHelpText('after', `
@@ -0,0 +1,463 @@
1
+ 'use strict';
2
+
3
+ const pc = require('picocolors');
4
+
5
+ /**
6
+ * Generate bash completion script for vai CLI.
7
+ * @returns {string}
8
+ */
9
+ function generateBashCompletions() {
10
+ return `#!/bin/bash
11
+ # vai bash completion script
12
+ # Install: vai completions bash >> ~/.bashrc && source ~/.bashrc
13
+ # Or: vai completions bash > /usr/local/etc/bash_completion.d/vai
14
+
15
+ _vai_completions() {
16
+ local cur prev commands
17
+ COMPREPLY=()
18
+ cur="\${COMP_WORDS[COMP_CWORD]}"
19
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
20
+
21
+ # Top-level commands
22
+ commands="embed rerank store search index models ping config demo explain similarity ingest completions help"
23
+
24
+ # Subcommands
25
+ local index_subs="create list delete"
26
+ local config_subs="set get delete path reset"
27
+
28
+ # Global flags
29
+ local global_flags="--json --quiet --help --version"
30
+
31
+ case "\${COMP_WORDS[1]}" in
32
+ embed)
33
+ COMPREPLY=( \$(compgen -W "--model --input-type --dimensions --file --output-format --json --quiet --help" -- "\$cur") )
34
+ return 0
35
+ ;;
36
+ rerank)
37
+ COMPREPLY=( \$(compgen -W "--query --documents --documents-file --model --top-k --json --quiet --help" -- "\$cur") )
38
+ return 0
39
+ ;;
40
+ store)
41
+ COMPREPLY=( \$(compgen -W "--db --collection --field --text --file --model --input-type --dimensions --metadata --json --quiet --help" -- "\$cur") )
42
+ return 0
43
+ ;;
44
+ search)
45
+ COMPREPLY=( \$(compgen -W "--query --db --collection --index --field --model --input-type --dimensions --limit --min-score --num-candidates --filter --json --quiet --help" -- "\$cur") )
46
+ return 0
47
+ ;;
48
+ index)
49
+ if [[ \$COMP_CWORD -eq 2 ]]; then
50
+ COMPREPLY=( \$(compgen -W "\$index_subs" -- "\$cur") )
51
+ else
52
+ case "\${COMP_WORDS[2]}" in
53
+ create)
54
+ COMPREPLY=( \$(compgen -W "--db --collection --field --dimensions --similarity --index-name --json --quiet --help" -- "\$cur") )
55
+ ;;
56
+ list)
57
+ COMPREPLY=( \$(compgen -W "--db --collection --json --quiet --help" -- "\$cur") )
58
+ ;;
59
+ delete)
60
+ COMPREPLY=( \$(compgen -W "--db --collection --index-name --json --quiet --help" -- "\$cur") )
61
+ ;;
62
+ esac
63
+ fi
64
+ return 0
65
+ ;;
66
+ models)
67
+ COMPREPLY=( \$(compgen -W "--type --json --quiet --help" -- "\$cur") )
68
+ return 0
69
+ ;;
70
+ ping)
71
+ COMPREPLY=( \$(compgen -W "--json --quiet --help" -- "\$cur") )
72
+ return 0
73
+ ;;
74
+ config)
75
+ if [[ \$COMP_CWORD -eq 2 ]]; then
76
+ COMPREPLY=( \$(compgen -W "\$config_subs" -- "\$cur") )
77
+ else
78
+ case "\${COMP_WORDS[2]}" in
79
+ set)
80
+ COMPREPLY=( \$(compgen -W "api-key mongodb-uri base-url default-model --stdin --help" -- "\$cur") )
81
+ ;;
82
+ get|delete)
83
+ COMPREPLY=( \$(compgen -W "api-key mongodb-uri base-url default-model --help" -- "\$cur") )
84
+ ;;
85
+ esac
86
+ fi
87
+ return 0
88
+ ;;
89
+ demo)
90
+ COMPREPLY=( \$(compgen -W "--no-pause --skip-pipeline --keep --json --quiet --help" -- "\$cur") )
91
+ return 0
92
+ ;;
93
+ explain)
94
+ COMPREPLY=( \$(compgen -W "embeddings reranking vector-search rag cosine-similarity two-stage-retrieval input-type models api-keys api-access batch-processing --help" -- "\$cur") )
95
+ return 0
96
+ ;;
97
+ similarity)
98
+ COMPREPLY=( \$(compgen -W "--against --file1 --file2 --model --dimensions --json --quiet --help" -- "\$cur") )
99
+ return 0
100
+ ;;
101
+ ingest)
102
+ COMPREPLY=( \$(compgen -W "--file --db --collection --field --model --input-type --dimensions --batch-size --text-field --text-column --strict --dry-run --json --quiet --help" -- "\$cur") )
103
+ return 0
104
+ ;;
105
+ completions)
106
+ COMPREPLY=( \$(compgen -W "bash zsh --help" -- "\$cur") )
107
+ return 0
108
+ ;;
109
+ esac
110
+
111
+ # Complete top-level commands and global flags
112
+ if [[ \$COMP_CWORD -eq 1 ]]; then
113
+ COMPREPLY=( \$(compgen -W "\$commands \$global_flags" -- "\$cur") )
114
+ return 0
115
+ fi
116
+
117
+ # Model name completions
118
+ case "\$prev" in
119
+ --model|-m)
120
+ COMPREPLY=( \$(compgen -W "voyage-4-large voyage-4 voyage-4-lite voyage-code-3 voyage-finance-2 voyage-law-2 voyage-multimodal-3.5 rerank-2.5 rerank-2.5-lite" -- "\$cur") )
121
+ return 0
122
+ ;;
123
+ --input-type)
124
+ COMPREPLY=( \$(compgen -W "query document" -- "\$cur") )
125
+ return 0
126
+ ;;
127
+ --type|-t)
128
+ COMPREPLY=( \$(compgen -W "embedding reranking all" -- "\$cur") )
129
+ return 0
130
+ ;;
131
+ --similarity|-s)
132
+ COMPREPLY=( \$(compgen -W "cosine dotProduct euclidean" -- "\$cur") )
133
+ return 0
134
+ ;;
135
+ --output-format|-o)
136
+ COMPREPLY=( \$(compgen -W "json array" -- "\$cur") )
137
+ return 0
138
+ ;;
139
+ --file|-f|--documents-file|--file1|--file2)
140
+ COMPREPLY=( \$(compgen -f -- "\$cur") )
141
+ return 0
142
+ ;;
143
+ esac
144
+ }
145
+
146
+ complete -F _vai_completions vai
147
+ `;
148
+ }
149
+
150
+ /**
151
+ * Generate zsh completion script for vai CLI.
152
+ * @returns {string}
153
+ */
154
+ function generateZshCompletions() {
155
+ return `#compdef vai
156
+ # vai zsh completion script
157
+ # Install: vai completions zsh > ~/.zsh/completions/_vai && source ~/.zshrc
158
+ # Or: vai completions zsh > /usr/local/share/zsh/site-functions/_vai
159
+
160
+ _vai() {
161
+ local -a commands
162
+ commands=(
163
+ 'embed:Generate embeddings for text'
164
+ 'rerank:Rerank documents against a query'
165
+ 'store:Embed text and store in MongoDB Atlas'
166
+ 'search:Vector search against Atlas collection'
167
+ 'index:Manage Atlas Vector Search indexes'
168
+ 'models:List available Voyage AI models'
169
+ 'ping:Test connectivity to Voyage AI API'
170
+ 'config:Manage persistent configuration'
171
+ 'demo:Interactive guided walkthrough'
172
+ 'explain:Learn about AI and vector search concepts'
173
+ 'similarity:Compute cosine similarity between texts'
174
+ 'ingest:Bulk import documents with progress'
175
+ 'completions:Generate shell completion scripts'
176
+ 'help:Display help for command'
177
+ )
178
+
179
+ local -a models
180
+ models=(voyage-4-large voyage-4 voyage-4-lite voyage-code-3 voyage-finance-2 voyage-law-2 voyage-multimodal-3.5 rerank-2.5 rerank-2.5-lite)
181
+
182
+ local -a explain_topics
183
+ explain_topics=(embeddings reranking vector-search rag cosine-similarity two-stage-retrieval input-type models api-keys api-access batch-processing)
184
+
185
+ _arguments -C \\
186
+ '(-V --version)'{-V,--version}'[output the version number]' \\
187
+ '(-h --help)'{-h,--help}'[display help]' \\
188
+ '1:command:->command' \\
189
+ '*::arg:->args'
190
+
191
+ case \$state in
192
+ command)
193
+ _describe 'vai command' commands
194
+ ;;
195
+ args)
196
+ case \$words[1] in
197
+ embed)
198
+ _arguments \\
199
+ '(-m --model)'{-m,--model}'[Embedding model]:model:(\$models)' \\
200
+ '(-t --input-type)'{-t,--input-type}'[Input type]:type:(query document)' \\
201
+ '(-d --dimensions)'{-d,--dimensions}'[Output dimensions]:dimensions:' \\
202
+ '(-f --file)'{-f,--file}'[Read text from file]:file:_files' \\
203
+ '(-o --output-format)'{-o,--output-format}'[Output format]:format:(json array)' \\
204
+ '--json[Machine-readable JSON output]' \\
205
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]' \\
206
+ '1:text:'
207
+ ;;
208
+ rerank)
209
+ _arguments \\
210
+ '--query[Search query]:query:' \\
211
+ '--documents[Documents to rerank]:document:' \\
212
+ '--documents-file[File with documents]:file:_files' \\
213
+ '(-m --model)'{-m,--model}'[Reranking model]:model:(\$models)' \\
214
+ '(-k --top-k)'{-k,--top-k}'[Return top K results]:k:' \\
215
+ '--json[Machine-readable JSON output]' \\
216
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
217
+ ;;
218
+ store)
219
+ _arguments \\
220
+ '--db[Database name]:database:' \\
221
+ '--collection[Collection name]:collection:' \\
222
+ '--field[Embedding field name]:field:' \\
223
+ '--text[Text to embed and store]:text:' \\
224
+ '(-f --file)'{-f,--file}'[File to embed and store]:file:_files' \\
225
+ '(-m --model)'{-m,--model}'[Embedding model]:model:(\$models)' \\
226
+ '--input-type[Input type]:type:(query document)' \\
227
+ '(-d --dimensions)'{-d,--dimensions}'[Output dimensions]:dimensions:' \\
228
+ '--metadata[Additional metadata as JSON]:json:' \\
229
+ '--json[Machine-readable JSON output]' \\
230
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
231
+ ;;
232
+ search)
233
+ _arguments \\
234
+ '--query[Search query text]:query:' \\
235
+ '--db[Database name]:database:' \\
236
+ '--collection[Collection name]:collection:' \\
237
+ '--index[Vector search index name]:index:' \\
238
+ '--field[Embedding field name]:field:' \\
239
+ '(-m --model)'{-m,--model}'[Embedding model]:model:(\$models)' \\
240
+ '--input-type[Input type]:type:(query document)' \\
241
+ '(-d --dimensions)'{-d,--dimensions}'[Output dimensions]:dimensions:' \\
242
+ '(-l --limit)'{-l,--limit}'[Maximum results]:limit:' \\
243
+ '--min-score[Minimum similarity score]:score:' \\
244
+ '--num-candidates[Number of ANN candidates]:n:' \\
245
+ '--filter[Pre-filter JSON]:json:' \\
246
+ '--json[Machine-readable JSON output]' \\
247
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
248
+ ;;
249
+ index)
250
+ local -a index_commands
251
+ index_commands=(
252
+ 'create:Create a vector search index'
253
+ 'list:List vector search indexes'
254
+ 'delete:Delete a vector search index'
255
+ )
256
+ _arguments -C \\
257
+ '1:index command:->index_command' \\
258
+ '*::arg:->index_args'
259
+ case \$state in
260
+ index_command)
261
+ _describe 'index command' index_commands
262
+ ;;
263
+ index_args)
264
+ case \$words[1] in
265
+ create)
266
+ _arguments \\
267
+ '--db[Database name]:database:' \\
268
+ '--collection[Collection name]:collection:' \\
269
+ '--field[Embedding field name]:field:' \\
270
+ '(-d --dimensions)'{-d,--dimensions}'[Vector dimensions]:dimensions:' \\
271
+ '(-s --similarity)'{-s,--similarity}'[Similarity function]:similarity:(cosine dotProduct euclidean)' \\
272
+ '(-n --index-name)'{-n,--index-name}'[Index name]:name:' \\
273
+ '--json[Machine-readable JSON output]' \\
274
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
275
+ ;;
276
+ list)
277
+ _arguments \\
278
+ '--db[Database name]:database:' \\
279
+ '--collection[Collection name]:collection:' \\
280
+ '--json[Machine-readable JSON output]' \\
281
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
282
+ ;;
283
+ delete)
284
+ _arguments \\
285
+ '--db[Database name]:database:' \\
286
+ '--collection[Collection name]:collection:' \\
287
+ '--index-name[Index name]:name:' \\
288
+ '--json[Machine-readable JSON output]' \\
289
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
290
+ ;;
291
+ esac
292
+ ;;
293
+ esac
294
+ ;;
295
+ models)
296
+ _arguments \\
297
+ '(-t --type)'{-t,--type}'[Filter by type]:type:(embedding reranking all)' \\
298
+ '--json[Machine-readable JSON output]' \\
299
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
300
+ ;;
301
+ ping)
302
+ _arguments \\
303
+ '--json[Machine-readable JSON output]' \\
304
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
305
+ ;;
306
+ config)
307
+ local -a config_commands
308
+ config_commands=(
309
+ 'set:Set a config value'
310
+ 'get:Show current configuration'
311
+ 'delete:Remove a config value'
312
+ 'path:Show config file path'
313
+ 'reset:Reset all configuration'
314
+ )
315
+ _arguments -C \\
316
+ '1:config command:->config_command' \\
317
+ '*::arg:->config_args'
318
+ case \$state in
319
+ config_command)
320
+ _describe 'config command' config_commands
321
+ ;;
322
+ config_args)
323
+ case \$words[1] in
324
+ set)
325
+ _arguments \\
326
+ '1:key:(api-key mongodb-uri base-url default-model)' \\
327
+ '2:value:' \\
328
+ '--stdin[Read value from stdin]'
329
+ ;;
330
+ delete)
331
+ _arguments \\
332
+ '1:key:(api-key mongodb-uri base-url default-model)'
333
+ ;;
334
+ esac
335
+ ;;
336
+ esac
337
+ ;;
338
+ demo)
339
+ _arguments \\
340
+ '--no-pause[Skip pauses between steps]' \\
341
+ '--skip-pipeline[Skip MongoDB pipeline steps]' \\
342
+ '--keep[Keep demo data after completion]' \\
343
+ '--json[Machine-readable JSON output]' \\
344
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
345
+ ;;
346
+ explain)
347
+ _arguments \\
348
+ '1:topic:(\$explain_topics)'
349
+ ;;
350
+ similarity)
351
+ _arguments \\
352
+ '--against[Compare against multiple texts]:text:' \\
353
+ '--file1[Read text A from file]:file:_files' \\
354
+ '--file2[Read text B from file]:file:_files' \\
355
+ '(-m --model)'{-m,--model}'[Embedding model]:model:(\$models)' \\
356
+ '--dimensions[Output dimensions]:dimensions:' \\
357
+ '--json[Machine-readable JSON output]' \\
358
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]' \\
359
+ '*:text:'
360
+ ;;
361
+ ingest)
362
+ _arguments \\
363
+ '--file[Input file]:file:_files' \\
364
+ '--db[Database name]:database:' \\
365
+ '--collection[Collection name]:collection:' \\
366
+ '--field[Embedding field name]:field:' \\
367
+ '(-m --model)'{-m,--model}'[Embedding model]:model:(\$models)' \\
368
+ '--input-type[Input type]:type:(query document)' \\
369
+ '(-d --dimensions)'{-d,--dimensions}'[Output dimensions]:dimensions:' \\
370
+ '--batch-size[Documents per batch]:size:' \\
371
+ '--text-field[JSON field containing text]:field:' \\
372
+ '--text-column[CSV column to embed]:column:' \\
373
+ '--strict[Abort on first batch error]' \\
374
+ '--dry-run[Validate only, no API calls]' \\
375
+ '--json[Machine-readable JSON output]' \\
376
+ '(-q --quiet)'{-q,--quiet}'[Suppress non-essential output]'
377
+ ;;
378
+ completions)
379
+ _arguments \\
380
+ '1:shell:(bash zsh)'
381
+ ;;
382
+ esac
383
+ ;;
384
+ esac
385
+ }
386
+
387
+ _vai "\$@"
388
+ `;
389
+ }
390
+
391
+ /**
392
+ * Show installation instructions for shell completions.
393
+ * @param {string} shell - 'bash' or 'zsh'
394
+ */
395
+ function showInstallInstructions(shell) {
396
+ console.log('');
397
+ if (shell === 'bash') {
398
+ console.log(` ${pc.bold('Install bash completions:')}`);
399
+ console.log('');
400
+ console.log(` ${pc.cyan('# Add to your ~/.bashrc (or ~/.bash_profile on macOS)')}`);
401
+ console.log(` ${pc.white('vai completions bash >> ~/.bashrc')}`);
402
+ console.log(` ${pc.white('source ~/.bashrc')}`);
403
+ console.log('');
404
+ console.log(` ${pc.cyan('# Or install system-wide (Linux)')}`);
405
+ console.log(` ${pc.white('vai completions bash > /etc/bash_completion.d/vai')}`);
406
+ console.log('');
407
+ console.log(` ${pc.cyan('# Or with Homebrew (macOS)')}`);
408
+ console.log(` ${pc.white('vai completions bash > $(brew --prefix)/etc/bash_completion.d/vai')}`);
409
+ } else {
410
+ console.log(` ${pc.bold('Install zsh completions:')}`);
411
+ console.log('');
412
+ console.log(` ${pc.cyan('# Create completions directory if needed')}`);
413
+ console.log(` ${pc.white('mkdir -p ~/.zsh/completions')}`);
414
+ console.log('');
415
+ console.log(` ${pc.cyan('# Add to fpath in your ~/.zshrc (if not already there)')}`);
416
+ console.log(` ${pc.white('echo \'fpath=(~/.zsh/completions $fpath)\' >> ~/.zshrc')}`);
417
+ console.log(` ${pc.white('echo \'autoload -Uz compinit && compinit\' >> ~/.zshrc')}`);
418
+ console.log('');
419
+ console.log(` ${pc.cyan('# Generate the completion file')}`);
420
+ console.log(` ${pc.white('vai completions zsh > ~/.zsh/completions/_vai')}`);
421
+ console.log(` ${pc.white('source ~/.zshrc')}`);
422
+ console.log('');
423
+ console.log(` ${pc.cyan('# Or install system-wide')}`);
424
+ console.log(` ${pc.white('vai completions zsh > /usr/local/share/zsh/site-functions/_vai')}`);
425
+ }
426
+ console.log('');
427
+ }
428
+
429
+ /**
430
+ * Register the completions command on a Commander program.
431
+ * @param {import('commander').Command} program
432
+ */
433
+ function registerCompletions(program) {
434
+ program
435
+ .command('completions [shell]')
436
+ .description('Generate shell completion scripts (bash or zsh)')
437
+ .action((shell) => {
438
+ if (!shell) {
439
+ console.log('');
440
+ console.log(` ${pc.bold('Usage:')} vai completions ${pc.cyan('<bash|zsh>')}`);
441
+ console.log('');
442
+ console.log(' Outputs a completion script for the specified shell.');
443
+ console.log(' Redirect the output to the appropriate file for your shell.');
444
+ console.log('');
445
+ showInstallInstructions('bash');
446
+ showInstallInstructions('zsh');
447
+ return;
448
+ }
449
+
450
+ const normalized = shell.toLowerCase().trim();
451
+
452
+ if (normalized === 'bash') {
453
+ process.stdout.write(generateBashCompletions());
454
+ } else if (normalized === 'zsh') {
455
+ process.stdout.write(generateZshCompletions());
456
+ } else {
457
+ console.error(` ${pc.red('✗')} Unknown shell: ${shell}. Supported: bash, zsh`);
458
+ process.exit(1);
459
+ }
460
+ });
461
+ }
462
+
463
+ module.exports = { registerCompletions, generateBashCompletions, generateZshCompletions };
@@ -17,6 +17,8 @@ function registerEmbed(program) {
17
17
  .option('-t, --input-type <type>', 'Input type: query or document')
18
18
  .option('-d, --dimensions <n>', 'Output dimensions', (v) => parseInt(v, 10))
19
19
  .option('-f, --file <path>', 'Read text from file')
20
+ .option('--truncation', 'Enable truncation for long inputs')
21
+ .option('--no-truncation', 'Disable truncation')
20
22
  .option('-o, --output-format <format>', 'Output format: json or array', 'json')
21
23
  .option('--json', 'Machine-readable JSON output')
22
24
  .option('-q, --quiet', 'Suppress non-essential output')
@@ -26,17 +28,29 @@ function registerEmbed(program) {
26
28
 
27
29
  const useColor = !opts.json;
28
30
  const useSpinner = useColor && !opts.quiet;
31
+
32
+ // Show hint when --input-type is not provided and output is interactive
33
+ if (!opts.inputType && !opts.json && !opts.quiet && process.stdout.isTTY) {
34
+ console.error(ui.dim('ℹ Tip: Use --input-type query or --input-type document for better retrieval accuracy.'));
35
+ }
36
+
29
37
  let spin;
30
38
  if (useSpinner) {
31
39
  spin = ui.spinner('Generating embeddings...');
32
40
  spin.start();
33
41
  }
34
42
 
35
- const result = await generateEmbeddings(texts, {
43
+ const embedOpts = {
36
44
  model: opts.model,
37
45
  inputType: opts.inputType,
38
46
  dimensions: opts.dimensions,
39
- });
47
+ };
48
+ // Only pass truncation when explicitly set via --truncation or --no-truncation
49
+ if (opts.truncation !== undefined) {
50
+ embedOpts.truncation = opts.truncation;
51
+ }
52
+
53
+ const result = await generateEmbeddings(texts, embedOpts);
40
54
 
41
55
  if (spin) spin.stop();
42
56