@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.
- package/dist/cli/commands/auth.js +120 -63
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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(
|
|
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
|
-
.
|
|
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 (
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
467
|
-
console.log(c.
|
|
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:');
|