ultraclaude-agent 0.0.13 → 0.0.15

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.
Files changed (74) hide show
  1. package/__tests__/config-multi-account.test.ts +413 -0
  2. package/__tests__/daemon-reconcile.test.ts +294 -0
  3. package/__tests__/daemon.test.ts +222 -46
  4. package/__tests__/push-version-metadata.test.ts +177 -0
  5. package/__tests__/repl-reset.test.ts +296 -0
  6. package/__tests__/repl-status-account.test.ts +175 -0
  7. package/__tests__/repl.test.ts +1030 -0
  8. package/__tests__/snapshot-sync.test.ts +379 -0
  9. package/__tests__/sync-bugs.test.ts +6 -4
  10. package/__tests__/sync-queue-credentials.test.ts +200 -0
  11. package/__tests__/sync.test.ts +7 -5
  12. package/dist/auth.d.ts.map +1 -1
  13. package/dist/auth.js +13 -5
  14. package/dist/auth.js.map +1 -1
  15. package/dist/cli.js +129 -31
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +42 -1
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +161 -7
  20. package/dist/config.js.map +1 -1
  21. package/dist/daemon-worker.d.ts +2 -0
  22. package/dist/daemon-worker.d.ts.map +1 -0
  23. package/dist/daemon-worker.js +22 -0
  24. package/dist/daemon-worker.js.map +1 -0
  25. package/dist/daemon.d.ts +45 -9
  26. package/dist/daemon.d.ts.map +1 -1
  27. package/dist/daemon.js +247 -25
  28. package/dist/daemon.js.map +1 -1
  29. package/dist/logger.d.ts +11 -1
  30. package/dist/logger.d.ts.map +1 -1
  31. package/dist/logger.js +26 -2
  32. package/dist/logger.js.map +1 -1
  33. package/dist/repl.d.ts +2 -0
  34. package/dist/repl.d.ts.map +1 -0
  35. package/dist/repl.js +512 -0
  36. package/dist/repl.js.map +1 -0
  37. package/dist/setup.d.ts +6 -0
  38. package/dist/setup.d.ts.map +1 -0
  39. package/dist/setup.js +181 -0
  40. package/dist/setup.js.map +1 -0
  41. package/dist/status.d.ts +35 -0
  42. package/dist/status.d.ts.map +1 -0
  43. package/dist/status.js +70 -0
  44. package/dist/status.js.map +1 -0
  45. package/dist/sync.d.ts +28 -10
  46. package/dist/sync.d.ts.map +1 -1
  47. package/dist/sync.js +107 -44
  48. package/dist/sync.js.map +1 -1
  49. package/dist/usage-sync.js +2 -2
  50. package/dist/usage-sync.js.map +1 -1
  51. package/dist/watcher.d.ts +5 -0
  52. package/dist/watcher.d.ts.map +1 -1
  53. package/dist/watcher.js +40 -6
  54. package/dist/watcher.js.map +1 -1
  55. package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts +4 -0
  56. package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts.map +1 -1
  57. package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js +4 -0
  58. package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js.map +1 -1
  59. package/node_modules/@ultra-claude/shared/dist/types.d.ts +3 -1
  60. package/node_modules/@ultra-claude/shared/dist/types.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/auth.ts +13 -5
  63. package/src/cli.ts +139 -33
  64. package/src/config.ts +189 -7
  65. package/src/daemon-worker.ts +27 -0
  66. package/src/daemon.ts +311 -28
  67. package/src/logger.ts +30 -2
  68. package/src/repl.ts +623 -0
  69. package/src/setup.ts +212 -0
  70. package/src/status.ts +96 -0
  71. package/src/sync.ts +124 -39
  72. package/src/usage-sync.ts +2 -2
  73. package/src/watcher.ts +51 -11
  74. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Tests for Task 1 multi-account credential storage functions.
