@getlore/cli 0.2.0 → 0.3.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.
@@ -37,6 +37,8 @@ export function registerAuthCommands(program) {
37
37
  .command('login')
38
38
  .description('Sign in with email (OTP)')
39
39
  .option('-e, --email <email>', 'Email address')
40
+ .option('--code <code>', 'OTP code (skip interactive prompt — use after --send-only)')
41
+ .option('--send-only', 'Send OTP and exit without waiting for code')
40
42
  .action(async (options) => {
41
43
  const { sendOTP, verifyOTP, sessionFromMagicLink, waitForMagicLinkCallback, isAuthenticated } = await import('../../core/auth.js');
42
44
  // Check if already logged in
@@ -52,25 +54,37 @@ export function registerAuthCommands(program) {
52
54
  console.error(c.error('Email is required'));
53
55
  process.exit(1);
54
56
  }
55
- // Start the localhost callback server before sending the OTP
56
- // so it's ready when the user clicks the magic link
57
- const callback = waitForMagicLinkCallback({
58
- onListening: () => {
59
- // Server is ready now send the OTP
60
- },
61
- });
57
+ // Non-interactive: --code provided verify directly (OTP must have been sent already)
58
+ if (options.code) {
59
+ const { session, error } = await verifyOTP(email, options.code);
60
+ if (error || !session) {
61
+ console.error(c.error(`Verification failed: ${error || 'Unknown error'}`));
62
+ process.exit(1);
63
+ }
64
+ console.log(c.success(`Logged in as ${session.user.email}`));
65
+ return;
66
+ }
67
+ // Send OTP
62
68
  console.log(c.dim(`Sending code to ${email}...`));
63
69
  const { error: sendError } = await sendOTP(email);
64
70
  if (sendError) {
65
- callback.abort();
66
71
  console.error(c.error(`Failed to send code: ${sendError}`));
67
72
  process.exit(1);
68
73
  }
69
- console.log(c.success('Check your email!'));
74
+ console.log(c.success(`OTP sent to ${email}`));
75
+ // --send-only: exit after sending
76
+ if (options.sendOnly) {
77
+ console.log(c.dim('Re-run with --code <code> to complete login.'));
78
+ return;
79
+ }
70
80
  console.log(c.dim('Click the magic link in the email — it will sign you in automatically.'));
71
81
  console.log(c.dim('Or paste a 6-digit code if your email shows one.'));
72
82
  console.log('');
73
83
  console.log(c.dim('Waiting for magic link click...'));
84
+ // Start the localhost callback server
85
+ const callback = waitForMagicLinkCallback({
86
+ onListening: () => { },
87
+ });
74
88
  // Race: callback server catches the magic link, or user pastes a code/URL
75
89
  const readline = await import('readline');
76
90
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -158,19 +172,27 @@ export function registerAuthCommands(program) {
158
172
  program
159
173
  .command('setup')
160
174
  .description('Guided setup wizard (config, login, init)')
161
- .action(async () => {
175
+ .option('--openai-key <key>', 'OpenAI API key (skip prompt)')
176
+ .option('--anthropic-key <key>', 'Anthropic API key (skip prompt)')
177
+ .option('-e, --email <email>', 'Email address (skip prompt)')
178
+ .option('--data-dir <dir>', 'Data directory path (skip prompt)')
179
+ .option('--code <code>', 'OTP code (for non-interactive login)')
180
+ .option('--skip-login', 'Skip the login step (use if already authenticated)')
181
+ .action(async (options) => {
162
182
  const { saveLoreConfig, getLoreConfigPath } = await import('../../core/config.js');
163
183
  const { sendOTP, verifyOTP, sessionFromMagicLink, isAuthenticated, loadAuthSession } = await import('../../core/auth.js');
164
184
  const { bridgeConfigToEnv } = await import('../../core/config.js');
185
+ // Non-interactive mode: skip prompts when all key flags are provided
186
+ const nonInteractive = !!(options.openaiKey && options.anthropicKey && options.email);
165
187
  console.log(`\n${c.title('Lore Setup Wizard')}`);
166
188
  console.log(`${c.dim('=')}`.repeat(40) + '\n');
167
189
  // ── Step 1: Configuration ───────────────────────────────────────
168
190
  console.log(c.bold('Step 1: Configuration\n'));
169
191
  const { expandPath } = await import('../../sync/config.js');
170
192
  const defaultDataDir = process.env.LORE_DATA_DIR || '~/.lore';
171
- const dataDir = await prompt('Data directory', defaultDataDir);
172
- const openaiApiKey = await prompt('OpenAI API Key (for embeddings)');
173
- const anthropicApiKey = await prompt('Anthropic API Key (for sync & research)');
193
+ const dataDir = options.dataDir || await prompt('Data directory', defaultDataDir);
194
+ const openaiApiKey = options.openaiKey || process.env.OPENAI_API_KEY || await prompt('OpenAI API Key (for embeddings)');
195
+ const anthropicApiKey = options.anthropicKey || process.env.ANTHROPIC_API_KEY || await prompt('Anthropic API Key (for sync & research)');
174
196
  await saveLoreConfig({
175
197
  ...(dataDir ? { data_dir: expandPath(dataDir) } : {}),
176
198
  ...(openaiApiKey ? { openai_api_key: openaiApiKey } : {}),
@@ -181,46 +203,75 @@ export function registerAuthCommands(program) {
181
203
  await bridgeConfigToEnv();
182
204
  // ── Step 2: Login ──────────────────────────────────────────────────
183
205
  console.log(c.bold('Step 2: Login\n'));
184
- if (await isAuthenticated()) {
206
+ if (options.skipLogin) {
207
+ console.log(c.dim('Skipped (--skip-login)\n'));
208
+ }
209
+ else if (await isAuthenticated()) {
185
210
  const session = await loadAuthSession();
186
211
  console.log(c.success(`Already logged in as ${session?.user.email}\n`));
187
212
  }
188
213
  else {
189
- const email = await prompt('Email');
214
+ const email = options.email || await prompt('Email');
190
215
  if (!email) {
191
216
  console.error(c.error('Email is required'));
192
217
  process.exit(1);
193
218
  }
194
- console.log(c.dim(`Sending code to ${email}...`));
195
- const { error: sendError } = await sendOTP(email);
196
- if (sendError) {
197
- console.error(c.error(`Failed to send code: ${sendError}`));
198
- process.exit(1);
199
- }
200
- console.log(c.success('Check your email!'));
201
- console.log(c.dim('You may receive a 6-digit code or a magic link URL.'));
202
- const response = await prompt('Paste the code or the full magic link URL');
203
- if (!response) {
204
- console.error(c.error('Code or magic link is required'));
205
- process.exit(1);
219
+ if (options.code) {
220
+ // Non-interactive: --code provided verify directly (OTP must have been sent already)
221
+ const result = await verifyOTP(email, options.code);
222
+ if (result.error || !result.session) {
223
+ console.error(c.error(`Verification failed: ${result.error || 'Unknown error'}`));
224
+ process.exit(1);
225
+ }
226
+ console.log(c.success(`Logged in as ${result.session.user.email}\n`));
206
227
  }
207
- let session;
208
- let verifyError;
209
- if (response.startsWith('http')) {
210
- const result = await sessionFromMagicLink(response);
211
- session = result.session;
212
- verifyError = result.error;
228
+ else if (nonInteractive) {
229
+ // Non-interactive without --code: send OTP and exit so agent can retrieve code
230
+ console.log(c.dim(`Sending code to ${email}...`));
231
+ const { error: sendError } = await sendOTP(email);
232
+ if (sendError) {
233
+ console.error(c.error(`Failed to send code: ${sendError}`));
234
+ process.exit(1);
235
+ }
236
+ console.log(c.success(`OTP sent to ${email}`));
237
+ console.log(c.dim('Re-run with --code <code> to complete setup.\n'));
238
+ console.log(c.dim('Example:'));
239
+ console.log(c.dim(` lore setup --openai-key ... --anthropic-key ... --email ${email} --code <code>\n`));
240
+ return; // Exit — config is saved, agent re-runs with --code
213
241
  }
214
242
  else {
215
- const result = await verifyOTP(email, response);
216
- session = result.session;
217
- verifyError = result.error;
218
- }
219
- if (verifyError || !session) {
220
- console.error(c.error(`Verification failed: ${verifyError || 'Unknown error'}`));
221
- process.exit(1);
243
+ // Interactive: send OTP and prompt
244
+ console.log(c.dim(`Sending code to ${email}...`));
245
+ const { error: sendError } = await sendOTP(email);
246
+ if (sendError) {
247
+ console.error(c.error(`Failed to send code: ${sendError}`));
248
+ process.exit(1);
249
+ }
250
+ console.log(c.success('Check your email!'));
251
+ console.log(c.dim('You may receive a 6-digit code or a magic link URL.'));
252
+ const response = await prompt('Paste the code or the full magic link URL');
253
+ if (!response) {
254
+ console.error(c.error('Code or magic link is required'));
255
+ process.exit(1);
256
+ }
257
+ let session;
258
+ let verifyError;
259
+ if (response.startsWith('http')) {
260
+ const result = await sessionFromMagicLink(response);
261
+ session = result.session;
262
+ verifyError = result.error;
263
+ }
264
+ else {
265
+ const result = await verifyOTP(email, response);
266
+ session = result.session;
267
+ verifyError = result.error;
268
+ }
269
+ if (verifyError || !session) {
270
+ console.error(c.error(`Verification failed: ${verifyError || 'Unknown error'}`));
271
+ process.exit(1);
272
+ }
273
+ console.log(c.success(`Logged in as ${session.user.email}\n`));
222
274
  }
223
- console.log(c.success(`Logged in as ${session.user.email}\n`));
224
275
  }
225
276
  // ── Step 3: Data Repository ────────────────────────────────────────
226
277
  console.log(c.bold('Step 3: Data Repository\n'));
@@ -262,7 +313,7 @@ export function registerAuthCommands(program) {
262
313
  if (savedUrl) {
263
314
  // Machine B: clone existing repo
264
315
  console.log(c.success(`Found your data repo URL: ${savedUrl}`));
265
- const cloneIt = await prompt('Clone it to ' + resolvedDataDir + '? (y/n)', 'y');
316
+ const cloneIt = nonInteractive ? 'y' : await prompt('Clone it to ' + resolvedDataDir + '? (y/n)', 'y');
266
317
  if (cloneIt.toLowerCase() === 'y') {
267
318
  try {
268
319
  const { execSync } = await import('child_process');
@@ -284,9 +335,9 @@ export function registerAuthCommands(program) {
284
335
  console.log(c.success('Created data repository.\n'));
285
336
  // Try to set up GitHub remote
286
337
  if (await isGhAvailable()) {
287
- const createRepo = await prompt('Create a private GitHub repo for cross-machine sync? (y/n)', 'y');
338
+ const createRepo = nonInteractive ? 'y' : await prompt('Create a private GitHub repo for cross-machine sync? (y/n)', 'y');
288
339
  if (createRepo.toLowerCase() === 'y') {
289
- const repoName = await prompt('Repository name', 'lore-data');
340
+ const repoName = nonInteractive ? 'lore-data' : await prompt('Repository name', 'lore-data');
290
341
  const url = await createGithubRepo(resolvedDataDir, repoName);
291
342
  if (url) {
292
343
  console.log(c.success(`Created and pushed to ${url}\n`));
@@ -303,7 +354,7 @@ export function registerAuthCommands(program) {
303
354
  }
304
355
  }
305
356
  }
306
- else {
357
+ else if (!nonInteractive) {
307
358
  const remoteUrl = await prompt('Git remote URL for cross-machine sync (or press Enter to skip)');
308
359
  if (remoteUrl) {
309
360
  try {
@@ -344,7 +395,7 @@ export function registerAuthCommands(program) {
344
395
  if (savedUrl) {
345
396
  // Found existing repo — add as remote and pull
346
397
  console.log(c.success(`Found your data repo: ${savedUrl}`));
347
- const useIt = await prompt('Add as remote and pull? (y/n)', 'y');
398
+ const useIt = nonInteractive ? 'y' : await prompt('Add as remote and pull? (y/n)', 'y');
348
399
  if (useIt.toLowerCase() === 'y') {
349
400
  try {
350
401
  const { execSync } = await import('child_process');
@@ -359,9 +410,9 @@ export function registerAuthCommands(program) {
359
410
  }
360
411
  }
361
412
  else if (await isGhAvailable()) {
362
- const createRepo = await prompt('Create a private GitHub repo for cross-machine sync? (y/n)', 'y');
413
+ const createRepo = nonInteractive ? 'y' : await prompt('Create a private GitHub repo for cross-machine sync? (y/n)', 'y');
363
414
  if (createRepo.toLowerCase() === 'y') {
364
- const repoName = await prompt('Repository name', 'lore-data');
415
+ const repoName = nonInteractive ? 'lore-data' : await prompt('Repository name', 'lore-data');
365
416
  const url = await createGithubRepo(resolvedDataDir, repoName);
366
417
  if (url) {
367
418
  console.log(c.success(`Created and pushed to ${url}\n`));
@@ -377,7 +428,7 @@ export function registerAuthCommands(program) {
377
428
  }
378
429
  }
379
430
  }
380
- else {
431
+ else if (!nonInteractive) {
381
432
  const remoteUrl = await prompt('Git remote URL for cross-machine sync (or press Enter to skip)');
382
433
  if (remoteUrl) {
383
434
  try {
@@ -430,7 +481,7 @@ export function registerAuthCommands(program) {
430
481
  }
431
482
  // ── Step 5: Background Daemon ──────────────────────────────────────
432
483
  console.log(c.bold('Step 5: Background Daemon\n'));
433
- const startDaemon = await prompt('Start background sync daemon? (y/n)', 'y');
484
+ const startDaemon = nonInteractive ? 'y' : await prompt('Start background sync daemon? (y/n)', 'y');
434
485
  if (startDaemon.toLowerCase() === 'y') {
435
486
  try {
436
487
  const { startDaemonProcess } = await import('./sync.js');
@@ -455,22 +506,28 @@ export function registerAuthCommands(program) {
455
506
  }
456
507
  // ── Step 6: Agent Skills ──────────────────────────────────────────
457
508
  console.log(c.bold('Step 6: Agent Skills\n'));
458
- console.log(c.dim('Lore works best when your AI agents know how to use it.'));
459
- console.log(c.dim('Install instruction files so agents automatically search and ingest into Lore.\n'));
460
- try {
461
- const { interactiveSkillInstall } = await import('./skills.js');
462
- const installed = await interactiveSkillInstall();
463
- if (installed.length > 0) {
464
- console.log(c.success(`\nInstalled skills: ${installed.join(', ')}\n`));
509
+ if (nonInteractive) {
510
+ console.log(c.dim('Skipped in non-interactive mode.'));
511
+ console.log(c.dim('Install later with: lore skills install <name>\n'));
512
+ }
513
+ else {
514
+ console.log(c.dim('Lore works best when your AI agents know how to use it.'));
515
+ console.log(c.dim('Install instruction files so agents automatically search and ingest into Lore.\n'));
516
+ try {
517
+ const { interactiveSkillInstall } = await import('./skills.js');
518
+ const installed = await interactiveSkillInstall();
519
+ if (installed.length > 0) {
520
+ console.log(c.success(`\nInstalled skills: ${installed.join(', ')}\n`));
521
+ }
522
+ else {
523
+ console.log(c.dim('\nSkipped. You can install later with: lore skills install <name>\n'));
524
+ }
465
525
  }
466
- else {
467
- console.log(c.dim('\nSkipped. You can install later with: lore skills install <name>\n'));
526
+ catch (err) {
527
+ console.log(c.warning(`Could not install skills: ${err instanceof Error ? err.message : err}`));
528
+ console.log(c.dim('You can install later with: lore skills install <name>\n'));
468
529
  }
469
530
  }
470
- catch (err) {
471
- console.log(c.warning(`Could not install skills: ${err instanceof Error ? err.message : err}`));
472
- console.log(c.dim('You can install later with: lore skills install <name>\n'));
473
- }
474
531
  // ── Done ───────────────────────────────────────────────────────────
475
532
  console.log(c.title('Setup complete!\n'));
476
533
  console.log('Try these commands:');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getlore/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Research knowledge repository with semantic search, citations, and project lineage tracking",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",