voyageai-cli 1.3.0 → 1.5.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/CHANGELOG.md +2 -0
- package/NOTICE +23 -0
- package/README.md +84 -0
- package/demo.gif +0 -0
- package/demo.tape +39 -0
- package/package.json +1 -1
- package/scripts/record-demo.sh +63 -0
- package/src/cli.js +19 -2
- package/src/commands/completions.js +463 -0
- package/src/commands/demo.js +3 -0
- package/src/commands/explain.js +170 -0
- package/src/commands/ingest.js +414 -0
- package/src/commands/models.js +61 -13
- package/src/commands/ping.js +5 -4
- package/src/commands/similarity.js +175 -0
- package/src/lib/api.js +48 -2
- package/src/lib/banner.js +1 -0
- package/src/lib/catalog.js +10 -10
- package/src/lib/config.js +1 -0
- package/src/lib/explanations.js +480 -0
- package/src/lib/math.js +20 -0
- package/test/commands/completions.test.js +166 -0
- package/test/commands/explain.test.js +207 -0
- package/test/commands/ingest.test.js +248 -0
- package/test/commands/ping.test.js +24 -11
- package/test/commands/similarity.test.js +79 -0
- package/test/fixtures/sample.csv +6 -0
- package/test/fixtures/sample.json +7 -0
- package/test/fixtures/sample.jsonl +5 -0
- package/test/fixtures/sample.txt +5 -0
- package/test/lib/api.test.js +12 -3
- package/test/lib/explanations.test.js +134 -0
- package/test/lib/math.test.js +43 -0
|
@@ -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 };
|
package/src/commands/demo.js
CHANGED
|
@@ -130,6 +130,9 @@ function registerDemo(program) {
|
|
|
130
130
|
console.log(pc.bold(' 🧭 Voyage AI Interactive Demo'));
|
|
131
131
|
console.log(pc.dim(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
132
132
|
console.log('');
|
|
133
|
+
console.log(pc.dim(' Note: This is a community tool, not an official MongoDB or Voyage AI product.'));
|
|
134
|
+
console.log(pc.dim(' For official docs and support: https://www.mongodb.com/docs/voyageai/'));
|
|
135
|
+
console.log('');
|
|
133
136
|
console.log(' This walkthrough demonstrates embeddings, semantic search, and reranking');
|
|
134
137
|
console.log(' using Voyage AI models via MongoDB Atlas.');
|
|
135
138
|
console.log('');
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pc = require('picocolors');
|
|
4
|
+
const { resolveConcept, listConcepts, getConcept } = require('../lib/explanations');
|
|
5
|
+
|
|
6
|
+
const DISCLAIMER = pc.dim(' This tool is not affiliated with MongoDB, Inc. or Voyage AI.');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Show the list of available topics.
|
|
10
|
+
*/
|
|
11
|
+
function showTopicList() {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(` 🧭 ${pc.bold('Voyage AI Concepts')}`);
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(' Available topics:');
|
|
16
|
+
|
|
17
|
+
const concepts = listConcepts();
|
|
18
|
+
const maxKeyLen = Math.max(...concepts.map(k => k.length));
|
|
19
|
+
|
|
20
|
+
for (const key of concepts) {
|
|
21
|
+
const concept = getConcept(key);
|
|
22
|
+
const padding = ' '.repeat(maxKeyLen - key.length + 4);
|
|
23
|
+
console.log(` ${pc.cyan(key)}${padding}${pc.dim(concept.summary)}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(` Usage: ${pc.cyan('vai explain <topic>')}`);
|
|
28
|
+
console.log('');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Show a formatted explanation of a concept.
|
|
33
|
+
* @param {string} key - canonical concept key
|
|
34
|
+
*/
|
|
35
|
+
function showExplanation(key) {
|
|
36
|
+
const concept = getConcept(key);
|
|
37
|
+
if (!concept) return;
|
|
38
|
+
|
|
39
|
+
console.log('');
|
|
40
|
+
console.log(` 🧭 ${pc.bold(concept.title)}`);
|
|
41
|
+
console.log(` ${pc.dim('━'.repeat(concept.title.length + 2))}`);
|
|
42
|
+
console.log('');
|
|
43
|
+
|
|
44
|
+
// Indent the content
|
|
45
|
+
const lines = concept.content.split('\n');
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
console.log(` ${line}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(` ${pc.bold('Key points:')}`);
|
|
52
|
+
|
|
53
|
+
// Extract key points from content — use a curated approach
|
|
54
|
+
// Actually, the content itself is the explanation. Let's show tryIt and links.
|
|
55
|
+
|
|
56
|
+
if (concept.tryIt && concept.tryIt.length > 0) {
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(` ${pc.bold('Try it:')}`);
|
|
59
|
+
for (const cmd of concept.tryIt) {
|
|
60
|
+
console.log(` ${pc.dim('$')} ${pc.cyan(cmd)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (concept.links && concept.links.length > 0) {
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(` ${pc.bold('Learn more:')}`);
|
|
67
|
+
for (const link of concept.links) {
|
|
68
|
+
console.log(` ${link}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(DISCLAIMER);
|
|
74
|
+
console.log('');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Show a JSON explanation of a concept.
|
|
79
|
+
* @param {string} key - canonical concept key
|
|
80
|
+
*/
|
|
81
|
+
function showJsonExplanation(key) {
|
|
82
|
+
const concept = getConcept(key);
|
|
83
|
+
if (!concept) return;
|
|
84
|
+
|
|
85
|
+
// Strip ANSI codes for JSON
|
|
86
|
+
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
87
|
+
|
|
88
|
+
const output = {
|
|
89
|
+
concept: key,
|
|
90
|
+
title: concept.title,
|
|
91
|
+
summary: concept.summary,
|
|
92
|
+
content: stripAnsi(concept.content),
|
|
93
|
+
links: concept.links,
|
|
94
|
+
tryIt: concept.tryIt,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
console.log(JSON.stringify(output, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Find close matches for an unknown concept.
|
|
102
|
+
* @param {string} input
|
|
103
|
+
* @returns {string[]} up to 3 suggestions
|
|
104
|
+
*/
|
|
105
|
+
function findSuggestions(input) {
|
|
106
|
+
const normalized = input.toLowerCase().trim();
|
|
107
|
+
const allKeys = listConcepts();
|
|
108
|
+
|
|
109
|
+
// Simple substring matching
|
|
110
|
+
const matches = allKeys.filter(k =>
|
|
111
|
+
k.includes(normalized) || normalized.includes(k)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (matches.length > 0) return matches.slice(0, 3);
|
|
115
|
+
|
|
116
|
+
// Levenshtein-ish: just return first 3 concepts as fallback
|
|
117
|
+
return allKeys.slice(0, 3);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Register the explain command on a Commander program.
|
|
122
|
+
* @param {import('commander').Command} program
|
|
123
|
+
*/
|
|
124
|
+
function registerExplain(program) {
|
|
125
|
+
program
|
|
126
|
+
.command('explain [concept]')
|
|
127
|
+
.description('Learn about embeddings, reranking, vector search, and more')
|
|
128
|
+
.option('--json', 'Output in JSON format')
|
|
129
|
+
.action((concept, opts) => {
|
|
130
|
+
if (!concept) {
|
|
131
|
+
// Show topic list
|
|
132
|
+
if (opts.json) {
|
|
133
|
+
const topics = listConcepts().map(key => {
|
|
134
|
+
const c = getConcept(key);
|
|
135
|
+
return { key, title: c.title, summary: c.summary };
|
|
136
|
+
});
|
|
137
|
+
console.log(JSON.stringify({ topics }, null, 2));
|
|
138
|
+
} else {
|
|
139
|
+
showTopicList();
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const resolved = resolveConcept(concept);
|
|
145
|
+
if (!resolved) {
|
|
146
|
+
const suggestions = findSuggestions(concept);
|
|
147
|
+
console.error('');
|
|
148
|
+
console.error(` ${pc.red('✗')} Unknown topic: ${pc.bold(concept)}`);
|
|
149
|
+
console.error('');
|
|
150
|
+
if (suggestions.length > 0) {
|
|
151
|
+
console.error(' Did you mean?');
|
|
152
|
+
for (const s of suggestions) {
|
|
153
|
+
console.error(` ${pc.cyan('vai explain ' + s)}`);
|
|
154
|
+
}
|
|
155
|
+
console.error('');
|
|
156
|
+
}
|
|
157
|
+
console.error(` Run ${pc.cyan('vai explain')} to see all available topics.`);
|
|
158
|
+
console.error('');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (opts.json) {
|
|
163
|
+
showJsonExplanation(resolved);
|
|
164
|
+
} else {
|
|
165
|
+
showExplanation(resolved);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { registerExplain };
|