ai-vault 3.3.0 → 3.5.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.
Files changed (97) hide show
  1. package/README.md +84 -74
  2. package/dist/cli.js +17 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/archive.d.ts +2 -0
  5. package/dist/commands/archive.d.ts.map +1 -1
  6. package/dist/commands/archive.js +80 -46
  7. package/dist/commands/archive.js.map +1 -1
  8. package/dist/commands/import.d.ts.map +1 -1
  9. package/dist/commands/import.js +29 -27
  10. package/dist/commands/import.js.map +1 -1
  11. package/dist/commands/schedule.d.ts.map +1 -1
  12. package/dist/commands/schedule.js +17 -6
  13. package/dist/commands/schedule.js.map +1 -1
  14. package/dist/commands/setup.d.ts.map +1 -1
  15. package/dist/commands/setup.js +295 -95
  16. package/dist/commands/setup.js.map +1 -1
  17. package/dist/commands/status.d.ts.map +1 -1
  18. package/dist/commands/status.js +2 -6
  19. package/dist/commands/status.js.map +1 -1
  20. package/dist/commands/verify.d.ts +14 -0
  21. package/dist/commands/verify.d.ts.map +1 -0
  22. package/dist/commands/verify.js +293 -0
  23. package/dist/commands/verify.js.map +1 -0
  24. package/dist/core/archiver.d.ts +10 -0
  25. package/dist/core/archiver.d.ts.map +1 -1
  26. package/dist/core/archiver.js +74 -4
  27. package/dist/core/archiver.js.map +1 -1
  28. package/dist/core/archiver.spec.js +2 -0
  29. package/dist/core/archiver.spec.js.map +1 -1
  30. package/dist/core/media.d.ts +5 -0
  31. package/dist/core/media.d.ts.map +1 -1
  32. package/dist/core/media.js +6 -0
  33. package/dist/core/media.js.map +1 -1
  34. package/dist/core/storage.d.ts +4 -0
  35. package/dist/core/storage.d.ts.map +1 -1
  36. package/dist/core/storage.js +10 -0
  37. package/dist/core/storage.js.map +1 -1
  38. package/dist/providers/chatgpt/api-provider.d.ts.map +1 -1
  39. package/dist/providers/chatgpt/api-provider.js +8 -82
  40. package/dist/providers/chatgpt/api-provider.js.map +1 -1
  41. package/dist/providers/chatgpt/attachments.d.ts +12 -0
  42. package/dist/providers/chatgpt/attachments.d.ts.map +1 -0
  43. package/dist/providers/chatgpt/attachments.js +124 -0
  44. package/dist/providers/chatgpt/attachments.js.map +1 -0
  45. package/dist/providers/chatgpt/index.d.ts.map +1 -1
  46. package/dist/providers/chatgpt/index.js +8 -129
  47. package/dist/providers/chatgpt/index.js.map +1 -1
  48. package/dist/providers/claude/api-provider.d.ts.map +1 -1
  49. package/dist/providers/claude/api-provider.js +22 -92
  50. package/dist/providers/claude/api-provider.js.map +1 -1
  51. package/dist/providers/claude/errors.d.ts +16 -0
  52. package/dist/providers/claude/errors.d.ts.map +1 -0
  53. package/dist/providers/claude/errors.js +38 -0
  54. package/dist/providers/claude/errors.js.map +1 -0
  55. package/dist/providers/claude/index.d.ts +4 -7
  56. package/dist/providers/claude/index.d.ts.map +1 -1
  57. package/dist/providers/claude/index.js +123 -191
  58. package/dist/providers/claude/index.js.map +1 -1
  59. package/dist/providers/claude/message-parser.d.ts +3 -0
  60. package/dist/providers/claude/message-parser.d.ts.map +1 -0
  61. package/dist/providers/claude/message-parser.js +95 -0
  62. package/dist/providers/claude/message-parser.js.map +1 -0
  63. package/dist/providers/gemini/index.d.ts +56 -0
  64. package/dist/providers/gemini/index.d.ts.map +1 -0
  65. package/dist/providers/gemini/index.js +420 -0
  66. package/dist/providers/gemini/index.js.map +1 -0
  67. package/dist/providers/index.d.ts +2 -1
  68. package/dist/providers/index.d.ts.map +1 -1
  69. package/dist/providers/index.js +3 -2
  70. package/dist/providers/index.js.map +1 -1
  71. package/dist/types/provider.d.ts +4 -0
  72. package/dist/types/provider.d.ts.map +1 -1
  73. package/dist/types/provider.js +8 -0
  74. package/dist/types/provider.js.map +1 -1
  75. package/dist/types/schemas.d.ts +6 -6
  76. package/dist/types/storage.d.ts +4 -0
  77. package/dist/types/storage.d.ts.map +1 -1
  78. package/dist/utils/archive-dir.d.ts +5 -0
  79. package/dist/utils/archive-dir.d.ts.map +1 -0
  80. package/dist/utils/archive-dir.js +15 -0
  81. package/dist/utils/archive-dir.js.map +1 -0
  82. package/dist/utils/cli-ui.d.ts +41 -0
  83. package/dist/utils/cli-ui.d.ts.map +1 -0
  84. package/dist/utils/cli-ui.js +145 -0
  85. package/dist/utils/cli-ui.js.map +1 -0
  86. package/dist/utils/interactive.d.ts +5 -0
  87. package/dist/utils/interactive.d.ts.map +1 -0
  88. package/dist/utils/interactive.js +13 -0
  89. package/dist/utils/interactive.js.map +1 -0
  90. package/dist/utils/scheduler.d.ts.map +1 -1
  91. package/dist/utils/scheduler.js +1 -6
  92. package/dist/utils/scheduler.js.map +1 -1
  93. package/dist/utils/scheduler.spec.js +1 -1
  94. package/dist/utils/scheduler.spec.js.map +1 -1
  95. package/dist/utils/scraper.js +1 -1
  96. package/dist/utils/scraper.js.map +1 -1
  97. package/package.json +1 -1
