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 +38 -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 +2 -0
- package/src/commands/completions.js +463 -0
- package/src/commands/embed.js +16 -2
- package/src/commands/models.js +86 -8
- package/src/commands/ping.js +5 -4
- package/src/commands/rerank.js +12 -2
- package/src/lib/api.js +52 -2
- package/src/lib/catalog.js +19 -10
- package/src/lib/config.js +1 -0
- package/src/lib/explanations.js +11 -8
- package/test/commands/completions.test.js +166 -0
- package/test/commands/embed.test.js +32 -0
- package/test/commands/ingest.test.js +13 -0
- package/test/commands/models.test.js +43 -0
- package/test/commands/ping.test.js +24 -11
- package/test/commands/rerank.test.js +32 -0
- package/test/commands/store.test.js +26 -0
- package/test/lib/api.test.js +12 -3
- package/test/lib/catalog.test.js +32 -0
|
@@ -86,4 +86,47 @@ describe('models command', () => {
|
|
|
86
86
|
assert.ok(parsed[0].name);
|
|
87
87
|
assert.ok(parsed[0].type);
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
it('hides legacy models by default', async () => {
|
|
91
|
+
const program = new Command();
|
|
92
|
+
program.exitOverride();
|
|
93
|
+
registerModels(program);
|
|
94
|
+
|
|
95
|
+
await program.parseAsync(['node', 'test', 'models', '--quiet']);
|
|
96
|
+
|
|
97
|
+
const combined = output.join('\n');
|
|
98
|
+
assert.ok(combined.includes('voyage-4-large'), 'Should include current models');
|
|
99
|
+
assert.ok(!combined.includes('voyage-3-large'), 'Should hide legacy voyage-3-large');
|
|
100
|
+
assert.ok(!combined.includes('rerank-2-lite'), 'Should hide legacy rerank-2-lite');
|
|
101
|
+
assert.ok(!combined.includes('voyage-code-2'), 'Should hide legacy voyage-code-2');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('shows legacy models when --all is used', async () => {
|
|
105
|
+
const program = new Command();
|
|
106
|
+
program.exitOverride();
|
|
107
|
+
registerModels(program);
|
|
108
|
+
|
|
109
|
+
await program.parseAsync(['node', 'test', 'models', '--all', '--quiet']);
|
|
110
|
+
|
|
111
|
+
const combined = output.join('\n');
|
|
112
|
+
assert.ok(combined.includes('voyage-4-large'), 'Should include current models');
|
|
113
|
+
assert.ok(combined.includes('voyage-3-large'), 'Should include legacy voyage-3-large');
|
|
114
|
+
assert.ok(combined.includes('rerank-2'), 'Should include legacy rerank-2');
|
|
115
|
+
assert.ok(combined.includes('Legacy Models'), 'Should show legacy header');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('--all with --json shows legacy models in JSON', async () => {
|
|
119
|
+
const program = new Command();
|
|
120
|
+
program.exitOverride();
|
|
121
|
+
registerModels(program);
|
|
122
|
+
|
|
123
|
+
await program.parseAsync(['node', 'test', 'models', '--all', '--json']);
|
|
124
|
+
|
|
125
|
+
const combined = output.join('\n');
|
|
126
|
+
const parsed = JSON.parse(combined);
|
|
127
|
+
assert.ok(Array.isArray(parsed));
|
|
128
|
+
const legacyNames = parsed.filter(m => m.legacy).map(m => m.name);
|
|
129
|
+
assert.ok(legacyNames.includes('voyage-3-large'), 'JSON should include legacy models');
|
|
130
|
+
assert.ok(legacyNames.includes('rerank-2'), 'JSON should include legacy rerankers');
|
|
131
|
+
});
|
|
89
132
|
});
|
|
@@ -105,8 +105,17 @@ describe('ping command', () => {
|
|
|
105
105
|
assert.ok(combined.includes('Authentication failed'), 'Should show auth error');
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
it('exits when VOYAGE_API_KEY is not set', async () => {
|
|
108
|
+
it('exits when VOYAGE_API_KEY is not set and no config', async () => {
|
|
109
109
|
delete process.env.VOYAGE_API_KEY;
|
|
110
|
+
// Mock config to return nothing so the key isn't found in ~/.vai/config.json
|
|
111
|
+
delete require.cache[require.resolve('../../src/lib/config')];
|
|
112
|
+
delete require.cache[require.resolve('../../src/lib/api')];
|
|
113
|
+
delete require.cache[require.resolve('../../src/commands/ping')];
|
|
114
|
+
const config = require('../../src/lib/config');
|
|
115
|
+
const origGetConfigValue = config.getConfigValue;
|
|
116
|
+
config.getConfigValue = () => undefined;
|
|
117
|
+
|
|
118
|
+
const { registerPing: registerPingFresh } = require('../../src/commands/ping');
|
|
110
119
|
|
|
111
120
|
let exitCode = null;
|
|
112
121
|
process.exit = (code) => {
|
|
@@ -116,16 +125,20 @@ describe('ping command', () => {
|
|
|
116
125
|
|
|
117
126
|
const program = new Command();
|
|
118
127
|
program.exitOverride();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
registerPingFresh(program);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await assert.rejects(
|
|
132
|
+
() => program.parseAsync(['node', 'test', 'ping']),
|
|
133
|
+
/process\.exit called/
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
assert.equal(exitCode, 1);
|
|
137
|
+
const combined = errorOutput.join('\n');
|
|
138
|
+
assert.ok(combined.includes('VOYAGE_API_KEY'), 'Should mention missing key');
|
|
139
|
+
} finally {
|
|
140
|
+
config.getConfigValue = origGetConfigValue;
|
|
141
|
+
}
|
|
129
142
|
});
|
|
130
143
|
|
|
131
144
|
it('outputs JSON when --json flag is used', async () => {
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
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/lib/api.test.js
CHANGED
|
@@ -25,8 +25,13 @@ describe('api', () => {
|
|
|
25
25
|
describe('requireApiKey', () => {
|
|
26
26
|
it('throws/exits when VOYAGE_API_KEY is not set', () => {
|
|
27
27
|
delete process.env.VOYAGE_API_KEY;
|
|
28
|
-
// Re-require to get fresh
|
|
28
|
+
// Re-require to get fresh modules (clear config cache too)
|
|
29
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
|
+
|
|
30
35
|
const { requireApiKey } = require('../../src/lib/api');
|
|
31
36
|
|
|
32
37
|
let exitCode = null;
|
|
@@ -35,8 +40,12 @@ describe('api', () => {
|
|
|
35
40
|
throw new Error('process.exit called');
|
|
36
41
|
};
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
try {
|
|
44
|
+
assert.throws(() => requireApiKey(), /process\.exit called/);
|
|
45
|
+
assert.equal(exitCode, 1);
|
|
46
|
+
} finally {
|
|
47
|
+
config.getConfigValue = originalGetConfigValue;
|
|
48
|
+
}
|
|
40
49
|
});
|
|
41
50
|
|
|
42
51
|
it('returns key when VOYAGE_API_KEY is set', () => {
|
package/test/lib/catalog.test.js
CHANGED
|
@@ -64,4 +64,36 @@ describe('catalog', () => {
|
|
|
64
64
|
const rerankCount = MODEL_CATALOG.filter(m => m.type === 'reranking').length;
|
|
65
65
|
assert.ok(embedCount > rerankCount);
|
|
66
66
|
});
|
|
67
|
+
|
|
68
|
+
it('contains voyage-4-nano as current model', () => {
|
|
69
|
+
const nano = MODEL_CATALOG.find(m => m.name === 'voyage-4-nano');
|
|
70
|
+
assert.ok(nano, 'Should have voyage-4-nano');
|
|
71
|
+
assert.equal(nano.type, 'embedding');
|
|
72
|
+
assert.ok(!nano.legacy, 'voyage-4-nano should not be legacy');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('contains legacy models with legacy flag', () => {
|
|
76
|
+
const legacyModels = MODEL_CATALOG.filter(m => m.legacy);
|
|
77
|
+
assert.ok(legacyModels.length > 0, 'Should have legacy models');
|
|
78
|
+
|
|
79
|
+
const legacyNames = legacyModels.map(m => m.name);
|
|
80
|
+
assert.ok(legacyNames.includes('voyage-3-large'), 'Should have voyage-3-large');
|
|
81
|
+
assert.ok(legacyNames.includes('voyage-3.5'), 'Should have voyage-3.5');
|
|
82
|
+
assert.ok(legacyNames.includes('voyage-3.5-lite'), 'Should have voyage-3.5-lite');
|
|
83
|
+
assert.ok(legacyNames.includes('voyage-code-2'), 'Should have voyage-code-2');
|
|
84
|
+
assert.ok(legacyNames.includes('voyage-multimodal-3'), 'Should have voyage-multimodal-3');
|
|
85
|
+
assert.ok(legacyNames.includes('rerank-2'), 'Should have rerank-2');
|
|
86
|
+
assert.ok(legacyNames.includes('rerank-2-lite'), 'Should have rerank-2-lite');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('legacy models have required fields', () => {
|
|
90
|
+
const legacyModels = MODEL_CATALOG.filter(m => m.legacy);
|
|
91
|
+
for (const model of legacyModels) {
|
|
92
|
+
assert.ok(model.name, `Legacy model missing name`);
|
|
93
|
+
assert.ok(model.type, `Legacy model ${model.name} missing type`);
|
|
94
|
+
assert.ok(model.context, `Legacy model ${model.name} missing context`);
|
|
95
|
+
assert.ok(model.price, `Legacy model ${model.name} missing price`);
|
|
96
|
+
assert.ok(model.bestFor, `Legacy model ${model.name} missing bestFor`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
67
99
|
});
|