voyageai-cli 1.10.0 → 1.11.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/demo.gif +0 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/about.js +85 -0
- package/src/commands/benchmark.js +2 -0
- package/src/commands/store.js +2 -0
- package/src/lib/api.js +2 -0
- package/src/lib/catalog.js +2 -0
- package/src/lib/math.js +5 -0
- package/src/playground/index.html +119 -0
- package/test/commands/about.test.js +23 -0
- package/voyageai-cli-playground.png +0 -0
package/demo.gif
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ const { registerIngest } = require('./commands/ingest');
|
|
|
20
20
|
const { registerCompletions } = require('./commands/completions');
|
|
21
21
|
const { registerPlayground } = require('./commands/playground');
|
|
22
22
|
const { registerBenchmark } = require('./commands/benchmark');
|
|
23
|
+
const { registerAbout } = require('./commands/about');
|
|
23
24
|
const { showBanner, showQuickStart, getVersion } = require('./lib/banner');
|
|
24
25
|
|
|
25
26
|
const version = getVersion();
|
|
@@ -44,6 +45,7 @@ registerIngest(program);
|
|
|
44
45
|
registerCompletions(program);
|
|
45
46
|
registerPlayground(program);
|
|
46
47
|
registerBenchmark(program);
|
|
48
|
+
registerAbout(program);
|
|
47
49
|
|
|
48
50
|
// Append disclaimer to all help output
|
|
49
51
|
program.addHelpText('after', `
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pc = require('picocolors');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register the about command on a Commander program.
|
|
7
|
+
* @param {import('commander').Command} program
|
|
8
|
+
*/
|
|
9
|
+
function registerAbout(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('about')
|
|
12
|
+
.description('About this tool and its author')
|
|
13
|
+
.option('--json', 'Machine-readable JSON output')
|
|
14
|
+
.action((opts) => {
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify({
|
|
17
|
+
tool: 'voyageai-cli',
|
|
18
|
+
binary: 'vai',
|
|
19
|
+
author: {
|
|
20
|
+
name: 'Michael Lynn',
|
|
21
|
+
role: 'Principal Staff Developer Advocate, MongoDB',
|
|
22
|
+
github: 'https://github.com/mrlynn',
|
|
23
|
+
website: 'https://mlynn.org',
|
|
24
|
+
},
|
|
25
|
+
links: {
|
|
26
|
+
npm: 'https://www.npmjs.com/package/voyageai-cli',
|
|
27
|
+
github: 'https://github.com/mrlynn/voyageai-cli',
|
|
28
|
+
docs: 'https://www.mongodb.com/docs/voyageai/',
|
|
29
|
+
},
|
|
30
|
+
disclaimer: 'Community tool — not an official MongoDB or Voyage AI product.',
|
|
31
|
+
}, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(` ${pc.bold(pc.cyan('voyageai-cli'))} ${pc.dim('(vai)')}`);
|
|
37
|
+
console.log(` ${pc.dim('Voyage AI embeddings, reranking & Atlas Vector Search CLI')}`);
|
|
38
|
+
console.log('');
|
|
39
|
+
|
|
40
|
+
// Author
|
|
41
|
+
console.log(` ${pc.bold('Author')}`);
|
|
42
|
+
console.log(` Michael Lynn`);
|
|
43
|
+
console.log(` ${pc.dim('Principal Staff Developer Advocate · MongoDB')}`);
|
|
44
|
+
console.log(` ${pc.dim('25+ years enterprise infrastructure · 10+ years at MongoDB')}`);
|
|
45
|
+
console.log('');
|
|
46
|
+
|
|
47
|
+
// About
|
|
48
|
+
console.log(` ${pc.bold('About This Project')}`);
|
|
49
|
+
console.log(` A community-built CLI for working with Voyage AI embeddings,`);
|
|
50
|
+
console.log(` reranking, and MongoDB Atlas Vector Search. Created to help`);
|
|
51
|
+
console.log(` developers explore, benchmark, and integrate Voyage AI models`);
|
|
52
|
+
console.log(` into their applications — right from the terminal.`);
|
|
53
|
+
console.log('');
|
|
54
|
+
|
|
55
|
+
// Features
|
|
56
|
+
console.log(` ${pc.bold('What You Can Do')}`);
|
|
57
|
+
console.log(` ${pc.cyan('vai embed')} Generate vector embeddings for text`);
|
|
58
|
+
console.log(` ${pc.cyan('vai similarity')} Compare texts with cosine similarity`);
|
|
59
|
+
console.log(` ${pc.cyan('vai rerank')} Rerank documents against a query`);
|
|
60
|
+
console.log(` ${pc.cyan('vai search')} Vector search against Atlas collections`);
|
|
61
|
+
console.log(` ${pc.cyan('vai store')} Embed and store documents in Atlas`);
|
|
62
|
+
console.log(` ${pc.cyan('vai benchmark')} Compare model latency, ranking & costs`);
|
|
63
|
+
console.log(` ${pc.cyan('vai explain')} Learn about embeddings, vector search & more`);
|
|
64
|
+
console.log(` ${pc.cyan('vai playground')} Launch interactive web playground`);
|
|
65
|
+
console.log('');
|
|
66
|
+
|
|
67
|
+
// Links
|
|
68
|
+
console.log(` ${pc.bold('Links')}`);
|
|
69
|
+
console.log(` ${pc.dim('npm:')} https://www.npmjs.com/package/voyageai-cli`);
|
|
70
|
+
console.log(` ${pc.dim('GitHub:')} https://github.com/mrlynn/voyageai-cli`);
|
|
71
|
+
console.log(` ${pc.dim('Docs:')} https://www.mongodb.com/docs/voyageai/`);
|
|
72
|
+
console.log(` ${pc.dim('Author:')} https://mlynn.org`);
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
// Disclaimer
|
|
76
|
+
console.log(` ${pc.yellow('⚠ Community Tool Disclaimer')}`);
|
|
77
|
+
console.log(` ${pc.dim('This tool is not an official product of MongoDB, Inc. or')}`);
|
|
78
|
+
console.log(` ${pc.dim('Voyage AI. It is independently built and maintained by')}`);
|
|
79
|
+
console.log(` ${pc.dim('Michael Lynn as a community resource. Not supported,')}`);
|
|
80
|
+
console.log(` ${pc.dim('endorsed, or guaranteed by either company.')}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { registerAbout };
|
|
@@ -21,6 +21,8 @@ const SAMPLE_TEXTS = [
|
|
|
21
21
|
'GraphQL provides a flexible query language that lets clients request exactly the data they need.',
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
// If you're reading this, you're either benchmarking or procrastinating.
|
|
25
|
+
// Either way, we respect the hustle.
|
|
24
26
|
const SAMPLE_QUERY = 'How do I search for similar documents using embeddings?';
|
|
25
27
|
|
|
26
28
|
const SAMPLE_RERANK_DOCS = [
|
package/src/commands/store.js
CHANGED
|
@@ -170,6 +170,8 @@ async function handleBatchStore(opts) {
|
|
|
170
170
|
|
|
171
171
|
const { client: c, collection } = await getMongoCollection(opts.db, opts.collection);
|
|
172
172
|
client = c;
|
|
173
|
+
// insertMany: because life's too short for one document at a time.
|
|
174
|
+
// This is the MongoDB equivalent of "I'll have what everyone's having."
|
|
173
175
|
const result = await collection.insertMany(docs);
|
|
174
176
|
|
|
175
177
|
if (spin) spin.stop();
|
package/src/lib/api.js
CHANGED
|
@@ -78,6 +78,8 @@ async function apiRequest(endpoint, body) {
|
|
|
78
78
|
body: JSON.stringify(body),
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
// 429: The API said "slow down monkey" — respect the rate limit
|
|
82
|
+
// like you'd respect a $merge that's already running on your replica set.
|
|
81
83
|
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
82
84
|
const retryAfter = response.headers.get('Retry-After');
|
|
83
85
|
const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : Math.pow(2, attempt) * 1000;
|
package/src/lib/catalog.js
CHANGED
|
@@ -22,6 +22,8 @@ function getDefaultDimensions() {
|
|
|
22
22
|
return getConfigValue('defaultDimensions') || DEFAULT_DIMENSIONS;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// The model catalog: like a wine list (I don't drink :-P), except every choice
|
|
26
|
+
// leads to vectors instead of regret.
|
|
25
27
|
/** @type {Array<{name: string, type: string, context: string, dimensions: string, price: string, bestFor: string}>} */
|
|
26
28
|
const MODEL_CATALOG = [
|
|
27
29
|
{ name: 'voyage-4-large', type: 'embedding', context: '32K', dimensions: '1024 (default), 256, 512, 2048', price: '$0.12/1M tokens', bestFor: 'Best quality, multilingual', shortFor: 'Best quality' },
|
package/src/lib/math.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Compute cosine similarity between two vectors.
|
|
5
5
|
* cosine_sim(a, b) = dot(a, b) / (||a|| * ||b||)
|
|
6
|
+
*
|
|
7
|
+
* Fun fact: this is basically asking "how much do these two vectors
|
|
8
|
+
* vibe?" — 1.0 means soulmates, 0.0 means strangers at a party,
|
|
9
|
+
* -1.0 means they're in a Twitter argument.
|
|
10
|
+
*
|
|
6
11
|
* @param {number[]} a
|
|
7
12
|
* @param {number[]} b
|
|
8
13
|
* @returns {number} Similarity score in [-1, 1]
|
|
@@ -680,6 +680,57 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
680
680
|
margin-top: 4px;
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
/* About page */
|
|
684
|
+
.about-container { max-width: 680px; margin: 0 auto; }
|
|
685
|
+
.about-header { display: flex; gap: 24px; align-items: center; margin-bottom: 24px; }
|
|
686
|
+
.about-avatar {
|
|
687
|
+
width: 120px; height: 120px;
|
|
688
|
+
border-radius: 50%;
|
|
689
|
+
border: 3px solid var(--accent);
|
|
690
|
+
box-shadow: 0 0 20px var(--accent-glow);
|
|
691
|
+
flex-shrink: 0;
|
|
692
|
+
}
|
|
693
|
+
.about-name { font-size: 24px; font-weight: 700; color: var(--text); }
|
|
694
|
+
.about-role { font-size: 14px; color: var(--accent); margin-top: 4px; }
|
|
695
|
+
.about-links { display: flex; gap: 12px; margin-top: 8px; }
|
|
696
|
+
.about-links a {
|
|
697
|
+
color: var(--text-dim);
|
|
698
|
+
font-size: 13px;
|
|
699
|
+
text-decoration: none;
|
|
700
|
+
transition: color 0.2s;
|
|
701
|
+
}
|
|
702
|
+
.about-links a:hover { color: var(--accent); }
|
|
703
|
+
.about-section { margin-bottom: 24px; }
|
|
704
|
+
.about-section-title {
|
|
705
|
+
font-size: 13px;
|
|
706
|
+
font-weight: 600;
|
|
707
|
+
color: var(--accent);
|
|
708
|
+
text-transform: uppercase;
|
|
709
|
+
letter-spacing: 0.5px;
|
|
710
|
+
margin-bottom: 8px;
|
|
711
|
+
}
|
|
712
|
+
.about-text { font-size: 14px; line-height: 1.8; color: var(--text); }
|
|
713
|
+
.about-text a { color: var(--accent); text-decoration: none; }
|
|
714
|
+
.about-text a:hover { text-decoration: underline; }
|
|
715
|
+
.about-disclaimer {
|
|
716
|
+
background: rgba(255, 215, 61, 0.08);
|
|
717
|
+
border: 1px solid rgba(255, 215, 61, 0.2);
|
|
718
|
+
border-radius: var(--radius);
|
|
719
|
+
padding: 16px 20px;
|
|
720
|
+
margin-top: 24px;
|
|
721
|
+
}
|
|
722
|
+
.about-disclaimer-title {
|
|
723
|
+
font-size: 13px;
|
|
724
|
+
font-weight: 600;
|
|
725
|
+
color: var(--warning);
|
|
726
|
+
margin-bottom: 6px;
|
|
727
|
+
}
|
|
728
|
+
.about-disclaimer-text {
|
|
729
|
+
font-size: 13px;
|
|
730
|
+
line-height: 1.7;
|
|
731
|
+
color: var(--text-dim);
|
|
732
|
+
}
|
|
733
|
+
|
|
683
734
|
@media (max-width: 768px) {
|
|
684
735
|
.compare-grid, .search-results { grid-template-columns: 1fr; }
|
|
685
736
|
.nav { padding: 0 12px; }
|
|
@@ -711,6 +762,7 @@ select:focus { outline: none; border-color: var(--accent); }
|
|
|
711
762
|
<button class="tab-btn" data-tab="search">🔍 Search</button>
|
|
712
763
|
<button class="tab-btn" data-tab="benchmark">⏱ Benchmark</button>
|
|
713
764
|
<button class="tab-btn" data-tab="explore">📚 Explore</button>
|
|
765
|
+
<button class="tab-btn" data-tab="about">ℹ️ About</button>
|
|
714
766
|
</div>
|
|
715
767
|
|
|
716
768
|
<div class="main">
|
|
@@ -1001,6 +1053,73 @@ Reranking models rescore initial search results to improve relevance ordering.</
|
|
|
1001
1053
|
|
|
1002
1054
|
</div>
|
|
1003
1055
|
|
|
1056
|
+
<!-- ========== ABOUT TAB ========== -->
|
|
1057
|
+
<div class="tab-panel" id="tab-about">
|
|
1058
|
+
<div class="about-container">
|
|
1059
|
+
<div class="card">
|
|
1060
|
+
<div class="about-header">
|
|
1061
|
+
<img src="https://avatars.githubusercontent.com/u/192552?v=4" alt="Michael Lynn" class="about-avatar">
|
|
1062
|
+
<div>
|
|
1063
|
+
<div class="about-name">Michael Lynn</div>
|
|
1064
|
+
<div class="about-role">Principal Staff Developer Advocate · MongoDB</div>
|
|
1065
|
+
<div class="about-links">
|
|
1066
|
+
<a href="https://github.com/mrlynn" target="_blank" rel="noopener">🔗 GitHub</a>
|
|
1067
|
+
<a href="https://mlynn.org" target="_blank" rel="noopener">🌐 mlynn.org</a>
|
|
1068
|
+
<a href="https://www.npmjs.com/package/voyageai-cli" target="_blank" rel="noopener">📦 npm</a>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
</div>
|
|
1072
|
+
|
|
1073
|
+
<div class="about-section">
|
|
1074
|
+
<div class="about-section-title">About This Project</div>
|
|
1075
|
+
<div class="about-text">
|
|
1076
|
+
<strong>voyageai-cli</strong> (<code style="color:var(--accent);">vai</code>) is a community-built command-line tool for working with
|
|
1077
|
+
<a href="https://www.mongodb.com/docs/voyageai/" target="_blank">Voyage AI</a> embeddings, reranking, and
|
|
1078
|
+
<a href="https://www.mongodb.com/products/platform/atlas-vector-search" target="_blank">MongoDB Atlas Vector Search</a>.
|
|
1079
|
+
It was created to make it easier for developers to explore, benchmark, and integrate
|
|
1080
|
+
Voyage AI models into their applications — right from the terminal or this playground.
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
|
|
1084
|
+
<div class="about-section">
|
|
1085
|
+
<div class="about-section-title">About Michael</div>
|
|
1086
|
+
<div class="about-text">
|
|
1087
|
+
Michael Lynn is a Principal Staff Developer Advocate at MongoDB with 25+ years in enterprise
|
|
1088
|
+
infrastructure and over a decade at MongoDB. He focuses on strategic developer relations,
|
|
1089
|
+
creating educational content around Vector Search, AI enablement, and developer tooling.
|
|
1090
|
+
He builds tools like this to help developers get hands-on with new technology faster.
|
|
1091
|
+
</div>
|
|
1092
|
+
</div>
|
|
1093
|
+
|
|
1094
|
+
<div class="about-section">
|
|
1095
|
+
<div class="about-section-title">What You Can Do Here</div>
|
|
1096
|
+
<div class="about-text">
|
|
1097
|
+
<strong>⚡ Embed</strong> — Generate vector embeddings for any text<br>
|
|
1098
|
+
<strong>⚖️ Compare</strong> — Measure cosine similarity between texts<br>
|
|
1099
|
+
<strong>🔍 Search</strong> — Semantic search with optional reranking<br>
|
|
1100
|
+
<strong>⏱ Benchmark</strong> — Compare model latency, ranking quality, and costs<br>
|
|
1101
|
+
<strong>📚 Explore</strong> — Learn about embeddings, vector search, RAG, and more
|
|
1102
|
+
</div>
|
|
1103
|
+
</div>
|
|
1104
|
+
|
|
1105
|
+
<div class="about-disclaimer">
|
|
1106
|
+
<div class="about-disclaimer-title">⚠️ Community Tool Disclaimer</div>
|
|
1107
|
+
<div class="about-disclaimer-text">
|
|
1108
|
+
This tool is <strong>not</strong> an official product of MongoDB, Inc. or Voyage AI.
|
|
1109
|
+
It is independently built and maintained by Michael Lynn as a community resource.
|
|
1110
|
+
It is not supported, endorsed, or guaranteed by either company. Use at your own discretion.
|
|
1111
|
+
For official documentation, visit
|
|
1112
|
+
<a href="https://www.mongodb.com/docs/voyageai/" target="_blank" style="color:var(--warning);">mongodb.com/docs/voyageai</a>.
|
|
1113
|
+
</div>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
|
|
1117
|
+
<div style="text-align:center;margin-top:16px;font-size:12px;color:var(--text-muted);">
|
|
1118
|
+
Made with ☕ and curiosity · <a href="https://github.com/mrlynn/voyageai-cli" target="_blank" style="color:var(--text-dim);">Source on GitHub</a>
|
|
1119
|
+
</div>
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
|
|
1004
1123
|
<!-- ========== EXPLORE TAB ========== -->
|
|
1005
1124
|
<div class="tab-panel" id="tab-explore">
|
|
1006
1125
|
<div style="margin-bottom:16px;">
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { Command } = require('commander');
|
|
6
|
+
const { registerAbout } = require('../../src/commands/about');
|
|
7
|
+
|
|
8
|
+
describe('about command', () => {
|
|
9
|
+
it('registers correctly on a program', () => {
|
|
10
|
+
const program = new Command();
|
|
11
|
+
registerAbout(program);
|
|
12
|
+
const aboutCmd = program.commands.find(c => c.name() === 'about');
|
|
13
|
+
assert.ok(aboutCmd, 'about command should be registered');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has --json option', () => {
|
|
17
|
+
const program = new Command();
|
|
18
|
+
registerAbout(program);
|
|
19
|
+
const aboutCmd = program.commands.find(c => c.name() === 'about');
|
|
20
|
+
const optionNames = aboutCmd.options.map(o => o.long);
|
|
21
|
+
assert.ok(optionNames.includes('--json'), 'should have --json option');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
Binary file
|