@@ -1,33 +1,39 @@
1
1
  /**
2
2
  * Setup Command - Interactive provider configuration
3
3
  */
4
- import * as clack from '@clack/prompts';
5
4
  import chalk from 'chalk';
6
5
  import { readFileSync } from 'fs';
7
6
  import { resolve } from 'path';
8
7
  import { saveProviderConfig, loadConfig, isProviderConfigured } from '../utils/config.js';
9
8
  import { getProvider } from '../providers/index.js';
10
- const VALID_PROVIDERS = ['grok-web', 'grok-x', 'chatgpt', 'claude'];
9
+ import { createCliUI } from '../utils/cli-ui.js';
10
+ import * as clack from '@clack/prompts';
11
+ const VALID_PROVIDERS = ['grok-web', 'grok-x', 'chatgpt', 'claude', 'gemini'];
11
12
  export async function setupCommand(options = {}) {
12
- clack.intro(chalk.bold.blue('AI Vault Setup'));
13
+ const ui = createCliUI();
14
+ ui.intro(chalk.bold.blue('AI Vault Setup'));
13
15
  // Check existing configuration
14
16
  const config = await loadConfig();
15
17
  const configuredProviders = Object.keys(config.providers);
16
18
  if (configuredProviders.length > 0) {
17
- clack.log.info(`Currently configured: ${configuredProviders.join(', ')}`);
19
+ ui.log.info(`Currently configured: ${configuredProviders.join(', ')}`);
18
20
  }
19
21
  let providerName;
20
22
  // Use provider from argument if provided
21
23
  if (options.provider) {
22
24
  if (!VALID_PROVIDERS.includes(options.provider)) {
23
- clack.log.error(`Invalid provider: ${options.provider}`);
24
- clack.log.info(`Valid providers: ${VALID_PROVIDERS.join(', ')}`);
25
+ ui.log.error(`Invalid provider: ${options.provider}`);
26
+ ui.log.info(`Valid providers: ${VALID_PROVIDERS.join(', ')}`);
25
27
  process.exit(1);
26
28
  }
27
29
  providerName = options.provider;
28
- clack.log.info(`Configuring provider: ${providerName}`);
30
+ ui.log.info(`Configuring provider: ${providerName}`);
29
31
  }
30
32
  else {
33
+ if (!ui.isInteractive) {
34
+ ui.log.error('Provider selection requires a TTY. Pass a provider name explicitly.');
35
+ process.exit(1);
36
+ }
31
37
  // Select provider interactively
32
38
  const provider = await clack.select({
33
39
  message: 'Which provider do you want to configure?',
@@ -36,22 +42,32 @@ export async function setupCommand(options = {}) {
36
42
  { value: 'grok-x', label: 'Grok on X (x.com)', hint: 'x.com/grok' },
37
43
  { value: 'chatgpt', label: 'ChatGPT (OpenAI)', hint: 'chatgpt.com' },
38
44
  { value: 'claude', label: 'Claude (Anthropic)', hint: 'claude.ai' },
45
+ { value: 'gemini', label: 'Gemini (Google)', hint: 'gemini.google.com' },
39
46
  ],
40
47
  });
41
48
  if (clack.isCancel(provider)) {
42
- clack.cancel('Setup cancelled');
49
+ ui.cancel('Setup cancelled');
43
50
  process.exit(0);
44
51
  }
45
52
  providerName = provider;
46
53
  }
47
54
  // Check if already configured
48
55
  if (await isProviderConfigured(providerName)) {
49
- const overwrite = await clack.confirm({
50
- message: `${providerName} is already configured. Overwrite?`,
51
- });
52
- if (clack.isCancel(overwrite) || !overwrite) {
53
- clack.cancel('Setup cancelled');
54
- process.exit(0);
56
+ if (!ui.isInteractive) {
57
+ if (!options.cookiesFile) {
58
+ ui.log.error(`${providerName} is already configured. Run setup in a TTY to overwrite or pass --cookies-file.`);
59
+ process.exit(1);
60
+ }
61
+ ui.log.warn(`${providerName} is already configured. Overwriting with provided cookies file.`);
62
+ }
63
+ else {
64
+ const overwrite = await clack.confirm({
65
+ message: `${providerName} is already configured. Overwrite?`,
66
+ });
67
+ if (clack.isCancel(overwrite) || !overwrite) {
68
+ ui.cancel('Setup cancelled');
69
+ process.exit(0);
70
+ }
55
71
  }
56
72
  }
57
73
  // Configure based on provider
@@ -66,22 +82,30 @@ export async function setupCommand(options = {}) {
66
82
  case 'claude':
67
83
  await setupClaude(options);
68
84
  break;
85
+ case 'gemini':
86
+ await setupGemini(options);
87
+ break;
69
88
  default:
70
- clack.log.error(`${providerName} provider is not yet implemented`);
89
+ ui.log.error(`${providerName} provider is not yet implemented`);
71
90
  process.exit(1);
72
91
  }
73
- clack.outro(chalk.green('✓ Setup complete! Run `ai-vault archive` to start archiving.'));
92
+ ui.outro(chalk.green('✓ Setup complete! Run `ai-vault archive` to start archiving.'));
74
93
  }
75
94
  async function setupGrok(providerName, options) {
95
+ const ui = createCliUI();
76
96
  const displayName = providerName === 'grok-web' ? 'Grok (grok.com)' : 'Grok on X (x.com/grok)';
77
- clack.log.step(`Configuring ${displayName}`);
97
+ ui.log.step(`Configuring ${displayName}`);
78
98
  // If cookies file provided, skip to cookie auth
79
99
  let authMethod;
80
100
  if (options.cookiesFile) {
81
101
  authMethod = 'cookies';
82
- clack.log.info(`Using cookies from file: ${options.cookiesFile}`);
102
+ ui.log.info(`Using cookies from file: ${options.cookiesFile}`);
83
103
  }
84
104
  else {
105
+ if (!ui.isInteractive) {
106
+ ui.log.error('Interactive auth selection requires a TTY. Pass --cookies-file.');
107
+ process.exit(1);
108
+ }
85
109
  // Choose auth method
86
110
  const selected = await clack.select({
87
111
  message: 'Authentication method:',
@@ -91,7 +115,7 @@ async function setupGrok(providerName, options) {
91
115
  ],
92
116
  });
93
117
  if (clack.isCancel(selected)) {
94
- clack.cancel('Setup cancelled');
118
+ ui.cancel('Setup cancelled');
95
119
  process.exit(0);
96
120
  }
97
121
  authMethod = selected;
@@ -143,21 +167,25 @@ async function setupGrok(providerName, options) {
143
167
  // Already in object format
144
168
  cookies = parsed;
145
169
  }
146
- clack.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
170
+ ui.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
147
171
  }
148
172
  catch (error) {
149
- clack.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
173
+ ui.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
150
174
  process.exit(1);
151
175
  }
152
176
  }
153
177
  else {
178
+ if (!ui.isInteractive) {
179
+ ui.log.error('Interactive cookie entry requires a TTY. Pass --cookies-file.');
180
+ process.exit(1);
181
+ }
154
182
  // Interactive prompt for each cookie
155
183
  const websiteUrl = providerName === 'grok-web' ? 'grok.com' : 'x.com/grok';
156
- clack.log.info(`To get cookies from ${displayName}:`);
157
- clack.log.info(`1. Open ${websiteUrl} in your browser and log in`);
158
- clack.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
159
- clack.log.info(`3. Go to Application → Cookies → https://${websiteUrl.split('/')[0]}`);
160
- clack.log.info('4. Find each cookie below and copy its VALUE');
184
+ ui.log.info(`To get cookies from ${displayName}:`);
185
+ ui.log.info(`1. Open ${websiteUrl} in your browser and log in`);
186
+ ui.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
187
+ ui.log.info(`3. Go to Application → Cookies → https://${websiteUrl.split('/')[0]}`);
188
+ ui.log.info('4. Find each cookie below and copy its VALUE');
161
189
  console.log();
162
190
  cookies = {};
163
191
  // Prompt for sso cookie first
@@ -175,7 +203,7 @@ async function setupGrok(providerName, options) {
175
203
  },
176
204
  });
