voyageai-cli 1.12.1 → 1.13.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.
@@ -1,124 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it, beforeEach, afterEach } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
-
9
- const {
10
- loadConfig,
11
- saveConfig,
12
- getConfigValue,
13
- setConfigValue,
14
- deleteConfigValue,
15
- maskSecret,
16
- } = require('../../src/lib/config');
17
-
18
- describe('config lib', () => {
19
- let tmpDir;
20
- let tmpConfigPath;
21
-
22
- beforeEach(() => {
23
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vai-test-'));
24
- tmpConfigPath = path.join(tmpDir, 'config.json');
25
- });
26
-
27
- afterEach(() => {
28
- fs.rmSync(tmpDir, { recursive: true, force: true });
29
- });
30
-
31
- describe('loadConfig', () => {
32
- it('returns {} when config file does not exist', () => {
33
- const config = loadConfig(tmpConfigPath);
34
- assert.deepEqual(config, {});
35
- });
36
-
37
- it('returns parsed config when file exists', () => {
38
- fs.writeFileSync(tmpConfigPath, JSON.stringify({ apiKey: 'test' }));
39
- const config = loadConfig(tmpConfigPath);
40
- assert.deepEqual(config, { apiKey: 'test' });
41
- });
42
- });
43
-
44
- describe('saveConfig', () => {
45
- it('creates directory and file', () => {
46
- const nestedPath = path.join(tmpDir, 'nested', 'config.json');
47
- saveConfig({ foo: 'bar' }, nestedPath);
48
- assert.ok(fs.existsSync(nestedPath));
49
- const data = JSON.parse(fs.readFileSync(nestedPath, 'utf-8'));
50
- assert.deepEqual(data, { foo: 'bar' });
51
- });
52
-
53
- it('sets file permissions to 600', () => {
54
- saveConfig({ secret: 'value' }, tmpConfigPath);
55
- const stats = fs.statSync(tmpConfigPath);
56
- const mode = (stats.mode & 0o777).toString(8);
57
- assert.equal(mode, '600');
58
- });
59
- });
60
-
61
- describe('setConfigValue / getConfigValue', () => {
62
- it('round-trips a value', () => {
63
- setConfigValue('apiKey', 'my-secret-key', tmpConfigPath);
64
- const value = getConfigValue('apiKey', tmpConfigPath);
65
- assert.equal(value, 'my-secret-key');
66
- });
67
-
68
- it('returns undefined for missing key', () => {
69
- const value = getConfigValue('nonExistent', tmpConfigPath);
70
- assert.equal(value, undefined);
71
- });
72
-
73
- it('handles multiple values', () => {
74
- setConfigValue('apiKey', 'key1', tmpConfigPath);
75
- setConfigValue('mongodbUri', 'mongodb://localhost', tmpConfigPath);
76
- assert.equal(getConfigValue('apiKey', tmpConfigPath), 'key1');
77
- assert.equal(getConfigValue('mongodbUri', tmpConfigPath), 'mongodb://localhost');
78
- });
79
-
80
- it('overwrites existing value', () => {
81
- setConfigValue('apiKey', 'old', tmpConfigPath);
82
- setConfigValue('apiKey', 'new', tmpConfigPath);
83
- assert.equal(getConfigValue('apiKey', tmpConfigPath), 'new');
84
- });
85
- });
86
-
87
- describe('deleteConfigValue', () => {
88
- it('removes a key', () => {
89
- setConfigValue('apiKey', 'val', tmpConfigPath);
90
- deleteConfigValue('apiKey', tmpConfigPath);
91
- assert.equal(getConfigValue('apiKey', tmpConfigPath), undefined);
92
- });
93
-
94
- it('does not affect other keys', () => {
95
- setConfigValue('apiKey', 'key', tmpConfigPath);
96
- setConfigValue('mongodbUri', 'uri', tmpConfigPath);
97
- deleteConfigValue('apiKey', tmpConfigPath);
98
- assert.equal(getConfigValue('mongodbUri', tmpConfigPath), 'uri');
99
- });
100
- });
101
-
102
- describe('maskSecret', () => {
103
- it('masks long strings: first 4 + ... + last 4', () => {
104
- assert.equal(maskSecret('al-EdFh1FwUCPTZw7ofd93ulmRNxEmt-JOCRmmWc96wWJ8'), 'al-E...wWJ8');
105
- });
106
-
107
- it('masks strings of exactly 10 chars', () => {
108
- assert.equal(maskSecret('1234567890'), '1234...7890');
109
- });
110
-
111
- it('returns **** for strings shorter than 10 chars', () => {
112
- assert.equal(maskSecret('short'), '****');
113
- assert.equal(maskSecret('123456789'), '****');
114
- });
115
-
116
- it('returns **** for empty string', () => {
117
- assert.equal(maskSecret(''), '****');
118
- });
119
-
120
- it('converts non-string values to string', () => {
121
- assert.equal(maskSecret(12345), '12345');
122
- });
123
- });
124
- });
@@ -1,141 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const { concepts, aliases, resolveConcept, listConcepts, getConcept } = require('../../src/lib/explanations');
6
-
7
- describe('explanations', () => {
8
- const expectedConcepts = [
9
- 'embeddings',
10
- 'reranking',
11
- 'vector-search',
12
- 'rag',
13
- 'cosine-similarity',
14
- 'two-stage-retrieval',
15
- 'input-type',
16
- 'models',
17
- 'api-keys',
18
- 'api-access',
19
- 'batch-processing',
20
- 'quantization',
21
- 'benchmarking',
22
- ];
23
-
24
- it('has all expected concepts', () => {
25
- for (const key of expectedConcepts) {
26
- assert.ok(concepts[key], `Missing concept: ${key}`);
27
- }
28
- });
29
-
30
- it('listConcepts returns all concept keys', () => {
31
- const keys = listConcepts();
32
- assert.equal(keys.length, expectedConcepts.length);
33
- for (const key of expectedConcepts) {
34
- assert.ok(keys.includes(key), `listConcepts should include ${key}`);
35
- }
36
- });
37
-
38
- it('all concepts have required fields: title, summary, content, links, tryIt', () => {
39
- for (const key of expectedConcepts) {
40
- const concept = concepts[key];
41
- assert.ok(concept.title, `${key} should have title`);
42
- assert.ok(typeof concept.title === 'string', `${key}.title should be a string`);
43
- assert.ok(concept.summary, `${key} should have summary`);
44
- assert.ok(typeof concept.summary === 'string', `${key}.summary should be a string`);
45
- assert.ok(concept.content, `${key} should have content`);
46
- assert.ok(typeof concept.content === 'string', `${key}.content should be a string`);
47
- assert.ok(Array.isArray(concept.links), `${key}.links should be an array`);
48
- assert.ok(concept.links.length > 0, `${key}.links should not be empty`);
49
- assert.ok(Array.isArray(concept.tryIt), `${key}.tryIt should be an array`);
50
- assert.ok(concept.tryIt.length > 0, `${key}.tryIt should not be empty`);
51
- }
52
- });
53
-
54
- it('all concepts have substantial content (at least 200 chars)', () => {
55
- const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
56
- for (const key of expectedConcepts) {
57
- const plainContent = stripAnsi(concepts[key].content);
58
- assert.ok(
59
- plainContent.length >= 200,
60
- `${key} content should be substantial (got ${plainContent.length} chars)`
61
- );
62
- }
63
- });
64
-
65
- it('getConcept returns concept for valid key', () => {
66
- const concept = getConcept('embeddings');
67
- assert.ok(concept);
68
- assert.equal(concept.title, 'Embeddings');
69
- });
70
-
71
- it('getConcept returns null for invalid key', () => {
72
- const concept = getConcept('nonexistent');
73
- assert.equal(concept, null);
74
- });
75
-
76
- describe('alias resolution', () => {
77
- const expectedAliases = {
78
- embed: 'embeddings',
79
- embedding: 'embeddings',
80
- rerank: 'reranking',
81
- vectors: 'vector-search',
82
- search: 'vector-search',
83
- cosine: 'cosine-similarity',
84
- similarity: 'cosine-similarity',
85
- 'two-stage': 'two-stage-retrieval',
86
- keys: 'api-keys',
87
- access: 'api-access',
88
- auth: 'api-access',
89
- 'atlas-vs-voyage': 'api-access',
90
- endpoint: 'api-access',
91
- batch: 'batch-processing',
92
- model: 'models',
93
- batching: 'batch-processing',
94
- quantize: 'quantization',
95
- int8: 'quantization',
96
- binary: 'quantization',
97
- matryoshka: 'quantization',
98
- dtype: 'quantization',
99
- };
100
-
101
- it('alias map covers expected aliases', () => {
102
- for (const [alias, expected] of Object.entries(expectedAliases)) {
103
- assert.ok(aliases[alias], `Alias map should include "${alias}"`);
104
- assert.equal(aliases[alias], expected, `Alias "${alias}" should resolve to "${expected}"`);
105
- }
106
- });
107
-
108
- it('resolveConcept resolves direct keys', () => {
109
- for (const key of expectedConcepts) {
110
- assert.equal(resolveConcept(key), key, `Direct key "${key}" should resolve to itself`);
111
- }
112
- });
113
-
114
- it('resolveConcept resolves aliases', () => {
115
- for (const [alias, expected] of Object.entries(expectedAliases)) {
116
- assert.equal(
117
- resolveConcept(alias),
118
- expected,
119
- `Alias "${alias}" should resolve to "${expected}"`
120
- );
121
- }
122
- });
123
-
124
- it('resolveConcept is case-insensitive', () => {
125
- assert.equal(resolveConcept('EMBEDDINGS'), 'embeddings');
126
- assert.equal(resolveConcept('Rerank'), 'reranking');
127
- assert.equal(resolveConcept('RAG'), 'rag');
128
- });
129
-
130
- it('resolveConcept returns null for unknown input', () => {
131
- assert.equal(resolveConcept('nonexistent'), null);
132
- assert.equal(resolveConcept('foobar'), null);
133
- });
134
-
135
- it('resolveConcept returns null for empty/null input', () => {
136
- assert.equal(resolveConcept(''), null);
137
- assert.equal(resolveConcept(null), null);
138
- assert.equal(resolveConcept(undefined), null);
139
- });
140
- });
141
- });
@@ -1,75 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const { formatTable } = require('../../src/lib/format');
6
-
7
- describe('formatTable', () => {
8
- it('formats a simple table', () => {
9
- const headers = ['Name', 'Age'];
10
- const rows = [
11
- ['Alice', '30'],
12
- ['Bob', '25'],
13
- ];
14
- const result = formatTable(headers, rows);
15
- assert.ok(result.includes('Alice'));
16
- assert.ok(result.includes('Bob'));
17
- assert.ok(result.includes('Name'));
18
- assert.ok(result.includes('Age'));
19
- });
20
-
21
- it('handles empty rows', () => {
22
- const headers = ['Col1', 'Col2'];
23
- const rows = [];
24
- const result = formatTable(headers, rows);
25
- // Should still have the header and separator
26
- const lines = result.split('\n');
27
- assert.equal(lines.length, 2); // header + separator, no data lines
28
- assert.ok(lines[0].includes('Col1'));
29
- assert.ok(lines[0].includes('Col2'));
30
- });
31
-
32
- it('handles varying column widths', () => {
33
- const headers = ['X', 'Long Header'];
34
- const rows = [
35
- ['Short', 'Y'],
36
- ['A very long cell value', 'Z'],
37
- ];
38
- const result = formatTable(headers, rows);
39
- // The widest cell should determine column width
40
- assert.ok(result.includes('A very long cell value'));
41
- // All rows should have consistent separators
42
- const lines = result.split('\n');
43
- assert.equal(lines.length, 4); // header + separator + 2 data rows
44
- });
45
-
46
- it('uses correct separator character', () => {
47
- const headers = ['A', 'B'];
48
- const rows = [['1', '2']];
49
- const result = formatTable(headers, rows);
50
- const lines = result.split('\n');
51
- // Separator line should contain ─ and ┼
52
- assert.ok(lines[1].includes('─'));
53
- assert.ok(lines[1].includes('┼'));
54
- });
55
-
56
- it('uses │ as column separator in data lines', () => {
57
- const headers = ['A', 'B'];
58
- const rows = [['1', '2']];
59
- const result = formatTable(headers, rows);
60
- const lines = result.split('\n');
61
- // Header and data lines should use │
62
- assert.ok(lines[0].includes('│'));
63
- assert.ok(lines[2].includes('│'));
64
- });
65
-
66
- it('pads cells correctly', () => {
67
- const headers = ['Name', 'Value'];
68
- const rows = [['A', 'B']];
69
- const result = formatTable(headers, rows);
70
- const lines = result.split('\n');
71
- // Each cell should be padded to column width
72
- // "Name" is 4 chars, "A" should be padded to 4
73
- assert.ok(lines[2].includes(' A ') || lines[2].includes(' A '));
74
- });
75
- });
@@ -1,48 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it, after } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const fs = require('fs');
6
- const os = require('os');
7
- const path = require('path');
8
- const { resolveTextInput } = require('../../src/lib/input');
9
-
10
- describe('resolveTextInput', () => {
11
- const tmpFiles = [];
12
-
13
- function createTempFile(content) {
14
- const tmpPath = path.join(os.tmpdir(), `vai-test-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
15
- fs.writeFileSync(tmpPath, content, 'utf-8');
16
- tmpFiles.push(tmpPath);
17
- return tmpPath;
18
- }
19
-
20
- after(() => {
21
- for (const f of tmpFiles) {
22
- try { fs.unlinkSync(f); } catch { /* ignore */ }
23
- }
24
- });
25
-
26
- it('returns direct text argument as array', async () => {
27
- const result = await resolveTextInput('hello world', undefined);
28
- assert.deepEqual(result, ['hello world']);
29
- });
30
-
31
- it('reads from file path', async () => {
32
- const tmpPath = createTempFile('file content here');
33
- const result = await resolveTextInput(undefined, tmpPath);
34
- assert.deepEqual(result, ['file content here']);
35
- });
36
-
37
- it('trims file content', async () => {
38
- const tmpPath = createTempFile(' trimmed \n');
39
- const result = await resolveTextInput(undefined, tmpPath);
40
- assert.deepEqual(result, ['trimmed']);
41
- });
42
-
43
- it('prefers file over text argument', async () => {
44
- const tmpPath = createTempFile('from file');
45
- const result = await resolveTextInput('from arg', tmpPath);
46
- assert.deepEqual(result, ['from file']);
47
- });
48
- });
@@ -1,43 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const { cosineSimilarity } = require('../../src/lib/math');
6
-
7
- describe('cosineSimilarity', () => {
8
- it('returns 1.0 for identical vectors', () => {
9
- const v = [1, 2, 3, 4, 5];
10
- const result = cosineSimilarity(v, v);
11
- assert.ok(Math.abs(result - 1.0) < 1e-10, `Expected ~1.0, got ${result}`);
12
- });
13
-
14
- it('returns 0.0 for orthogonal vectors', () => {
15
- const a = [1, 0, 0];
16
- const b = [0, 1, 0];
17
- const result = cosineSimilarity(a, b);
18
- assert.ok(Math.abs(result) < 1e-10, `Expected ~0.0, got ${result}`);
19
- });
20
-
21
- it('returns -1.0 for opposite vectors', () => {
22
- const a = [1, 2, 3];
23
- const b = [-1, -2, -3];
24
- const result = cosineSimilarity(a, b);
25
- assert.ok(Math.abs(result - (-1.0)) < 1e-10, `Expected ~-1.0, got ${result}`);
26
- });
27
-
28
- it('computes correct value for known vectors', () => {
29
- // cos([1,0], [1,1]) = 1 / (1 * sqrt(2)) ≈ 0.7071
30
- const a = [1, 0];
31
- const b = [1, 1];
32
- const result = cosineSimilarity(a, b);
33
- const expected = 1 / Math.sqrt(2);
34
- assert.ok(Math.abs(result - expected) < 1e-10, `Expected ~${expected}, got ${result}`);
35
- });
36
-
37
- it('returns ~1.0 for vectors of different magnitudes but same direction', () => {
38
- const a = [1, 2, 3];
39
- const b = [10, 20, 30];
40
- const result = cosineSimilarity(a, b);
41
- assert.ok(Math.abs(result - 1.0) < 1e-10, `Expected ~1.0, got ${result}`);
42
- });
43
- });
@@ -1,79 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const ui = require('../../src/lib/ui');
6
-
7
- describe('ui helpers', () => {
8
- it('ui.success() contains ✓', () => {
9
- const result = ui.success('done');
10
- assert.ok(result.includes('✓'), `Expected "✓" in: ${result}`);
11
- assert.ok(result.includes('done'), `Expected "done" in: ${result}`);
12
- });
13
-
14
- it('ui.error() contains ✗', () => {
15
- const result = ui.error('fail');
16
- assert.ok(result.includes('✗'), `Expected "✗" in: ${result}`);
17
- assert.ok(result.includes('fail'), `Expected "fail" in: ${result}`);
18
- });
19
-
20
- it('ui.warn() contains ⚠', () => {
21
- const result = ui.warn('careful');
22
- assert.ok(result.includes('⚠'), `Expected "⚠" in: ${result}`);
23
- });
24
-
25
- it('ui.info() contains ℹ', () => {
26
- const result = ui.info('note');
27
- assert.ok(result.includes('ℹ'), `Expected "ℹ" in: ${result}`);
28
- });
29
-
30
- it('ui.score() returns a string for various values', () => {
31
- const high = ui.score(0.85);
32
- assert.equal(typeof high, 'string');
33
- assert.ok(high.includes('0.850000'), `Expected "0.850000" in: ${high}`);
34
-
35
- const mid = ui.score(0.55);
36
- assert.equal(typeof mid, 'string');
37
- assert.ok(mid.includes('0.550000'), `Expected "0.550000" in: ${mid}`);
38
-
39
- const low = ui.score(0.1);
40
- assert.equal(typeof low, 'string');
41
- assert.ok(low.includes('0.100000'), `Expected "0.100000" in: ${low}`);
42
- });
43
-
44
- it('ui.label() formats key: value with indentation', () => {
45
- const result = ui.label('Model', 'voyage-4');
46
- assert.ok(result.includes('Model'), `Expected "Model" in: ${result}`);
47
- assert.ok(result.includes(':'), `Expected ":" in: ${result}`);
48
- assert.ok(result.includes('voyage-4'), `Expected "voyage-4" in: ${result}`);
49
- // Should start with spaces (indentation)
50
- assert.ok(result.startsWith(' '), `Expected leading spaces in: ${result}`);
51
- });
52
-
53
- it('ui.status() colors READY green, BUILDING yellow, FAILED red', () => {
54
- const ready = ui.status('READY');
55
- assert.equal(typeof ready, 'string');
56
- assert.ok(ready.includes('READY'), `Expected "READY" in: ${ready}`);
57
-
58
- const building = ui.status('BUILDING');
59
- assert.ok(building.includes('BUILDING'), `Expected "BUILDING" in: ${building}`);
60
-
61
- const failed = ui.status('FAILED');
62
- assert.ok(failed.includes('FAILED'), `Expected "FAILED" in: ${failed}`);
63
- });
64
-
65
- it('ui.spinner() returns an object with start and stop methods', () => {
66
- const spin = ui.spinner('loading...');
67
- assert.equal(typeof spin.start, 'function', 'spinner should have start()');
68
- assert.equal(typeof spin.stop, 'function', 'spinner should have stop()');
69
- });
70
-
71
- it('ui style functions exist and return strings', () => {
72
- assert.equal(typeof ui.bold('x'), 'string');
73
- assert.equal(typeof ui.dim('x'), 'string');
74
- assert.equal(typeof ui.green('x'), 'string');
75
- assert.equal(typeof ui.red('x'), 'string');
76
- assert.equal(typeof ui.cyan('x'), 'string');
77
- assert.equal(typeof ui.yellow('x'), 'string');
78
- });
79
- });
Binary file
package/voyageai-cli.png DELETED
Binary file