3
+ * Covers: loadAllCredentials, saveAccountCredentials, saveCredentials,
4
+ * migrateCredentialsToAccounts, loadServerConfig/saveServerConfig,
5
+ * getDefaultAccount/setDefaultAccount, getProjectAccount/setProjectAccount.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ import { join } from 'node:path';
10
+ import { mkdtemp, writeFile, mkdir, rm, readFile, readdir } from 'node:fs/promises';
11
+ import { tmpdir } from 'node:os';
12
+
13
+ // Mock node:os for homedir/platform control
14
+ const mockPlatform = vi.fn().mockReturnValue('linux');
15
+ const mockHomedir = vi.fn().mockReturnValue('/home/testuser');
16
+
17
+ vi.mock('node:os', async (importOriginal) => {
18
+ const actual = await importOriginal<typeof import('node:os')>();
19
+ return {
20
+ ...actual,
21
+ homedir: () => mockHomedir(),
22
+ platform: () => mockPlatform(),
23
+ };
24
+ });
25
+
26
+ describe('config — loadAllCredentials', () => {
27
+ let tempHome: string;
28
+
29
+ beforeEach(async () => {
30
+ vi.resetModules();
31
+ mockPlatform.mockReturnValue('linux');
32
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-load-test-'));
33
+ mockHomedir.mockReturnValue(tempHome);
34
+ });
35
+
36
+ afterEach(async () => {
37
+ await rm(tempHome, { recursive: true, force: true });
38
+ });
39
+
40
+ it('returns empty Map when no accounts directory exists', async () => {
41
+ const { loadAllCredentials } = await import('../src/config.js');
42
+ const result = await loadAllCredentials('https://dashboard.ultra-claude.dev');
43
+ expect(result).toBeInstanceOf(Map);
44
+ expect(result.size).toBe(0);
45
+ });
46
+
47
+ it('returns Map of userId → credentials for each account file', async () => {
48
+ const { loadAllCredentials } = await import('../src/config.js');
49
+
50
+ const serverKey = 'dashboard.ultra-claude.dev';
51
+ const accountsDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey, 'accounts');
52
+ await mkdir(accountsDir, { recursive: true });
53
+
54
+ const creds1 = { apiKey: 'key-1', userId: 'user-1', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'alice@example.com' };
55
+ const creds2 = { apiKey: 'key-2', userId: 'user-2', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'bob@example.com' };
56
+
57
+ await writeFile(join(accountsDir, 'user-1.json'), JSON.stringify(creds1));
58
+ await writeFile(join(accountsDir, 'user-2.json'), JSON.stringify(creds2));
59
+
60
+ const result = await loadAllCredentials('https://dashboard.ultra-claude.dev');
61
+
62
+ expect(result.size).toBe(2);
63
+ expect(result.get('user-1')).toEqual(creds1);
64
+ expect(result.get('user-2')).toEqual(creds2);
65
+ });
66
+
67
+ it('ignores non-JSON files in accounts directory', async () => {
68
+ const { loadAllCredentials } = await import('../src/config.js');
69
+
70
+ const serverKey = 'dashboard.ultra-claude.dev';
71
+ const accountsDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey, 'accounts');
72
+ await mkdir(accountsDir, { recursive: true });
73
+
74
+ const creds = { apiKey: 'key-1', userId: 'user-1', serverUrl: 'https://dashboard.ultra-claude.dev' };
75
+ await writeFile(join(accountsDir, 'user-1.json'), JSON.stringify(creds));
76
+ await writeFile(join(accountsDir, 'README.txt'), 'not a json file');
77
+ await writeFile(join(accountsDir, '.DS_Store'), 'binary data');
78
+
79
+ const result = await loadAllCredentials('https://dashboard.ultra-claude.dev');
80
+ expect(result.size).toBe(1);
81
+ expect(result.has('user-1')).toBe(true);
82
+ });
83
+
84
+ it('runs migration from credentials.json on first call', async () => {
85
+ const { loadAllCredentials } = await import('../src/config.js');
86
+
87
+ // Set up old credentials.json
88
+ const serverKey = 'dashboard.ultra-claude.dev';
89
+ const configDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey);
90
+ await mkdir(configDir, { recursive: true });
91
+
92
+ const oldCreds = { apiKey: 'old-key', userId: 'migrated-user', serverUrl: 'https://dashboard.ultra-claude.dev' };
93
+ await writeFile(join(configDir, 'credentials.json'), JSON.stringify(oldCreds));
94
+
95
+ const result = await loadAllCredentials('https://dashboard.ultra-claude.dev');
96
+
97
+ // Should have migrated the old credentials
98
+ expect(result.size).toBe(1);
99
+ expect(result.get('migrated-user')).toMatchObject({ apiKey: 'old-key', userId: 'migrated-user' });
100
+
101
+ // Old credentials.json should have been renamed to .bak
102
+ const backupExists = await readFile(join(configDir, 'credentials.json.bak'), 'utf8').catch(() => null);
103
+ expect(backupExists).not.toBeNull();
104
+
105
+ // New account file should exist
106
+ const accountFile = await readFile(join(configDir, 'accounts', 'migrated-user.json'), 'utf8').catch(() => null);
107
+ expect(accountFile).not.toBeNull();
108
+ const parsed = JSON.parse(accountFile!);
109
+ expect(parsed.userId).toBe('migrated-user');
110
+ });
111
+ });
112
+
113
+ describe('config — saveAccountCredentials (saveCredentials)', () => {
114
+ let tempHome: string;
115
+
116
+ beforeEach(async () => {
117
+ vi.resetModules();
118
+ mockPlatform.mockReturnValue('linux');
119
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-save-test-'));
120
+ mockHomedir.mockReturnValue(tempHome);
121
+ });
122
+
123
+ afterEach(async () => {
124
+ await rm(tempHome, { recursive: true, force: true });
125
+ });
126
+
127
+ it('stores credentials to accounts/{userId}.json', async () => {
128
+ const { saveAccountCredentials } = await import('../src/config.js');
129
+
130
+ const creds = { apiKey: 'my-key', userId: 'alice', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'alice@example.com' };
131
+ await saveAccountCredentials(creds);
132
+
133
+ const expectedPath = join(tempHome, '.claude', 'ultra', 'agent', 'dashboard.ultra-claude.dev', 'accounts', 'alice.json');
134
+ const saved = JSON.parse(await readFile(expectedPath, 'utf8'));
135
+ expect(saved).toEqual(creds);
136
+ });
137
+
138
+ it('does not overwrite other accounts when saving', async () => {
139
+ const { saveAccountCredentials } = await import('../src/config.js');
140
+
141
+ const creds1 = { apiKey: 'key-1', userId: 'alice', serverUrl: 'https://dashboard.ultra-claude.dev' };
142
+ const creds2 = { apiKey: 'key-2', userId: 'bob', serverUrl: 'https://dashboard.ultra-claude.dev' };
143
+
144
+ await saveAccountCredentials(creds1);
145
+ await saveAccountCredentials(creds2);
146
+
147
+ const accountsDir = join(tempHome, '.claude', 'ultra', 'agent', 'dashboard.ultra-claude.dev', 'accounts');
148
+ const files = await readdir(accountsDir);
149
+ expect(files).toContain('alice.json');
150
+ expect(files).toContain('bob.json');
151
+ expect(files).toHaveLength(2);
152
+
153
+ const aliceSaved = JSON.parse(await readFile(join(accountsDir, 'alice.json'), 'utf8'));
154
+ expect(aliceSaved.apiKey).toBe('key-1');
155
+
156
+ const bobSaved = JSON.parse(await readFile(join(accountsDir, 'bob.json'), 'utf8'));
157
+ expect(bobSaved.apiKey).toBe('key-2');
158
+ });
159
+
160
+ it('saveCredentials (deprecated wrapper) stores to accounts/{userId}.json', async () => {
161
+ const { saveCredentials } = await import('../src/config.js');
162
+
163
+ const creds = { apiKey: 'legacy-key', userId: 'legacy-user', serverUrl: 'https://dashboard.ultra-claude.dev' };
164
+ await saveCredentials(creds);
165
+
166
+ const expectedPath = join(tempHome, '.claude', 'ultra', 'agent', 'dashboard.ultra-claude.dev', 'accounts', 'legacy-user.json');
167
+ const saved = JSON.parse(await readFile(expectedPath, 'utf8'));
168
+ expect(saved.apiKey).toBe('legacy-key');
169
+ expect(saved.userId).toBe('legacy-user');
170
+ });
171
+
172
+ it('saving same user twice updates the existing account file', async () => {
173
+ const { saveAccountCredentials } = await import('../src/config.js');
174
+
175
+ const creds1 = { apiKey: 'old-key', userId: 'alice', serverUrl: 'https://dashboard.ultra-claude.dev' };
176
+ const creds2 = { apiKey: 'new-key', userId: 'alice', serverUrl: 'https://dashboard.ultra-claude.dev' };
177
+
178
+ await saveAccountCredentials(creds1);
179
+ await saveAccountCredentials(creds2);
180
+
181
+ const accountsDir = join(tempHome, '.claude', 'ultra', 'agent', 'dashboard.ultra-claude.dev', 'accounts');
182
+ const files = await readdir(accountsDir);
183
+ expect(files).toHaveLength(1); // Still one file, not two
184
+
185
+ const saved = JSON.parse(await readFile(join(accountsDir, 'alice.json'), 'utf8'));
186
+ expect(saved.apiKey).toBe('new-key'); // Updated
187
+ });
188
+ });
189
+
190
+ describe('config — migrateCredentialsToAccounts', () => {
191
+ let tempHome: string;
192
+
193
+ beforeEach(async () => {
194
+ vi.resetModules();
195
+ mockPlatform.mockReturnValue('linux');
196
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-migrate-test-'));
197
+ mockHomedir.mockReturnValue(tempHome);
198
+ });
199
+
200
+ afterEach(async () => {
201
+ await rm(tempHome, { recursive: true, force: true });
202
+ });
203
+
204
+ it('migrates credentials.json to accounts/{userId}.json', async () => {
205
+ const { migrateCredentialsToAccounts } = await import('../src/config.js');
206
+
207
+ const serverKey = 'dashboard.ultra-claude.dev';
208
+ const configDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey);
209
+ await mkdir(configDir, { recursive: true });
210
+
211
+ const oldCreds = { apiKey: 'old-key', userId: 'user-123', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'user@example.com' };
212
+ await writeFile(join(configDir, 'credentials.json'), JSON.stringify(oldCreds));
213
+
214
+ const migrated = await migrateCredentialsToAccounts('https://dashboard.ultra-claude.dev');
215
+ expect(migrated).toBe(true);
216
+
217
+ // Check account file was created
218
+ const accountFile = join(configDir, 'accounts', 'user-123.json');
219
+ const saved = JSON.parse(await readFile(accountFile, 'utf8'));
220
+ expect(saved).toEqual(oldCreds);
221
+
222
+ // Check old file was backed up
223
+ const backupContent = await readFile(join(configDir, 'credentials.json.bak'), 'utf8');
224
+ expect(JSON.parse(backupContent)).toEqual(oldCreds);
225
+ });
226
+
227
+ it('creates config.json with defaultAccount set to migrated userId', async () => {
228
+ const { migrateCredentialsToAccounts } = await import('../src/config.js');
229
+
230
+ const serverKey = 'dashboard.ultra-claude.dev';
231
+ const configDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey);
232
+ await mkdir(configDir, { recursive: true });
233
+
234
+ const oldCreds = { apiKey: 'key', userId: 'the-user', serverUrl: 'https://dashboard.ultra-claude.dev' };
235
+ await writeFile(join(configDir, 'credentials.json'), JSON.stringify(oldCreds));
236
+
237
+ await migrateCredentialsToAccounts('https://dashboard.ultra-claude.dev');
238
+
239
+ const configContent = JSON.parse(await readFile(join(configDir, 'config.json'), 'utf8'));
240
+ expect(configContent.defaultAccount).toBe('the-user');
241
+ expect(configContent.projectAccounts).toEqual({});
242
+ });
243
+
244
+ it('returns false when credentials.json does not exist', async () => {
245
+ const { migrateCredentialsToAccounts } = await import('../src/config.js');
246
+ const migrated = await migrateCredentialsToAccounts('https://dashboard.ultra-claude.dev');
247
+ expect(migrated).toBe(false);
248
+ });
249
+
250
+ it('returns false when accounts/ already has JSON files (already migrated)', async () => {
251
+ const { migrateCredentialsToAccounts } = await import('../src/config.js');
252
+
253
+ const serverKey = 'dashboard.ultra-claude.dev';
254
+ const configDir = join(tempHome, '.claude', 'ultra', 'agent', serverKey);
255
+ const accountsDir = join(configDir, 'accounts');
256
+ await mkdir(accountsDir, { recursive: true });
257
+
258
+ // Both credentials.json AND accounts/ exist (already migrated)
259
+ const creds = { apiKey: 'key', userId: 'user', serverUrl: 'https://dashboard.ultra-claude.dev' };
260
+ await writeFile(join(configDir, 'credentials.json'), JSON.stringify(creds));
261
+ await writeFile(join(accountsDir, 'user.json'), JSON.stringify(creds));
262
+
263
+ const migrated = await migrateCredentialsToAccounts('https://dashboard.ultra-claude.dev');
264
+ expect(migrated).toBe(false);
265
+ });
266
+ });
267
+
268
+ describe('config — loadServerConfig / saveServerConfig', () => {
269
+ let tempHome: string;
270
+
271
+ beforeEach(async () => {
272
+ vi.resetModules();
273
+ mockPlatform.mockReturnValue('linux');
274
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-config-test-'));
275
+ mockHomedir.mockReturnValue(tempHome);
276
+ });
277
+
278
+ afterEach(async () => {
279
+ await rm(tempHome, { recursive: true, force: true });
280
+ });
281
+
282
+ it('returns default empty config when config.json does not exist', async () => {
283
+ const { loadServerConfig } = await import('../src/config.js');
284
+ const config = await loadServerConfig('https://dashboard.ultra-claude.dev');
285
+ expect(config.defaultAccount).toBe('');
286
+ expect(config.projectAccounts).toEqual({});
287
+ });
288
+
289
+ it('saves and loads config.json atomically', async () => {
290
+ const { loadServerConfig, saveServerConfig } = await import('../src/config.js');
291
+
292
+ const config = { defaultAccount: 'user-abc', projectAccounts: { '/home/user/project': 'user-abc' } };
293
+ await saveServerConfig('https://dashboard.ultra-claude.dev', config);
294
+
295
+ const loaded = await loadServerConfig('https://dashboard.ultra-claude.dev');
296
+ expect(loaded.defaultAccount).toBe('user-abc');
297
+ expect(loaded.projectAccounts['/home/user/project']).toBe('user-abc');
298
+ });
299
+ });
300
+
301
+ describe('config — getDefaultAccount / setDefaultAccount', () => {
302
+ let tempHome: string;
303
+
304
+ beforeEach(async () => {
305
+ vi.resetModules();
306
+ mockPlatform.mockReturnValue('linux');
307
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-default-test-'));
308
+ mockHomedir.mockReturnValue(tempHome);
309
+ });
310
+
311
+ afterEach(async () => {
312
+ await rm(tempHome, { recursive: true, force: true });
313
+ });
314
+
315
+ it('returns null when no default account is set', async () => {
316
+ const { getDefaultAccount } = await import('../src/config.js');
317
+ const result = await getDefaultAccount('https://dashboard.ultra-claude.dev');
318
+ expect(result).toBeNull();
319
+ });
320
+
321
+ it('sets and gets the default account', async () => {
322
+ const { getDefaultAccount, setDefaultAccount } = await import('../src/config.js');
323
+
324
+ await setDefaultAccount('https://dashboard.ultra-claude.dev', 'user-xyz');
325
+ const result = await getDefaultAccount('https://dashboard.ultra-claude.dev');
326
+ expect(result).toBe('user-xyz');
327
+ });
328
+
329
+ it('updating default does not lose projectAccounts', async () => {
330
+ const { setDefaultAccount, setProjectAccount, loadServerConfig } = await import('../src/config.js');
331
+
332
+ await setProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/proj', 'original-user');
333
+ await setDefaultAccount('https://dashboard.ultra-claude.dev', 'new-default');
334
+
335
+ const config = await loadServerConfig('https://dashboard.ultra-claude.dev');
336
+ expect(config.defaultAccount).toBe('new-default');
337
+ expect(config.projectAccounts['/home/user/proj']).toBe('original-user');
338
+ });
339
+ });
340
+
341
+ describe('config — getProjectAccount / setProjectAccount', () => {
342
+ let tempHome: string;
343
+
344
+ beforeEach(async () => {
345
+ vi.resetModules();
346
+ mockPlatform.mockReturnValue('linux');
347
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-project-test-'));
348
+ mockHomedir.mockReturnValue(tempHome);
349
+ });
350
+
351
+ afterEach(async () => {
352
+ await rm(tempHome, { recursive: true, force: true });
353
+ });
354
+
355
+ it('returns null when no mapping exists for a project', async () => {
356
+ const { getProjectAccount } = await import('../src/config.js');
357
+ const result = await getProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project');
358
+ expect(result).toBeNull();
359
+ });
360
+
361
+ it('sets and gets a project-to-account mapping', async () => {
362
+ const { getProjectAccount, setProjectAccount } = await import('../src/config.js');
363
+
364
+ await setProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project', 'user-abc');
365
+ const result = await getProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project');
366
+ expect(result).toBe('user-abc');
367
+ });
368
+
369
+ it('setting one project does not affect another project mapping', async () => {
370
+ const { getProjectAccount, setProjectAccount } = await import('../src/config.js');
371
+
372
+ await setProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project-a', 'user-1');
373
+ await setProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project-b', 'user-2');
374
+
375
+ expect(await getProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project-a')).toBe('user-1');
376
+ expect(await getProjectAccount('https://dashboard.ultra-claude.dev', '/home/user/project-b')).toBe('user-2');
377
+ });
378
+ });
379
+
380
+ describe('config — login creates two account files for two different users', () => {
381
+ /**
382
+ * Verifies that calling saveAccountCredentials twice with different userId values
383
+ * results in two separate files, not one overwriting the other.
384
+ * This simulates the login() flow being called twice with different Clerk users.
385
+ */
386
+ let tempHome: string;
387
+
388
+ beforeEach(async () => {
389
+ vi.resetModules();
390
+ mockPlatform.mockReturnValue('linux');
391
+ tempHome = await mkdtemp(join(tmpdir(), 'multi-acct-dual-login-test-'));
392
+ mockHomedir.mockReturnValue(tempHome);
393
+ });
394
+
395
+ afterEach(async () => {
396
+ await rm(tempHome, { recursive: true, force: true });
397
+ });
398
+
399
+ it('two saveAccountCredentials calls with different users → two separate account files', async () => {
400
+ const { saveAccountCredentials, loadAllCredentials } = await import('../src/config.js');
401
+
402
+ const user1Creds = { apiKey: 'key-user1', userId: 'clerk-user-1', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'user1@org.com' };
403
+ const user2Creds = { apiKey: 'key-user2', userId: 'clerk-user-2', serverUrl: 'https://dashboard.ultra-claude.dev', email: 'user2@org.com' };
404
+
405
+ await saveAccountCredentials(user1Creds);
406
+ await saveAccountCredentials(user2Creds);
407
+
408
+ const allCreds = await loadAllCredentials('https://dashboard.ultra-claude.dev');
409
+ expect(allCreds.size).toBe(2);
410
+ expect(allCreds.get('clerk-user-1')).toMatchObject({ email: 'user1@org.com' });
411
+ expect(allCreds.get('clerk-user-2')).toMatchObject({ email: 'user2@org.com' });
412
+ });
413
+ });