177
205
  if (clack.isCancel(ssoValue)) {
178
- clack.cancel('Setup cancelled');
206
+ ui.cancel('Setup cancelled');
179
207
  process.exit(0);
180
208
  }
181
209
  cookies['sso'] = ssoValue.trim();
@@ -196,14 +224,14 @@ async function setupGrok(providerName, options) {
196
224
  },
197
225
  });
198
226
  if (clack.isCancel(ssoRwValue)) {
199
- clack.cancel('Setup cancelled');
227
+ ui.cancel('Setup cancelled');
200
228
  process.exit(0);
201
229
  }
202
230
  // Use sso value if sso-rw is empty
203
231
  const ssoRwTrimmed = ssoRwValue ? ssoRwValue.trim() : '';
204
232
  cookies['sso-rw'] = ssoRwTrimmed.length > 0 ? ssoRwTrimmed : cookies['sso'];
205
233
  if (ssoRwTrimmed.length === 0) {
206
- clack.log.info('Using same value as "sso" for "sso-rw"');
234
+ ui.log.info('Using same value as "sso" for "sso-rw"');
207
235
  }
208
236
  // Prompt for stblid cookie
209
237
  const stblidValue = await clack.text({
@@ -221,11 +249,11 @@ async function setupGrok(providerName, options) {
221
249
  },
222
250
  });
