voyageai-cli 1.12.1 → 1.15.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 +3 -3
- package/demo-readme.gif +0 -0
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/commands/benchmark.js +164 -0
- package/src/commands/completions.js +18 -1
- package/src/commands/estimate.js +209 -0
- package/src/commands/models.js +32 -4
- package/src/lib/catalog.js +42 -18
- package/src/lib/explanations.js +183 -0
- package/.github/workflows/ci.yml +0 -22
- package/CONTRIBUTING.md +0 -81
- package/demo.gif +0 -0
- package/demo.tape +0 -39
- package/scripts/record-demo.sh +0 -63
- package/test/commands/about.test.js +0 -23
- package/test/commands/benchmark.test.js +0 -319
- package/test/commands/completions.test.js +0 -166
- package/test/commands/config.test.js +0 -35
- package/test/commands/demo.test.js +0 -46
- package/test/commands/embed.test.js +0 -42
- package/test/commands/explain.test.js +0 -207
- package/test/commands/ingest.test.js +0 -261
- package/test/commands/models.test.js +0 -132
- package/test/commands/ping.test.js +0 -172
- package/test/commands/playground.test.js +0 -137
- package/test/commands/rerank.test.js +0 -32
- package/test/commands/similarity.test.js +0 -79
- package/test/commands/store.test.js +0 -26
- package/test/fixtures/sample.csv +0 -6
- package/test/fixtures/sample.json +0 -7
- package/test/fixtures/sample.jsonl +0 -5
- package/test/fixtures/sample.txt +0 -5
- package/test/lib/api.test.js +0 -133
- package/test/lib/banner.test.js +0 -44
- package/test/lib/catalog.test.js +0 -99
- package/test/lib/config.test.js +0 -124
- package/test/lib/explanations.test.js +0 -141
- package/test/lib/format.test.js +0 -75
- package/test/lib/input.test.js +0 -48
- package/test/lib/math.test.js +0 -43
- package/test/lib/ui.test.js +0 -79
- package/voyageai-cli-playground.png +0 -0
- package/voyageai-cli.png +0 -0
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { Command } = require('commander');
|
|
6
|
-
const { registerPing } = require('../../src/commands/ping');
|
|
7
|
-
|
|
8
|
-
describe('ping command', () => {
|
|
9
|
-
let originalLog;
|
|
10
|
-
let originalError;
|
|
11
|
-
let originalExit;
|
|
12
|
-
let originalKey;
|
|
13
|
-
let originalMongoUri;
|
|
14
|
-
let output;
|
|
15
|
-
let errorOutput;
|
|
16
|
-
|
|
17
|
-
// Strip ANSI escape codes for reliable string assertions in CI
|
|
18
|
-
// (GitHub Actions sets FORCE_COLOR which adds ANSI codes via picocolors)
|
|
19
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
originalLog = console.log;
|
|
23
|
-
originalError = console.error;
|
|
24
|
-
originalExit = process.exit;
|
|
25
|
-
originalKey = process.env.VOYAGE_API_KEY;
|
|
26
|
-
originalMongoUri = process.env.MONGODB_URI;
|
|
27
|
-
output = [];
|
|
28
|
-
errorOutput = [];
|
|
29
|
-
console.log = (...args) => output.push(args.join(' '));
|
|
30
|
-
console.error = (...args) => errorOutput.push(args.join(' '));
|
|
31
|
-
// Remove MONGODB_URI by default so we don't accidentally test mongo
|
|
32
|
-
delete process.env.MONGODB_URI;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
console.log = originalLog;
|
|
37
|
-
console.error = originalError;
|
|
38
|
-
process.exit = originalExit;
|
|
39
|
-
if (originalKey !== undefined) {
|
|
40
|
-
process.env.VOYAGE_API_KEY = originalKey;
|
|
41
|
-
} else {
|
|
42
|
-
delete process.env.VOYAGE_API_KEY;
|
|
43
|
-
}
|
|
44
|
-
if (originalMongoUri !== undefined) {
|
|
45
|
-
process.env.MONGODB_URI = originalMongoUri;
|
|
46
|
-
} else {
|
|
47
|
-
delete process.env.MONGODB_URI;
|
|
48
|
-
}
|
|
49
|
-
mock.restoreAll();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('registers correctly on a program', () => {
|
|
53
|
-
const program = new Command();
|
|
54
|
-
registerPing(program);
|
|
55
|
-
const pingCmd = program.commands.find(c => c.name() === 'ping');
|
|
56
|
-
assert.ok(pingCmd, 'ping command should be registered');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('prints success on valid API response', async () => {
|
|
60
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
61
|
-
|
|
62
|
-
mock.method(global, 'fetch', async () => ({
|
|
63
|
-
ok: true,
|
|
64
|
-
status: 200,
|
|
65
|
-
json: async () => ({
|
|
66
|
-
data: [{ embedding: new Array(1024).fill(0) }],
|
|
67
|
-
usage: { total_tokens: 1 },
|
|
68
|
-
}),
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
const program = new Command();
|
|
72
|
-
program.exitOverride();
|
|
73
|
-
registerPing(program);
|
|
74
|
-
|
|
75
|
-
await program.parseAsync(['node', 'test', 'ping']);
|
|
76
|
-
|
|
77
|
-
const combined = stripAnsi(output.join('\n'));
|
|
78
|
-
assert.ok(combined.includes('✓ Connected to Voyage AI API'), 'Should show success message');
|
|
79
|
-
assert.ok(combined.includes('voyage-4-lite'), 'Should show model name');
|
|
80
|
-
assert.ok(combined.includes('1024'), 'Should show dimensions');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('exits with error on auth failure', async () => {
|
|
84
|
-
process.env.VOYAGE_API_KEY = 'bad-key';
|
|
85
|
-
|
|
86
|
-
mock.method(global, 'fetch', async () => ({
|
|
87
|
-
ok: false,
|
|
88
|
-
status: 401,
|
|
89
|
-
text: async () => 'Unauthorized',
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
let exitCode = null;
|
|
93
|
-
process.exit = (code) => {
|
|
94
|
-
exitCode = code;
|
|
95
|
-
throw new Error('process.exit called');
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const program = new Command();
|
|
99
|
-
program.exitOverride();
|
|
100
|
-
registerPing(program);
|
|
101
|
-
|
|
102
|
-
await assert.rejects(
|
|
103
|
-
() => program.parseAsync(['node', 'test', 'ping']),
|
|
104
|
-
/process\.exit called/
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
assert.equal(exitCode, 1);
|
|
108
|
-
const combined = stripAnsi(errorOutput.join('\n'));
|
|
109
|
-
assert.ok(combined.includes('Authentication failed'), 'Should show auth error');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('exits when VOYAGE_API_KEY is not set and no config', async () => {
|
|
113
|
-
delete process.env.VOYAGE_API_KEY;
|
|
114
|
-
// Mock config to return nothing so the key isn't found in ~/.vai/config.json
|
|
115
|
-
delete require.cache[require.resolve('../../src/lib/config')];
|
|
116
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
117
|
-
delete require.cache[require.resolve('../../src/commands/ping')];
|
|
118
|
-
const config = require('../../src/lib/config');
|
|
119
|
-
const origGetConfigValue = config.getConfigValue;
|
|
120
|
-
config.getConfigValue = () => undefined;
|
|
121
|
-
|
|
122
|
-
const { registerPing: registerPingFresh } = require('../../src/commands/ping');
|
|
123
|
-
|
|
124
|
-
let exitCode = null;
|
|
125
|
-
process.exit = (code) => {
|
|
126
|
-
exitCode = code;
|
|
127
|
-
throw new Error('process.exit called');
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const program = new Command();
|
|
131
|
-
program.exitOverride();
|
|
132
|
-
registerPingFresh(program);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await assert.rejects(
|
|
136
|
-
() => program.parseAsync(['node', 'test', 'ping']),
|
|
137
|
-
/process\.exit called/
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
assert.equal(exitCode, 1);
|
|
141
|
-
const combined = stripAnsi(errorOutput.join('\n'));
|
|
142
|
-
assert.ok(combined.includes('VOYAGE_API_KEY'), 'Should mention missing key');
|
|
143
|
-
} finally {
|
|
144
|
-
config.getConfigValue = origGetConfigValue;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('outputs JSON when --json flag is used', async () => {
|
|
149
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
150
|
-
|
|
151
|
-
mock.method(global, 'fetch', async () => ({
|
|
152
|
-
ok: true,
|
|
153
|
-
status: 200,
|
|
154
|
-
json: async () => ({
|
|
155
|
-
data: [{ embedding: new Array(1024).fill(0) }],
|
|
156
|
-
usage: { total_tokens: 1 },
|
|
157
|
-
}),
|
|
158
|
-
}));
|
|
159
|
-
|
|
160
|
-
const program = new Command();
|
|
161
|
-
program.exitOverride();
|
|
162
|
-
registerPing(program);
|
|
163
|
-
|
|
164
|
-
await program.parseAsync(['node', 'test', 'ping', '--json']);
|
|
165
|
-
|
|
166
|
-
const combined = output.join('\n');
|
|
167
|
-
const parsed = JSON.parse(combined);
|
|
168
|
-
assert.equal(parsed.ok, true);
|
|
169
|
-
assert.ok(parsed.voyage);
|
|
170
|
-
assert.equal(parsed.voyage.ok, true);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, after } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const http = require('http');
|
|
6
|
-
const { Command } = require('commander');
|
|
7
|
-
const { registerPlayground, createPlaygroundServer } = require('../../src/commands/playground');
|
|
8
|
-
|
|
9
|
-
describe('playground command', () => {
|
|
10
|
-
let server;
|
|
11
|
-
let port;
|
|
12
|
-
|
|
13
|
-
// Start server once for all tests
|
|
14
|
-
const serverReady = new Promise((resolve) => {
|
|
15
|
-
server = createPlaygroundServer();
|
|
16
|
-
server.listen(0, () => {
|
|
17
|
-
port = server.address().port;
|
|
18
|
-
resolve();
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
after(() => {
|
|
23
|
-
return new Promise((resolve) => {
|
|
24
|
-
if (server) server.close(resolve);
|
|
25
|
-
else resolve();
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('registers correctly on a program', () => {
|
|
30
|
-
const program = new Command();
|
|
31
|
-
registerPlayground(program);
|
|
32
|
-
const cmd = program.commands.find(c => c.name() === 'playground');
|
|
33
|
-
assert.ok(cmd, 'playground command should be registered');
|
|
34
|
-
assert.ok(cmd.description().includes('playground') || cmd.description().includes('Playground') || cmd.description().includes('web'),
|
|
35
|
-
'should have a relevant description');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('serves HTML on GET /', async () => {
|
|
39
|
-
await serverReady;
|
|
40
|
-
const body = await httpGet(`http://localhost:${port}/`);
|
|
41
|
-
assert.ok(body.includes('<!DOCTYPE html>'), 'should return HTML');
|
|
42
|
-
assert.ok(body.includes('Voyage AI Playground'), 'should include playground title');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('returns JSON from GET /api/models', async () => {
|
|
46
|
-
await serverReady;
|
|
47
|
-
const body = await httpGet(`http://localhost:${port}/api/models`);
|
|
48
|
-
const data = JSON.parse(body);
|
|
49
|
-
assert.ok(Array.isArray(data.models), 'models should be an array');
|
|
50
|
-
assert.ok(data.models.length > 0, 'should have at least one model');
|
|
51
|
-
assert.ok(data.models.every(m => !m.legacy), 'should not include legacy models');
|
|
52
|
-
// Check a known model exists
|
|
53
|
-
const names = data.models.map(m => m.name);
|
|
54
|
-
assert.ok(names.includes('voyage-4-large'), 'should include voyage-4-large');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('returns config with hasKey boolean from GET /api/config', async () => {
|
|
58
|
-
await serverReady;
|
|
59
|
-
const body = await httpGet(`http://localhost:${port}/api/config`);
|
|
60
|
-
const data = JSON.parse(body);
|
|
61
|
-
assert.ok(typeof data.hasKey === 'boolean', 'hasKey should be a boolean');
|
|
62
|
-
assert.ok(typeof data.baseUrl === 'string', 'baseUrl should be a string');
|
|
63
|
-
// Should never expose the actual key
|
|
64
|
-
assert.ok(!data.apiKey, 'should not expose apiKey');
|
|
65
|
-
assert.ok(!data.key, 'should not expose key');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('returns 404 for unknown routes', async () => {
|
|
69
|
-
await serverReady;
|
|
70
|
-
const { statusCode } = await httpGetFull(`http://localhost:${port}/nonexistent`);
|
|
71
|
-
assert.equal(statusCode, 404);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('returns 400 for POST /api/embed with invalid body', async () => {
|
|
75
|
-
await serverReady;
|
|
76
|
-
const { statusCode, body } = await httpPostFull(`http://localhost:${port}/api/embed`, { texts: 'not-an-array' });
|
|
77
|
-
assert.equal(statusCode, 400);
|
|
78
|
-
const data = JSON.parse(body);
|
|
79
|
-
assert.ok(data.error, 'should return an error message');
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Simple HTTP GET that returns the body string.
|
|
85
|
-
*/
|
|
86
|
-
function httpGet(url) {
|
|
87
|
-
return new Promise((resolve, reject) => {
|
|
88
|
-
http.get(url, (res) => {
|
|
89
|
-
const chunks = [];
|
|
90
|
-
res.on('data', c => chunks.push(c));
|
|
91
|
-
res.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
92
|
-
res.on('error', reject);
|
|
93
|
-
}).on('error', reject);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* HTTP GET returning { statusCode, body }.
|
|
99
|
-
*/
|
|
100
|
-
function httpGetFull(url) {
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
http.get(url, (res) => {
|
|
103
|
-
const chunks = [];
|
|
104
|
-
res.on('data', c => chunks.push(c));
|
|
105
|
-
res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
|
|
106
|
-
res.on('error', reject);
|
|
107
|
-
}).on('error', reject);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* HTTP POST returning { statusCode, body }.
|
|
113
|
-
*/
|
|
114
|
-
function httpPostFull(url, data) {
|
|
115
|
-
return new Promise((resolve, reject) => {
|
|
116
|
-
const payload = JSON.stringify(data);
|
|
117
|
-
const parsed = new URL(url);
|
|
118
|
-
const req = http.request({
|
|
119
|
-
hostname: parsed.hostname,
|
|
120
|
-
port: parsed.port,
|
|
121
|
-
path: parsed.pathname,
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers: {
|
|
124
|
-
'Content-Type': 'application/json',
|
|
125
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
126
|
-
},
|
|
127
|
-
}, (res) => {
|
|
128
|
-
const chunks = [];
|
|
129
|
-
res.on('data', c => chunks.push(c));
|
|
130
|
-
res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
|
|
131
|
-
res.on('error', reject);
|
|
132
|
-
});
|
|
133
|
-
req.on('error', reject);
|
|
134
|
-
req.write(payload);
|
|
135
|
-
req.end();
|
|
136
|
-
});
|
|
137
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
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 { registerRerank } = require('../../src/commands/rerank');
|
|
7
|
-
|
|
8
|
-
describe('rerank command', () => {
|
|
9
|
-
it('registers correctly on a program', () => {
|
|
10
|
-
const program = new Command();
|
|
11
|
-
registerRerank(program);
|
|
12
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
13
|
-
assert.ok(rerankCmd, 'rerank command should be registered');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('has --truncation flag', () => {
|
|
17
|
-
const program = new Command();
|
|
18
|
-
registerRerank(program);
|
|
19
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
20
|
-
const optionNames = rerankCmd.options.map(o => o.long);
|
|
21
|
-
assert.ok(optionNames.includes('--truncation'), 'should have --truncation option');
|
|
22
|
-
assert.ok(optionNames.includes('--no-truncation'), 'should have --no-truncation option');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('has --return-documents flag', () => {
|
|
26
|
-
const program = new Command();
|
|
27
|
-
registerRerank(program);
|
|
28
|
-
const rerankCmd = program.commands.find(c => c.name() === 'rerank');
|
|
29
|
-
const optionNames = rerankCmd.options.map(o => o.long);
|
|
30
|
-
assert.ok(optionNames.includes('--return-documents'), 'should have --return-documents option');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { Command } = require('commander');
|
|
6
|
-
const { registerSimilarity } = require('../../src/commands/similarity');
|
|
7
|
-
|
|
8
|
-
describe('similarity command', () => {
|
|
9
|
-
let originalLog, originalError, originalExit;
|
|
10
|
-
let output, errors;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
originalLog = console.log;
|
|
14
|
-
originalError = console.error;
|
|
15
|
-
originalExit = process.exit;
|
|
16
|
-
output = [];
|
|
17
|
-
errors = [];
|
|
18
|
-
console.log = (...args) => output.push(args.join(' '));
|
|
19
|
-
console.error = (...args) => errors.push(args.join(' '));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
console.log = originalLog;
|
|
24
|
-
console.error = originalError;
|
|
25
|
-
process.exit = originalExit;
|
|
26
|
-
mock.restoreAll();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('registers correctly on a program', () => {
|
|
30
|
-
const program = new Command();
|
|
31
|
-
registerSimilarity(program);
|
|
32
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
33
|
-
assert.ok(cmd, 'similarity command should be registered');
|
|
34
|
-
assert.ok(cmd.description().includes('similarity'), 'should have a description about similarity');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('requires at least two inputs', async () => {
|
|
38
|
-
let exitCode;
|
|
39
|
-
process.exit = (code) => { exitCode = code; throw new Error('exit'); };
|
|
40
|
-
|
|
41
|
-
const program = new Command();
|
|
42
|
-
program.exitOverride();
|
|
43
|
-
registerSimilarity(program);
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await program.parseAsync(['node', 'test', 'similarity', 'only-one-text']);
|
|
47
|
-
} catch {
|
|
48
|
-
// expected
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
assert.equal(exitCode, 1, 'should exit with code 1');
|
|
52
|
-
const combined = errors.join('\n');
|
|
53
|
-
assert.ok(combined.includes('two texts') || combined.includes('second text'), 'should mention needing two texts');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('accepts --against flag with multiple values', () => {
|
|
57
|
-
const program = new Command();
|
|
58
|
-
registerSimilarity(program);
|
|
59
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
60
|
-
const againstOpt = cmd.options.find(o => o.long === '--against');
|
|
61
|
-
assert.ok(againstOpt, '--against option should exist');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('accepts --json flag', () => {
|
|
65
|
-
const program = new Command();
|
|
66
|
-
registerSimilarity(program);
|
|
67
|
-
const cmd = program.commands.find(c => c.name() === 'similarity');
|
|
68
|
-
const jsonOpt = cmd.options.find(o => o.long === '--json');
|
|
69
|
-
assert.ok(jsonOpt, '--json option should exist');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('cosineSimilarity integration — identical texts would score 1.0', () => {
|
|
73
|
-
// Direct test of math module integration
|
|
74
|
-
const { cosineSimilarity } = require('../../src/lib/math');
|
|
75
|
-
const vec = [0.1, 0.2, 0.3, 0.4];
|
|
76
|
-
const result = cosineSimilarity(vec, vec);
|
|
77
|
-
assert.ok(Math.abs(result - 1.0) < 1e-10);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
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 { registerStore } = require('../../src/commands/store');
|
|
7
|
-
|
|
8
|
-
describe('store command', () => {
|
|
9
|
-
it('registers correctly on a program', () => {
|
|
10
|
-
const program = new Command();
|
|
11
|
-
registerStore(program);
|
|
12
|
-
const storeCmd = program.commands.find(c => c.name() === 'store');
|
|
13
|
-
assert.ok(storeCmd, 'store command should be registered');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('has --input-type flag defaulting to document', () => {
|
|
17
|
-
const program = new Command();
|
|
18
|
-
registerStore(program);
|
|
19
|
-
const storeCmd = program.commands.find(c => c.name() === 'store');
|
|
20
|
-
const optionNames = storeCmd.options.map(o => o.long);
|
|
21
|
-
assert.ok(optionNames.includes('--input-type'), 'should have --input-type option');
|
|
22
|
-
// Check the default value
|
|
23
|
-
const inputTypeOpt = storeCmd.options.find(o => o.long === '--input-type');
|
|
24
|
-
assert.equal(inputTypeOpt.defaultValue, 'document', '--input-type should default to document');
|
|
25
|
-
});
|
|
26
|
-
});
|
package/test/fixtures/sample.csv
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
title,content,category
|
|
2
|
-
"MongoDB Overview","MongoDB is a document database designed for ease of development and scaling.",databases
|
|
3
|
-
"Vector Search","Atlas Vector Search enables semantic search over your data using embeddings.",search
|
|
4
|
-
"Voyage AI","Voyage AI provides state-of-the-art embedding models for retrieval.",ai
|
|
5
|
-
"RAG Explained","RAG combines retrieval with generation for more accurate AI responses.",ai
|
|
6
|
-
"Node.js Intro","Node.js is a JavaScript runtime built on Chrome's V8 engine.",runtime
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{"text": "MongoDB is a document database designed for ease of development and scaling.", "source": "docs"},
|
|
3
|
-
{"text": "Atlas Vector Search enables semantic search over your data using embeddings.", "source": "atlas"},
|
|
4
|
-
{"text": "Voyage AI provides state-of-the-art embedding models for retrieval.", "source": "voyage"},
|
|
5
|
-
{"text": "RAG combines retrieval with generation for more accurate AI responses.", "source": "ai"},
|
|
6
|
-
{"text": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", "source": "nodejs"}
|
|
7
|
-
]
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
{"text": "MongoDB is a document database designed for ease of development and scaling.", "source": "docs"}
|
|
2
|
-
{"text": "Atlas Vector Search enables semantic search over your data using embeddings.", "source": "atlas"}
|
|
3
|
-
{"text": "Voyage AI provides state-of-the-art embedding models for retrieval.", "source": "voyage"}
|
|
4
|
-
{"text": "RAG combines retrieval with generation for more accurate AI responses.", "source": "ai"}
|
|
5
|
-
{"text": "Node.js is a JavaScript runtime built on Chrome's V8 engine.", "source": "nodejs"}
|
package/test/fixtures/sample.txt
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
MongoDB is a document database designed for ease of development and scaling.
|
|
2
|
-
Atlas Vector Search enables semantic search over your data using embeddings.
|
|
3
|
-
Voyage AI provides state-of-the-art embedding models for retrieval.
|
|
4
|
-
RAG combines retrieval with generation for more accurate AI responses.
|
|
5
|
-
Node.js is a JavaScript runtime built on Chrome's V8 engine.
|
package/test/lib/api.test.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach, mock } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
|
|
6
|
-
describe('api', () => {
|
|
7
|
-
let originalKey;
|
|
8
|
-
let originalExit;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
originalKey = process.env.VOYAGE_API_KEY;
|
|
12
|
-
originalExit = process.exit;
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
if (originalKey !== undefined) {
|
|
17
|
-
process.env.VOYAGE_API_KEY = originalKey;
|
|
18
|
-
} else {
|
|
19
|
-
delete process.env.VOYAGE_API_KEY;
|
|
20
|
-
}
|
|
21
|
-
process.exit = originalExit;
|
|
22
|
-
mock.restoreAll();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('requireApiKey', () => {
|
|
26
|
-
it('throws/exits when VOYAGE_API_KEY is not set', () => {
|
|
27
|
-
delete process.env.VOYAGE_API_KEY;
|
|
28
|
-
// Re-require to get fresh modules (clear config cache too)
|
|
29
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
30
|
-
delete require.cache[require.resolve('../../src/lib/config')];
|
|
31
|
-
const config = require('../../src/lib/config');
|
|
32
|
-
const originalGetConfigValue = config.getConfigValue;
|
|
33
|
-
config.getConfigValue = () => undefined;
|
|
34
|
-
|
|
35
|
-
const { requireApiKey } = require('../../src/lib/api');
|
|
36
|
-
|
|
37
|
-
let exitCode = null;
|
|
38
|
-
process.exit = (code) => {
|
|
39
|
-
exitCode = code;
|
|
40
|
-
throw new Error('process.exit called');
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
assert.throws(() => requireApiKey(), /process\.exit called/);
|
|
45
|
-
assert.equal(exitCode, 1);
|
|
46
|
-
} finally {
|
|
47
|
-
config.getConfigValue = originalGetConfigValue;
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('returns key when VOYAGE_API_KEY is set', () => {
|
|
52
|
-
process.env.VOYAGE_API_KEY = 'test-key-123';
|
|
53
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
54
|
-
const { requireApiKey } = require('../../src/lib/api');
|
|
55
|
-
|
|
56
|
-
const key = requireApiKey();
|
|
57
|
-
assert.equal(key, 'test-key-123');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('apiRequest', () => {
|
|
62
|
-
it('returns parsed JSON on success', async () => {
|
|
63
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
64
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
65
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
66
|
-
|
|
67
|
-
const mockResponse = {
|
|
68
|
-
ok: true,
|
|
69
|
-
status: 200,
|
|
70
|
-
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
|
|
71
|
-
headers: new Map(),
|
|
72
|
-
};
|
|
73
|
-
mock.method(global, 'fetch', async () => mockResponse);
|
|
74
|
-
|
|
75
|
-
const result = await apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' });
|
|
76
|
-
assert.deepEqual(result, { data: [{ embedding: [1, 2, 3] }] });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('exits on non-200 response', async () => {
|
|
80
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
81
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
82
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
83
|
-
|
|
84
|
-
let exitCode = null;
|
|
85
|
-
process.exit = (code) => {
|
|
86
|
-
exitCode = code;
|
|
87
|
-
throw new Error('process.exit called');
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const mockResponse = {
|
|
91
|
-
ok: false,
|
|
92
|
-
status: 400,
|
|
93
|
-
json: async () => ({ detail: 'Bad request' }),
|
|
94
|
-
headers: new Map(),
|
|
95
|
-
};
|
|
96
|
-
mock.method(global, 'fetch', async () => mockResponse);
|
|
97
|
-
|
|
98
|
-
await assert.rejects(
|
|
99
|
-
() => apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' }),
|
|
100
|
-
/API Error \(400\)/
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('retries on 429', async () => {
|
|
105
|
-
process.env.VOYAGE_API_KEY = 'test-key';
|
|
106
|
-
delete require.cache[require.resolve('../../src/lib/api')];
|
|
107
|
-
const { apiRequest } = require('../../src/lib/api');
|
|
108
|
-
|
|
109
|
-
let callCount = 0;
|
|
110
|
-
mock.method(global, 'fetch', async () => {
|
|
111
|
-
callCount++;
|
|
112
|
-
if (callCount === 1) {
|
|
113
|
-
return {
|
|
114
|
-
ok: false,
|
|
115
|
-
status: 429,
|
|
116
|
-
headers: { get: () => '0' },
|
|
117
|
-
json: async () => ({ detail: 'Rate limited' }),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
ok: true,
|
|
122
|
-
status: 200,
|
|
123
|
-
json: async () => ({ data: 'success' }),
|
|
124
|
-
headers: new Map(),
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const result = await apiRequest('/embeddings', { input: ['test'], model: 'voyage-4-lite' });
|
|
129
|
-
assert.deepEqual(result, { data: 'success' });
|
|
130
|
-
assert.equal(callCount, 2, 'Should have retried once');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
package/test/lib/banner.test.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const { showBanner, showQuickStart, getVersion } = require('../../src/lib/banner');
|
|
6
|
-
|
|
7
|
-
describe('banner', () => {
|
|
8
|
-
let originalLog;
|
|
9
|
-
let output;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
originalLog = console.log;
|
|
13
|
-
output = [];
|
|
14
|
-
console.log = (...args) => output.push(args.join(' '));
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
console.log = originalLog;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('getVersion returns a semver string', () => {
|
|
22
|
-
const version = getVersion();
|
|
23
|
-
assert.ok(/^\d+\.\d+\.\d+/.test(version), `Expected semver, got: ${version}`);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('showBanner prints box with vai and Voyage AI CLI', () => {
|
|
27
|
-
showBanner();
|
|
28
|
-
const combined = output.join('\n');
|
|
29
|
-
assert.ok(combined.includes('vai'), 'Should include "vai"');
|
|
30
|
-
assert.ok(combined.includes('Voyage AI CLI'), 'Should include "Voyage AI CLI"');
|
|
31
|
-
assert.ok(combined.includes('╭'), 'Should include top border');
|
|
32
|
-
assert.ok(combined.includes('╰'), 'Should include bottom border');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('showQuickStart prints quick start commands', () => {
|
|
36
|
-
showQuickStart();
|
|
37
|
-
const combined = output.join('\n');
|
|
38
|
-
assert.ok(combined.includes('Quick start'), 'Should include header');
|
|
39
|
-
assert.ok(combined.includes('vai ping'), 'Should include ping');
|
|
40
|
-
assert.ok(combined.includes('vai embed'), 'Should include embed');
|
|
41
|
-
assert.ok(combined.includes('vai models'), 'Should include models');
|
|
42
|
-
assert.ok(combined.includes('vai demo'), 'Should include demo');
|
|
43
|
-
});
|
|
44
|
-
});
|