@satori-sh/cli 0.0.6 → 0.0.8

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,322 +0,0 @@
1
- import { test, expect, mock, beforeEach, afterEach } from 'bun:test';
2
- import { main, searchMemories, addMemories, enhanceMessagesWithMemory } from '../src/index';
3
- import { getConfig, checkWriteAccess, saveApiKey } from '../src/config.js';
4
-
5
- let originalFetch: typeof global.fetch;
6
- let originalEnv: NodeJS.ProcessEnv;
7
- let originalArgv: string[];
8
- let originalPlatform: string;
9
-
10
- beforeEach(() => {
11
- originalFetch = global.fetch;
12
- originalEnv = { ...process.env };
13
- originalArgv = [...process.argv];
14
- originalPlatform = process.platform;
15
- });
16
-
17
- afterEach(() => {
18
- global.fetch = originalFetch;
19
- process.env = originalEnv;
20
- process.argv = originalArgv;
21
- (process as any).platform = originalPlatform;
22
- });
23
-
24
-
25
-
26
- test('error handling for invalid base URL', async () => {
27
- process.env.SATORI_BASE_URL = 'invalid-url';
28
- process.argv = ['node', 'index.js', 'query'];
29
- const errorSpy = mock(() => {});
30
- console.error = errorSpy;
31
- const exitSpy = mock(() => { throw new Error('exit'); });
32
- process.exit = exitSpy;
33
-
34
- try {
35
- await main();
36
- expect(true).toBe(false); // should not reach
37
- } catch (e) {
38
- expect((e as Error).message).toBe('exit');
39
- }
40
- });
41
-
42
- test('error handling for invalid base URL', async () => {
43
- process.env.SATORI_API_KEY = 'test-key';
44
- process.env.SATORI_BASE_URL = 'invalid-url';
45
- process.argv = ['node', 'index.js', 'query'];
46
- const errorSpy = mock(() => {});
47
- console.error = errorSpy;
48
- const exitSpy = mock(() => { throw new Error('exit'); });
49
- process.exit = exitSpy;
50
-
51
- try {
52
- await main();
53
- expect(true).toBe(false); // should not reach
54
- } catch (e) {
55
- expect((e as Error).message).toBe('exit');
56
- }
57
- });
58
-
59
- test('search with empty query', async () => {
60
- process.env.SATORI_API_KEY = 'satori-erQCxG3GYryoXt4JydgEsbRmg5LuJf6p';
61
- const errorSpy = mock(() => {});
62
- console.error = errorSpy;
63
-
64
- await searchMemories('');
65
-
66
- expect(errorSpy).toHaveBeenCalledWith('Query cannot be empty');
67
- });
68
-
69
- test('add with empty text', async () => {
70
- process.env.SATORI_API_KEY = 'satori-erQCxG3GYryoXt4JydgEsbRmg5LuJf6p';
71
- const errorSpy = mock(() => {});
72
- console.error = errorSpy;
73
-
74
- await addMemories('');
75
-
76
- expect(errorSpy).toHaveBeenCalledWith('Text cannot be empty');
77
- });
78
-
79
- test('HTTP 401 error', async () => {
80
- process.env.SATORI_API_KEY = 'test-key';
81
- const fetchMock = Object.assign(
82
- mock(() => Promise.resolve(new Response('Unauthorized', { status: 401, statusText: 'Unauthorized' }))),
83
- { preconnect: mock(() => {}) }
84
- ) as typeof fetch;
85
- globalThis.fetch = fetchMock;
86
- const errorSpy = mock(() => {});
87
- console.error = errorSpy;
88
-
89
- await searchMemories('test');
90
-
91
- expect(errorSpy).toHaveBeenCalledWith('HTTP error: 401 Unauthorized');
92
- });
93
-
94
- test('HTTP 500 error', async () => {
95
- process.env.SATORI_API_KEY = 'test-key';
96
- const fetchMock = Object.assign(
97
- mock(() => Promise.resolve(new Response('Internal Server Error', { status: 500, statusText: 'Internal Server Error' }))),
98
- { preconnect: mock(() => {}) }
99
- ) as typeof fetch;
100
- globalThis.fetch = fetchMock;
101
- const errorSpy = mock(() => {});
102
- console.error = errorSpy;
103
-
104
- await searchMemories('test');
105
-
106
- expect(errorSpy).toHaveBeenCalledWith('HTTP error: 500 Internal Server Error');
107
- });
108
-
109
- test('network error', async () => {
110
- process.env.SATORI_API_KEY = 'test-key';
111
- const fetchMock = Object.assign(
112
- mock(() => { throw new Error('network error'); }),
113
- { preconnect: mock(() => {}) }
114
- ) as typeof fetch;
115
- globalThis.fetch = fetchMock;
116
- const errorSpy = mock(() => {});
117
- console.error = errorSpy;
118
-
119
- await searchMemories('test');
120
-
121
- expect(errorSpy).toHaveBeenCalledWith('Error searching memories: network error');
122
- });
123
-
124
- test('malformed JSON response', async () => {
125
- process.env.SATORI_API_KEY = 'test-key';
126
- const fetchMock = Object.assign(
127
- mock(() => Promise.resolve(new Response('invalid json', { status: 200 }))),
128
- { preconnect: mock(() => {}) }
129
- ) as typeof fetch;
130
- (globalThis as any).fetch = fetchMock;
131
- const errorSpy = mock(() => {});
132
- console.error = errorSpy;
133
-
134
- await searchMemories('test');
135
-
136
- expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error searching memories:'));
137
- });
138
-
139
- test('enhanceMessagesWithMemory adds context', () => {
140
- const messages = [{ role: 'user' as const, content: 'hello' }];
141
- const memoryContext = { results: [{ id: '1', memory: 'test memory' }] };
142
- const enhanced = enhanceMessagesWithMemory(messages, memoryContext);
143
-
144
- expect(enhanced).toHaveLength(2);
145
- expect(enhanced[0].role).toBe('system');
146
- expect(enhanced[0].content).toContain('Relevant context from memory:\ntest memory');
147
- expect(enhanced[1]).toEqual(messages[0]);
148
- });
149
-
150
- test('enhanceMessagesWithMemory handles empty memories', () => {
151
- const messages = [{ role: 'user' as const, content: 'hello' }];
152
- const memoryContext = { results: [] };
153
- const enhanced = enhanceMessagesWithMemory(messages, memoryContext);
154
-
155
- expect(enhanced).toEqual(messages);
156
- });
157
-
158
-
159
-
160
- test('getConfig throws on invalid base URL', async () => {
161
- process.env.SATORI_BASE_URL = 'invalid-url';
162
- try {
163
- await getConfig();
164
- expect(true).toBe(false); // should throw
165
- } catch (error) {
166
- expect((error as Error).message).toBe('Invalid SATORI_BASE_URL format');
167
- } finally {
168
- delete process.env.SATORI_BASE_URL;
169
- }
170
- });
171
-
172
- test('getConfig throws on non-darwin platform', async () => {
173
- (process as any).platform = 'win32';
174
- try {
175
- await getConfig();
176
- expect(true).toBe(false); // should throw
177
- } catch (error) {
178
- expect((error as Error).message).toBe('We do not currently support Windows yet, email support@satori.sh to request Windows support');
179
- }
180
- });
181
-
182
- test('getConfig generates apiKey on successful fetch', async () => {
183
- delete process.env.SATORI_API_KEY;
184
- const fetchMock = Object.assign(
185
- mock(() => Promise.resolve(new Response(JSON.stringify({ api_key: 'generated-key' }), { status: 200 }))),
186
- { preconnect: mock(() => {}) }
187
- ) as typeof fetch;
188
- globalThis.fetch = fetchMock;
189
-
190
- const config = await getConfig();
191
- expect(config.apiKey).toBe('generated-key');
192
-
193
- globalThis.fetch = originalFetch;
194
- });
195
-
196
- test('getConfig throws on network error during generation', async () => {
197
- delete process.env.SATORI_API_KEY;
198
- mock.module('node:fs', () => ({
199
- promises: {
200
- mkdir: mock(() => Promise.resolve()),
201
- writeFile: mock(() => Promise.resolve()),
202
- unlink: mock(() => Promise.resolve())
203
- }
204
- }));
205
- const mockFile = {
206
- exists: mock(() => Promise.resolve(false))
207
- };
208
- const originalBun = (globalThis as any).Bun;
209
- (globalThis as any).Bun = { file: mock(() => mockFile) };
210
- const fetchMock = Object.assign(
211
- mock(() => Promise.reject(new Error('network error'))),
212
- { preconnect: mock(() => {}) }
213
- ) as typeof fetch;
214
- globalThis.fetch = fetchMock;
215
-
216
- try {
217
- await getConfig();
218
- expect(true).toBe(false); // should throw
219
- } catch (error) {
220
- expect((error as Error).message).toBe('Failed to generate API key: network error');
221
- }
222
-
223
- globalThis.fetch = originalFetch;
224
- (globalThis as any).Bun = originalBun;
225
- });
226
-
227
- test('getConfig throws on invalid response during generation', async () => {
228
- delete process.env.SATORI_API_KEY;
229
- mock.module('node:fs', () => ({
230
- promises: {
231
- mkdir: mock(() => Promise.resolve()),
232
- writeFile: mock(() => Promise.resolve()),
233
- unlink: mock(() => Promise.resolve())
234
- }
235
- }));
236
- const mockFile = {
237
- exists: mock(() => Promise.resolve(false))
238
- };
239
- const originalBun = (globalThis as any).Bun;
240
- (globalThis as any).Bun = { file: mock(() => mockFile) };
241
- const fetchMock = Object.assign(
242
- mock(() => Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))),
243
- { preconnect: mock(() => {}) }
244
- ) as typeof fetch;
245
- globalThis.fetch = fetchMock;
246
-
247
- try {
248
- await getConfig();
249
- expect(true).toBe(false); // should throw
250
- } catch (error) {
251
- expect((error as Error).message).toBe('Failed to generate API key: Invalid response: missing api_key');
252
- }
253
-
254
- globalThis.fetch = originalFetch;
255
- (globalThis as any).Bun = originalBun;
256
- });
257
-
258
- test('checkWriteAccess succeeds on successful write', async () => {
259
- mock.module('node:fs', () => ({
260
- promises: {
261
- mkdir: mock(() => Promise.resolve()),
262
- writeFile: mock(() => Promise.resolve()),
263
- unlink: mock(() => Promise.resolve())
264
- }
265
- }));
266
-
267
- await checkWriteAccess();
268
- // No throw means success
269
- });
270
-
271
- test('checkWriteAccess throws on permission denied', async () => {
272
- mock.module('node:fs', () => ({
273
- promises: {
274
- mkdir: mock(() => Promise.resolve()),
275
- writeFile: mock(() => { throw new Error('EACCES: permission denied'); }),
276
- unlink: mock(() => Promise.resolve())
277
- }
278
- }));
279
-
280
- try {
281
- await checkWriteAccess();
282
- expect(true).toBe(false); // should throw
283
- } catch (error) {
284
- expect((error as Error).message).toBe('Cannot write to config directory: EACCES: permission denied');
285
- }
286
- });
287
-
288
- test('saveApiKey saves successfully', async () => {
289
- const mockWriteFile = mock(() => Promise.resolve());
290
- mock.module('node:fs', () => ({
291
- promises: {
292
- mkdir: mock(() => Promise.resolve()),
293
- writeFile: mockWriteFile,
294
- unlink: mock(() => Promise.resolve())
295
- }
296
- }));
297
-
298
- await saveApiKey('test-key');
299
-
300
- expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('satori.json'), JSON.stringify({ api_key: 'test-key' }, null, 2));
301
- });
302
-
303
- test('saveApiKey throws on write error', async () => {
304
- const mockWriteFile = mock(() => { throw new Error('write error'); });
305
- mock.module('node:fs', () => ({
306
- promises: {
307
- mkdir: mock(() => Promise.resolve()),
308
- writeFile: mockWriteFile,
309
- unlink: mock(() => Promise.resolve())
310
- }
311
- }));
312
-
313
- try {
314
- await saveApiKey('test-key');
315
- expect(true).toBe(false); // should throw
316
- } catch (error) {
317
- expect((error as Error).message).toBe('Cannot write to config directory: write error');
318
- }
319
- });
320
-
321
- // Note: Additional unit tests for file reading scenarios would require mocking Bun.file,
322
- // which is readonly. The existing test covers the missing file case.
package/tsconfig.json DELETED
@@ -1,33 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": [
5
- "ES2022"
6
- ],
7
- "types": ["bun-types"],
8
- "allowJs": true,
9
- "skipLibCheck": true,
10
- "esModuleInterop": true,
11
- "allowSyntheticDefaultImports": true,
12
- "strict": true,
13
- "forceConsistentCasingInFileNames": true,
14
- "noFallthroughCasesInSwitch": true,
15
- "module": "ESNext",
16
- "moduleResolution": "bundler",
17
- "resolveJsonModule": true,
18
- "isolatedModules": true,
19
- "noEmit": false,
20
- "outDir": "./dist",
21
- "declaration": true,
22
- "declarationMap": true,
23
- "sourceMap": true
24
- },
25
- "include": [
26
- "src/**/*",
27
- "tests/**/*"
28
- ],
29
- "exclude": [
30
- "node_modules",
31
- "dist"
32
- ]
33
- }
package/tsup.config.js DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'tsup'
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['esm'],
6
- clean: true
7
- })