223
251
  if (clack.isCancel(stblidValue)) {
224
- clack.cancel('Setup cancelled');
252
+ ui.cancel('Setup cancelled');
225
253
  process.exit(0);
226
254
  }
227
255
  cookies['stblid'] = stblidValue.trim();
228
- clack.log.success('All cookies collected successfully!');
256
+ ui.log.success('All cookies collected successfully!');
229
257
  }
230
258
  providerConfig = {
231
259
  providerName: providerName,
@@ -234,7 +262,7 @@ async function setupGrok(providerName, options) {
234
262
  };
235
263
  }
236
264
  // Test authentication
237
- const spinner = clack.spinner();
265
+ const spinner = ui.spinner();
238
266
  spinner.start('Testing authentication...');
239
267
  try {
240
268
  const provider = getProvider(providerConfig.providerName);
@@ -243,23 +271,27 @@ async function setupGrok(providerName, options) {
243
271
  await provider.cleanup?.();
244
272
  if (!isAuth) {
245
273
  spinner.stop('Authentication failed');
246
- clack.log.error('Could not authenticate with provided credentials');
274
+ ui.log.error('Could not authenticate with provided credentials');
247
275
  process.exit(1);
248
276
  }
249
277
  spinner.stop('✓ Authentication successful!');
250
278
  }
251
279
  catch (error) {
252
280
  spinner.stop('Authentication failed');
253
- clack.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
281
+ ui.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
254
282
  process.exit(1);
255
283
  }
256
284
  // Save configuration
257
285
  await saveProviderConfig(providerConfig);
258
- clack.log.success('Configuration saved to ~/.ai-vault/config.json');
286
+ ui.log.success('Configuration saved to ~/.ai-vault/config.json');
259
287
  // Configure archive directory
260
288
  const config = await loadConfig();
261
289
  if (!config.settings?.archiveDir) {
262
290
  console.log();
291
+ if (!ui.isInteractive) {
292
+ ui.log.info('Using default archive directory (~/ai-vault-data)');
293
+ return;
294
+ }
263
295
  const customDir = await clack.text({
264
296
  message: 'Archive directory (press Enter for default):',
265
297
  placeholder: '~/ai-vault-data',
@@ -269,12 +301,13 @@ async function setupGrok(providerName, options) {
269
301
  config.settings.archiveDir = customDir;
270
302
  const { saveConfig } = await import('../utils/config.js');
271
303
  await saveConfig(config);
272
- clack.log.info(`Archive directory set to: ${customDir}`);
304
+ ui.log.info(`Archive directory set to: ${customDir}`);
273
305
  }
274
306
  }
275
307
  }
276
308
  async function setupChatGPT(options) {
277
- clack.log.step('Configuring ChatGPT (OpenAI)');
309
+ const ui = createCliUI();
310
+ ui.log.step('Configuring ChatGPT (OpenAI)');
278
311
  let cookies;
279
312
  // Read from file if provided
280
313
  if (options.cookiesFile) {
@@ -290,33 +323,28 @@ async function setupChatGPT(options) {
290
323
  // Already in object format
291
324
  cookies = parsed;
292
325
  }
293
- clack.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
326
+ ui.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
294
327
  }
295
328
  catch (error) {
296
- clack.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
329
+ ui.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
297
330
  process.exit(1);
298
331
  }
299
332
  }
300
333
  else {
334
+ if (!ui.isInteractive) {
335
+ ui.log.error('Interactive cookie entry requires a TTY. Pass --cookies-file.');
336
+ process.exit(1);
337
+ }
301
338
  // Interactive prompt for each cookie
302
- clack.log.info('To get cookies from ChatGPT:');
303
- clack.log.info('1. Open chatgpt.com in your browser and log in');
304
- clack.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
305
- clack.log.info('3. Go to Application → Cookies → https://chatgpt.com');
306
- clack.log.info('4. Find the cookies below and copy their VALUES');
307
- clack.log.info('');
308
- clack.log.warn('⚠️ Important: Some cookies may not be present for all users.');
309
- clack.log.warn(' Press Enter to skip optional cookies.');
339
+ ui.log.info('To get cookies from ChatGPT:');
340
+ ui.log.info('1. Open chatgpt.com in your browser and log in');
341
+ ui.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
342
+ ui.log.info('3. Go to Application → Cookies → https://chatgpt.com');
343
+ ui.log.info('4. Find the cookies below and copy their VALUES');
344
+ ui.log.info('');
345
+ ui.log.warn('⚠️ Important: Some cookies may not be present for all users.');
346
+ ui.log.warn(' Press Enter to skip optional cookies.');
310
347
  console.log();
311
- // Define required cookies for ChatGPT
312
- const requiredCookies = [
313
- {
314
- name: '__Secure-next-auth.session-token',
315
- description: 'Session token (REQUIRED)',
316
- hint: 'Long JWT-like token',
317
- required: true,
318
- },
319
- ];
320
348
  const optionalCookies = [
321
349
  {
322
350
  name: '__Host-next-auth.csrf-token',
@@ -332,23 +360,48 @@ async function setupChatGPT(options) {
332
360
  },
333
361
  ];
334
362
  cookies = {};
335
- // Prompt for required cookies
336
- for (const cookieInfo of requiredCookies) {
337
- const value = await clack.text({
338
- message: `Enter value for cookie "${cookieInfo.name}" (${cookieInfo.description}):`,
339
- placeholder: cookieInfo.hint,
340
- validate: (val) => {
341
- if (!val || val.trim().length === 0) {
342
- return `${cookieInfo.name} is required`;
343
- }
344
- return undefined;
345
- },
346
- });
347
- if (clack.isCancel(value)) {
348
- clack.cancel('Setup cancelled');
349
- process.exit(0);
350
- }
351
- cookies[cookieInfo.name] = value.trim();
363
+ ui.log.warn(' Some accounts now split the session token into .0 and .1 cookies.');
364
+ ui.log.warn(' Provide either the single session token or both .0 and .1 parts.');
365
+ console.log();
366
+ const baseTokenValue = await clack.text({
367
+ message: 'Enter value for cookie "__Secure-next-auth.session-token" (optional if using .0/.1):',
368
+ placeholder: 'Long JWT-like token - press Enter to use .0/.1 cookies',
369
+ });
370
+ if (clack.isCancel(baseTokenValue)) {
371
+ ui.cancel('Setup cancelled');
372
+ process.exit(0);
373
+ }
374
+ const tokenPart0Value = await clack.text({
375
+ message: 'Enter value for cookie "__Secure-next-auth.session-token.0" (optional):',
376
+ placeholder: 'First chunk - press Enter to skip if not found',
377
+ });
378
+ if (clack.isCancel(tokenPart0Value)) {
379
+ ui.cancel('Setup cancelled');
380
+ process.exit(0);
381
+ }
382
+ const tokenPart1Value = await clack.text({
383
+ message: 'Enter value for cookie "__Secure-next-auth.session-token.1" (optional):',
384
+ placeholder: 'Second chunk - press Enter to skip if not found',
385
+ });
386
+ if (clack.isCancel(tokenPart1Value)) {
387
+ ui.cancel('Setup cancelled');
388
+ process.exit(0);
389
+ }
390
+ const baseToken = baseTokenValue?.trim();
391
+ const tokenPart0 = tokenPart0Value?.trim();
392
+ const tokenPart1 = tokenPart1Value?.trim();
393
+ if (!baseToken && (!tokenPart0 || !tokenPart1)) {
394
+ ui.log.error('Missing required session token. Provide "__Secure-next-auth.session-token" or both ".0" and ".1" parts.');
395
+ process.exit(1);
396
+ }
397
+ if (baseToken) {
398
+ cookies['__Secure-next-auth.session-token'] = baseToken;
399
+ }
400
+ if (tokenPart0) {
401
+ cookies['__Secure-next-auth.session-token.0'] = tokenPart0;
402
+ }
403
+ if (tokenPart1) {
404
+ cookies['__Secure-next-auth.session-token.1'] = tokenPart1;
352
405
  }
353
406
  // Prompt for optional cookies
354
407
  for (const cookieInfo of optionalCookies) {
@@ -357,7 +410,7 @@ async function setupChatGPT(options) {
357
410
  placeholder: `${cookieInfo.hint} (press Enter to skip)`,
358
411
  });
359
412
  if (clack.isCancel(value)) {
360
- clack.cancel('Setup cancelled');
413
+ ui.cancel('Setup cancelled');
361
414
  process.exit(0);
362
415
  }
363
416
  const trimmedValue = value?.trim();
@@ -365,7 +418,7 @@ async function setupChatGPT(options) {
365
418
  cookies[cookieInfo.name] = trimmedValue;
366
419
  }
367
420
  }
368
- clack.log.success(`Collected ${Object.keys(cookies).length} cookie${Object.keys(cookies).length > 1 ? 's' : ''} successfully!`);
421
+ ui.log.success(`Collected ${Object.keys(cookies).length} cookie${Object.keys(cookies).length > 1 ? 's' : ''} successfully!`);
369
422
  }
370
423
  const providerConfig = {
371
424
  providerName: 'chatgpt',
@@ -373,7 +426,7 @@ async function setupChatGPT(options) {
373
426
  cookies,
374
427
  };
375
428
  // Test authentication
376
- const spinner = clack.spinner();
429
+ const spinner = ui.spinner();
377
430
  spinner.start('Testing authentication...');
378
431
  try {
379
432
  const provider = getProvider(providerConfig.providerName);
@@ -382,23 +435,27 @@ async function setupChatGPT(options) {
382
435
  await provider.cleanup?.();
383
436
  if (!isAuth) {
384
437
  spinner.stop('Authentication failed');
385
- clack.log.error('Could not authenticate with provided credentials');
438
+ ui.log.error('Could not authenticate with provided credentials');
386
439
  process.exit(1);
387
440
  }
388
441
  spinner.stop('✓ Authentication successful!');
389
442
  }
390
443
  catch (error) {
391
444
  spinner.stop('Authentication failed');
392
- clack.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
445
+ ui.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
393
446
  process.exit(1);
394
447
  }
395
448
  // Save configuration
396
449
  await saveProviderConfig(providerConfig);
397
- clack.log.success('Configuration saved to ~/.ai-vault/config.json');
450
+ ui.log.success('Configuration saved to ~/.ai-vault/config.json');
398
451
  // Configure archive directory
399
452
  const config = await loadConfig();
400
453
  if (!config.settings?.archiveDir) {
401
454
  console.log();
455
+ if (!ui.isInteractive) {
456
+ ui.log.info('Using default archive directory (~/ai-vault-data)');
457
+ return;
458
+ }
402
459
  const customDir = await clack.text({
403
460
  message: 'Archive directory (press Enter for default):',
404
461
  placeholder: '~/ai-vault-data',
@@ -408,12 +465,147 @@ async function setupChatGPT(options) {
408
465
  config.settings.archiveDir = customDir;
409
466
  const { saveConfig } = await import('../utils/config.js');
410
467
  await saveConfig(config);
411
- clack.log.info(`Archive directory set to: ${customDir}`);
468
+ ui.log.info(`Archive directory set to: ${customDir}`);
469
+ }
470
+ }
471
+ }
472
+ async function setupGemini(options) {
473
+ const ui = createCliUI();
474
+ ui.log.step('Configuring Gemini (Google)');
475
+ let cookies;
476
+ // Read from file if provided
477
+ if (options.cookiesFile) {
478
+ try {
479
+ const filePath = resolve(options.cookiesFile);
480
+ const fileContent = readFileSync(filePath, 'utf-8');
481
+ const parsed = JSON.parse(fileContent);
482
+ if (Array.isArray(parsed)) {
483
+ cookies = Object.fromEntries(parsed.map((cookie) => [cookie.name, cookie.value]));
484
+ }
485
+ else {
486
+ cookies = parsed;
487
+ }
488
+ ui.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
489
+ }
490
+ catch (error) {
491
+ ui.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
492
+ process.exit(1);
493
+ }
494
+ }
495
+ else {
496
+ if (!ui.isInteractive) {
497
+ ui.log.error('Interactive cookie entry requires a TTY. Pass --cookies-file.');
498
+ process.exit(1);
499
+ }
500
+ ui.log.info('Gemini requires the following cookies from gemini.google.com:');
501
+ console.log();
502
+ ui.log.info(' Required:');
503
+ ui.log.info(' SID, HSID, SSID, APISID, SAPISID, SIDCC, NID');
504
+ ui.log.info(' __Secure-1PSID, __Secure-1PSIDTS, __Secure-1PSIDCC, __Secure-1PAPISID');
505
+ ui.log.info(' __Secure-3PSID, __Secure-3PSIDTS, __Secure-3PSIDCC, __Secure-3PAPISID');
506
+ ui.log.info(' Optional (but recommended):');
507
+ ui.log.info(' SOCS (required in EU), AEC, S, NID, __Secure-ENID, __Secure-STRP,');
508
+ ui.log.info(' SEARCH_SAMESITE, __Secure-BUCKET, _ga, _gcl_au');
509
+ console.log();
510
+ ui.log.info('How to export all cookies at once:');
511
+ ui.log.info(' 1. Open gemini.google.com in your browser and log in');
512
+ ui.log.info(' 2. Install "Cookie-Editor" or "EditThisCookie" browser extension');
513
+ ui.log.info(' 3. Click the extension icon → Export as JSON');
514
+ ui.log.info(' 4. Paste the JSON below, OR save to a file and use:');
515
+ ui.log.info(' ai-vault setup gemini --cookies-file <path>');
516
+ console.log();
517
+ const jsonInput = await clack.text({
518
+ message: 'Paste cookies JSON (array or {"name":"value"} object):',
519
+ placeholder: '[{"name":"SID","value":"..."},...] or {"SID":"...","HSID":"..."}',
520
+ validate: (val) => {
521
+ if (!val || val.trim().length === 0)
522
+ return 'Cookies JSON is required';
523
+ try {
524
+ const parsed = JSON.parse(val.trim());
525
+ if (typeof parsed !== 'object' || parsed === null)
526
+ return 'Must be a JSON object or array';
527
+ const keys = Array.isArray(parsed)
528
+ ? parsed.map((c) => c?.name).filter(Boolean)
529
+ : Object.keys(parsed);
530
+ if (keys.length === 0)
531
+ return 'No cookies found in JSON';
532
+ const required = ['SID', '__Secure-1PSID', 'SAPISID'];
533
+ const missing = required.filter((k) => !keys.includes(k));
534
+ if (missing.length === required.length) {
535
+ return `Missing key cookies: ${required.join(', ')}`;
536
+ }
537
+ }
538
+ catch {
539
+ return 'Invalid JSON — copy the full export from your browser extension';
540
+ }
541
+ },
542
+ });
543
+ if (clack.isCancel(jsonInput)) {
544
+ ui.cancel('Setup cancelled');
545
+ process.exit(0);
546
+ }
547
+ // JSON validity already confirmed by the validator above
548
+ const parsed = JSON.parse(jsonInput.trim());
549
+ if (Array.isArray(parsed)) {
550
+ cookies = Object.fromEntries(parsed.map((c) => [c.name, c.value]));
551
+ }
552
+ else {
553
+ cookies = parsed;
554
+ }
555
+ ui.log.success(`Loaded ${Object.keys(cookies).length} cookies`);
556
+ }
557
+ const providerConfig = {
558
+ providerName: 'gemini',
559
+ authMethod: 'cookies',
560
+ cookies,
561
+ };
562
+ // Test authentication
563
+ const spinner = ui.spinner();
564
+ spinner.start('Testing authentication...');
565
+ try {
566
+ const provider = getProvider(providerConfig.providerName);
567
+ await provider.authenticate(providerConfig);
568
+ const isAuth = await provider.isAuthenticated();
569
+ await provider.cleanup?.();
570
+ if (!isAuth) {
571
+ spinner.stop('Authentication failed');
572
+ ui.log.error('Could not authenticate with provided credentials');
573
+ process.exit(1);
574
+ }
575
+ spinner.stop('✓ Authentication successful!');
576
+ }
577
+ catch (error) {
578
+ spinner.stop('Authentication failed');
579
+ ui.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
580
+ process.exit(1);
581
+ }
582
+ // Save configuration
583
+ await saveProviderConfig(providerConfig);
584
+ ui.log.success('Configuration saved to ~/.ai-vault/config.json');
585
+ // Configure archive directory
586
+ const config = await loadConfig();
587
+ if (!config.settings?.archiveDir) {
588
+ console.log();
589
+ if (!ui.isInteractive) {
590
+ ui.log.info('Using default archive directory (~/ai-vault-data)');
591
+ return;
592
+ }
593
+ const customDir = await clack.text({
594
+ message: 'Archive directory (press Enter for default):',
595
+ placeholder: '~/ai-vault-data',
596
+ });
597
+ if (!clack.isCancel(customDir) && customDir) {
598
+ config.settings = config.settings || {};
599
+ config.settings.archiveDir = customDir;
600
+ const { saveConfig } = await import('../utils/config.js');
601
+ await saveConfig(config);
602
+ ui.log.info(`Archive directory set to: ${customDir}`);
412
603
  }
413
604
  }
414
605
  }
415
606
  async function setupClaude(options) {
416
- clack.log.step('Configuring Claude (Anthropic)');
607
+ const ui = createCliUI();
608
+ ui.log.step('Configuring Claude (Anthropic)');
417
609
  let cookies;
418
610
  // Read from file if provided
419
611
  if (options.cookiesFile) {
@@ -429,20 +621,24 @@ async function setupClaude(options) {
429
621
  // Already in object format
430
622
  cookies = parsed;
431
623
  }
432
- clack.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
624
+ ui.log.success(`Loaded ${Object.keys(cookies).length} cookies from file`);
433
625
  }
434
626
  catch (error) {
435
- clack.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
627
+ ui.log.error(`Failed to read cookies file: ${error instanceof Error ? error.message : 'Unknown error'}`);
436
628
  process.exit(1);
437
629
  }
438
630
  }
439
631
  else {
632
+ if (!ui.isInteractive) {
633
+ ui.log.error('Interactive cookie entry requires a TTY. Pass --cookies-file.');
634
+ process.exit(1);
635
+ }
440
636
  // Interactive prompt for sessionKey cookie
441
- clack.log.info('To get your sessionKey cookie from Claude:');
442
- clack.log.info('1. Open claude.ai in your browser and log in');
443
- clack.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
444
- clack.log.info('3. Go to Application → Cookies → https://claude.ai');
445
- clack.log.info('4. Find the "sessionKey" cookie and copy its VALUE');
637
+ ui.log.info('To get your sessionKey cookie from Claude:');
638
+ ui.log.info('1. Open claude.ai in your browser and log in');
639
+ ui.log.info('2. Open Developer Tools (F12 or Cmd+Option+I)');
640
+ ui.log.info('3. Go to Application → Cookies → https://claude.ai');
641
+ ui.log.info('4. Find the "sessionKey" cookie and copy its VALUE');
446
642
  console.log();
447
643
  const sessionKey = await clack.text({
448
644
  message: 'Enter value for cookie "sessionKey":',
@@ -462,13 +658,13 @@ async function setupClaude(options) {
462
658
  },
463
659
  });
464
660
  if (clack.isCancel(sessionKey)) {
465
- clack.cancel('Setup cancelled');
661
+ ui.cancel('Setup cancelled');
466
662
  process.exit(0);
467
663
  }
468
664
  cookies = {
469
665
  sessionKey: sessionKey.trim(),
470
666
  };
471
- clack.log.success('sessionKey collected successfully!');
667
+ ui.log.success('sessionKey collected successfully!');
472
668
  }
473
669
  const providerConfig = {
474
670
  providerName: 'claude',
@@ -476,7 +672,7 @@ async function setupClaude(options) {
476
672
  cookies,
477
673
  };
478
674
  // Test authentication
479
- const spinner = clack.spinner();
675
+ const spinner = ui.spinner();
480
676
  spinner.start('Testing authentication...');
481
677
  try {
482
678
  const provider = getProvider(providerConfig.providerName);
@@ -485,23 +681,27 @@ async function setupClaude(options) {
485
681
  await provider.cleanup?.();
486
682
  if (!isAuth) {
487
683
  spinner.stop('Authentication failed');
488
- clack.log.error('Could not authenticate with provided credentials');
684
+ ui.log.error('Could not authenticate with provided credentials');
489
685
  process.exit(1);
490
686
  }
491
687
  spinner.stop('✓ Authentication successful!');
492
688
  }
493
689
  catch (error) {
494
690
  spinner.stop('Authentication failed');
495
- clack.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
691
+ ui.log.error('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
496
692
  process.exit(1);
497
693
  }
498
694
  // Save configuration
499
695
  await saveProviderConfig(providerConfig);
500
- clack.log.success('Configuration saved to ~/.ai-vault/config.json');
696
+ ui.log.success('Configuration saved to ~/.ai-vault/config.json');
501
697
  // Configure archive directory
502
698
  const config = await loadConfig();
503
699
  if (!config.settings?.archiveDir) {
504
700
  console.log();
701
+ if (!ui.isInteractive) {
702
+ ui.log.info('Using default archive directory (~/ai-vault-data)');
703
+ return;
704
+ }
505
705
  const customDir = await clack.text({
506
706
  message: 'Archive directory (press Enter for default):',
507
707
  placeholder: '~/ai-vault-data',
@@ -511,7 +711,7 @@ async function setupClaude(options) {
511
711
  config.settings.archiveDir = customDir;
512
712
  const { saveConfig } = await import('../utils/config.js');
513
713
  await saveConfig(config);
514
- clack.log.info(`Archive directory set to: ${customDir}`);
714
+ ui.log.info(`Archive directory set to: ${customDir}`);
515
715
  }
516
716
  }
517
717
  }