@getlore/cli 0.8.0 → 0.8.1

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/README.md CHANGED
@@ -62,7 +62,7 @@ If the MCP host doesn't inherit your shell environment (e.g. Claude Desktop), ad
62
62
  | Command | Description |
63
63
  |---------|-------------|
64
64
  | `lore setup` | Guided configuration wizard |
65
- | `lore login` | Sign in with email OTP |
65
+ | `lore auth login` | Sign in with email OTP |
66
66
  | `lore sync` | Sync all configured sources |
67
67
  | `lore sync add` | Add a source directory |
68
68
  | `lore search <query>` | Hybrid search |
@@ -77,7 +77,7 @@ If the MCP host doesn't inherit your shell environment (e.g. Claude Desktop), ad
77
77
  - **Node.js** 18+
78
78
  - **OpenAI API key** (embeddings)
79
79
  - **Anthropic API key** (metadata extraction & research)
80
- - **Lore account** (free — sign up via `lore login`)
80
+ - **Lore account** (free — sign up via `lore auth login`)
81
81
 
82
82
  ## How Sync Works
83
83
 
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import path from 'path';
10
10
  import { c } from '../colors.js';
11
+ import { getLogo } from '../logo.js';
11
12
  // ============================================================================
12
13
  // Readline helper
13
14
  // ============================================================================
@@ -46,7 +47,7 @@ export function registerAuthCommands(program) {
46
47
  const { loadAuthSession } = await import('../../core/auth.js');
47
48
  const session = await loadAuthSession();
48
49
  console.log(c.success(`Already logged in as ${session?.user.email}`));
49
- console.log(c.dim('Run \'lore logout\' first to switch accounts.'));
50
+ console.log(c.dim('Run \'lore auth logout\' first to switch accounts.'));
50
51
  return;
51
52
  }
52
53
  const email = options.email || await prompt('Email');
@@ -119,7 +120,7 @@ export function registerAuthCommands(program) {
119
120
  }
120
121
  else {
121
122
  callback.abort();
122
- console.error(c.error('Login timed out. Try again with \'lore login\'.'));
123
+ console.error(c.error('Login timed out. Try again with \'lore auth login\'.'));
123
124
  process.exit(1);
124
125
  }
125
126
  if (verifyError || !session) {
@@ -153,7 +154,7 @@ export function registerAuthCommands(program) {
153
154
  }
154
155
  const session = await getValidSession();
155
156
  if (!session) {
156
- console.log(c.dim('Not logged in. Run \'lore login\' to sign in.'));
157
+ console.log(c.dim('Not logged in. Run \'lore auth login\' to sign in.'));
157
158
  return;
158
159
  }
159
160
  console.log(`${c.bold('Email:')} ${session.user.email}`);
@@ -178,14 +179,21 @@ export function registerAuthCommands(program) {
178
179
  .option('--data-dir <dir>', 'Data directory path (skip prompt)')
179
180
  .option('--code <code>', 'OTP code (for non-interactive login)')
180
181
  .option('--skip-login', 'Skip the login step (use if already authenticated)')
182
+ .option('--sync-path <path>', 'Sync source directory path (non-interactive)')
183
+ .option('--sync-project <project>', 'Default project for sync source (non-interactive)')
184
+ .option('--sync-name <name>', 'Name for sync source (non-interactive, defaults to project name)')
181
185
  .action(async (options) => {
182
186
  const { saveLoreConfig, getLoreConfigPath } = await import('../../core/config.js');
183
187
  const { sendOTP, verifyOTP, sessionFromMagicLink, isAuthenticated, loadAuthSession } = await import('../../core/auth.js');
184
188
  const { bridgeConfigToEnv } = await import('../../core/config.js');
185
189
  // Non-interactive mode: skip prompts when all key flags are provided
186
190
  const nonInteractive = !!(options.openaiKey && options.anthropicKey && options.email);
187
- console.log(`\n${c.title('Lore Setup Wizard')}`);
188
- console.log(`${c.dim('=')}`.repeat(40) + '\n');
191
+ console.log('');
192
+ console.log(getLogo());
193
+ console.log('');
194
+ console.log(` ${c.title('Setup Wizard')}`);
195
+ console.log(` ${c.dim('━'.repeat(12))}`);
196
+ console.log('');
189
197
  // ── Step 1: Configuration ───────────────────────────────────────
190
198
  console.log(c.bold('Step 1: Configuration\n'));
191
199
  const { expandPath } = await import('../../sync/config.js');
@@ -479,8 +487,74 @@ export function registerAuthCommands(program) {
479
487
  console.log(c.warning(`Skipped — ${err instanceof Error ? err.message : 'could not index welcome document'}`));
480
488
  console.log(c.dim('You can add documents later with lore sync\n'));
481
489
  }
482
- // ── Step 5: Background Daemon ──────────────────────────────────────
483
- console.log(c.bold('Step 5: Background Daemon\n'));
490
+ // ── Step 5: Sync Sources ──────────────────────────────────────────
491
+ console.log(c.bold('Step 5: Sync Sources\n'));
492
+ if (nonInteractive) {
493
+ if (options.syncPath && options.syncProject) {
494
+ const { addSyncSource } = await import('../../sync/config.js');
495
+ const syncName = options.syncName || options.syncProject.charAt(0).toUpperCase() + options.syncProject.slice(1);
496
+ try {
497
+ await addSyncSource({
498
+ name: syncName,
499
+ path: expandPath(options.syncPath),
500
+ glob: '**/*',
501
+ project: options.syncProject,
502
+ enabled: true,
503
+ });
504
+ console.log(c.success(`Added sync source "${syncName}" → ${options.syncPath}\n`));
505
+ }
506
+ catch (err) {
507
+ console.log(c.warning(`Could not add sync source: ${err instanceof Error ? err.message : err}\n`));
508
+ }
509
+ }
510
+ else {
511
+ console.log(c.dim('Skipped (no --sync-path/--sync-project provided).\n'));
512
+ }
513
+ }
514
+ else {
515
+ console.log(c.dim('Sync sources are directories on your machine that Lore watches'));
516
+ console.log(c.dim('for new files. When files appear or change, they\'re automatically'));
517
+ console.log(c.dim('indexed into your knowledge base.\n'));
518
+ console.log(c.dim('Supported formats: Markdown, JSON, JSONL, plain text, CSV,'));
519
+ console.log(c.dim('HTML, XML, PDF, and images.\n'));
520
+ let addMore = true;
521
+ while (addMore) {
522
+ const wantsSource = await prompt('Would you like to add a sync source? (y/n)', 'y');
523
+ if (wantsSource.toLowerCase() !== 'y') {
524
+ if (wantsSource.toLowerCase() === 'n') {
525
+ console.log(c.dim('You can add sources anytime with: lore sync add\n'));
526
+ }
527
+ break;
528
+ }
529
+ const sourceName = await prompt('Name (e.g., "Meeting Notes")');
530
+ const sourcePath = await prompt('Path (e.g., ~/Documents/notes)');
531
+ const sourceProject = await prompt('Default project');
532
+ if (!sourceName || !sourcePath || !sourceProject) {
533
+ console.log(c.warning('Name, path, and project are all required. Skipping.\n'));
534
+ break;
535
+ }
536
+ try {
537
+ const { addSyncSource } = await import('../../sync/config.js');
538
+ await addSyncSource({
539
+ name: sourceName,
540
+ path: expandPath(sourcePath),
541
+ glob: '**/*',
542
+ project: sourceProject,
543
+ enabled: true,
544
+ });
545
+ console.log(c.success(`Added source "${sourceName}"`));
546
+ console.log(c.dim(` Path: ${sourcePath}`));
547
+ console.log(c.dim(` Glob: **/* (all supported files)\n`));
548
+ }
549
+ catch (err) {
550
+ console.log(c.warning(`Could not add source: ${err instanceof Error ? err.message : err}\n`));
551
+ }
552
+ const another = await prompt('Add another source? (y/n)', 'n');
553
+ addMore = another.toLowerCase() === 'y';
554
+ }
555
+ }
556
+ // ── Step 6: Background Daemon ──────────────────────────────────────
557
+ console.log(c.bold('Step 6: Background Daemon\n'));
484
558
  const startDaemon = nonInteractive ? 'y' : await prompt('Start background sync daemon? (y/n)', 'y');
485
559
  if (startDaemon.toLowerCase() === 'y') {
486
560
  try {
@@ -504,8 +578,8 @@ export function registerAuthCommands(program) {
504
578
  else {
505
579
  console.log(c.dim('You can start it later with: lore sync start\n'));
506
580
  }
507
- // ── Step 6: Agent Skills ──────────────────────────────────────────
508
- console.log(c.bold('Step 6: Agent Skills\n'));
581
+ // ── Step 7: Agent Skills ──────────────────────────────────────────
582
+ console.log(c.bold('Step 7: Agent Skills\n'));
509
583
  if (nonInteractive) {
510
584
  console.log(c.dim('Skipped in non-interactive mode.'));
511
585
  console.log(c.dim('Install later with: lore skills install <name>\n'));
@@ -148,7 +148,7 @@ Done! To use this data repository:
148
148
  "env": { "LORE_DATA_DIR": "${expandedPath}" }
149
149
 
150
150
  3. Add sync sources:
151
- lore sync add --name "My Notes" --path ~/notes --glob "**/*.md" -p myproject
151
+ lore sync add --name "My Notes" --path ~/notes -p myproject
152
152
 
153
153
  Tip: Run 'lore setup' for the full guided experience (config + login + data repo).
154
154
  `);
@@ -4,7 +4,7 @@
4
4
  * All sync-related functionality: one-time sync, daemon, watch, sources.
5
5
  */
6
6
  import { spawn, spawnSync } from 'child_process';
7
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
7
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from 'fs';
8
8
  import { mkdir } from 'fs/promises';
9
9
  import path from 'path';
10
10
  import os from 'os';
@@ -598,54 +598,115 @@ export function registerSyncCommand(program, defaultDataDir) {
598
598
  .description('List configured sync sources')
599
599
  .action(async () => {
600
600
  const { loadSyncConfig, getConfigPath } = await import('../../sync/config.js');
601
- console.log(`\nSync Sources`);
602
- console.log(`============`);
603
- console.log(`Config: ${getConfigPath()}\n`);
601
+ console.log('');
602
+ console.log(` ${c.title('Sync Sources')}`);
603
+ console.log(` ${c.dim('━'.repeat(12))}`);
604
+ console.log('');
604
605
  const config = await loadSyncConfig();
605
606
  if (config.sources.length === 0) {
606
- console.log('No sources configured. Run "lore sync add" to add one.');
607
+ console.log(c.dim(' No sources configured.'));
608
+ console.log(` Run ${c.bold('lore sync add')} to add one.`);
609
+ console.log('');
607
610
  return;
608
611
  }
609
612
  for (const source of config.sources) {
610
- const status = source.enabled ? '✓' : '○';
611
- console.log(`${status} ${source.name}`);
612
- console.log(` Path: ${source.path}`);
613
- console.log(` Glob: ${source.glob}`);
613
+ if (source.enabled) {
614
+ console.log(` ${c.success('✓')} ${c.bold(source.name)}`);
615
+ }
616
+ else {
617
+ console.log(` ${c.dim('○')} ${c.dim(source.name + ' (disabled)')}`);
618
+ }
619
+ console.log(` Path: ${source.path}`);
620
+ console.log(` Glob: ${c.dim(source.glob)}`);
614
621
  console.log(` Project: ${source.project}`);
615
622
  console.log('');
616
623
  }
624
+ console.log(c.dim(` Config: ${getConfigPath()}`));
625
+ console.log(c.dim(` Add more with: lore sync add`));
626
+ console.log('');
617
627
  });
618
628
  syncCmd
619
629
  .command('add')
620
630
  .description('Add a new sync source directory')
621
631
  .option('-n, --name <name>', 'Source name')
622
632
  .option('-p, --path <path>', 'Directory path')
623
- .option('-g, --glob <glob>', 'File glob pattern', '**/*.md')
633
+ .option('-g, --glob <glob>', 'File glob pattern', '**/*')
624
634
  .option('--project <project>', 'Default project')
625
635
  .action(async (options) => {
626
- const { addSyncSource } = await import('../../sync/config.js');
636
+ const { addSyncSource, expandPath } = await import('../../sync/config.js');
637
+ // Non-interactive: path and project provided
638
+ const nonInteractive = !!(options.path && options.project);
639
+ if (nonInteractive) {
640
+ const dirBase = path.basename(expandPath(options.path)) || 'Source';
641
+ const name = options.name || dirBase.charAt(0).toUpperCase() + dirBase.slice(1);
642
+ try {
643
+ await addSyncSource({
644
+ name,
645
+ path: options.path,
646
+ glob: options.glob || '**/*',
647
+ project: options.project,
648
+ enabled: true,
649
+ });
650
+ console.log(`\n ${c.success('✓')} Added "${name}"`);
651
+ console.log(` Path: ${options.path}`);
652
+ console.log(` Glob: ${options.glob || '**/*'}`);
653
+ console.log(` Project: ${options.project}`);
654
+ console.log(`\n Run ${c.bold("'lore sync'")} to index these files now.\n`);
655
+ }
656
+ catch (error) {
657
+ console.error(`\nError: ${error}`);
658
+ process.exit(1);
659
+ }
660
+ return;
661
+ }
662
+ // Interactive flow: path → project → done
627
663
  const readline = await import('readline');
628
664
  const rl = readline.createInterface({
629
665
  input: process.stdin,
630
666
  output: process.stdout,
631
667
  });
632
668
  const ask = (question, defaultValue) => new Promise((resolve) => {
633
- const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
669
+ const prompt = defaultValue ? ` ${question} [${defaultValue}]: ` : ` ${question}: `;
634
670
  rl.question(prompt, (answer) => {
635
671
  resolve(answer.trim() || defaultValue || '');
636
672
  });
637
673
  });
638
- console.log(`\nAdd Sync Source`);
639
- console.log(`===============\n`);
640
- const name = options.name || await ask('Name (e.g., "Granola Meetings")');
641
- const sourcePath = options.path || await ask('Path (e.g., ~/granola-extractor/output)');
642
- const glob = options.glob || await ask('Glob pattern', '**/*.md');
643
- const project = options.project || await ask('Default project');
644
- rl.close();
645
- if (!name || !sourcePath || !project) {
646
- console.log('\nAll fields are required.');
674
+ console.log('');
675
+ // Path
676
+ const sourcePath = options.path || await ask('Path');
677
+ if (!sourcePath) {
678
+ rl.close();
679
+ console.log(c.warning('\n Path is required.\n'));
647
680
  process.exit(1);
648
681
  }
682
+ // Validate path
683
+ const resolved = expandPath(sourcePath);
684
+ if (existsSync(resolved)) {
685
+ try {
686
+ const count = readdirSync(resolved).length;
687
+ console.log(c.success(` ✓ Found (${count} items)`));
688
+ }
689
+ catch {
690
+ console.log(c.success(' ✓ Directory exists'));
691
+ }
692
+ }
693
+ else {
694
+ console.log(c.warning(' ⚠ Directory does not exist yet'));
695
+ }
696
+ // Derive name from directory basename
697
+ const dirName = path.basename(resolved) || 'Source';
698
+ const defaultName = options.name || dirName.charAt(0).toUpperCase() + dirName.slice(1);
699
+ const defaultProject = options.project || dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
700
+ // Project
701
+ const project = await ask('Project', defaultProject);
702
+ if (!project) {
703
+ rl.close();
704
+ console.log(c.warning('\n Project is required.\n'));
705
+ process.exit(1);
706
+ }
707
+ rl.close();
708
+ const name = options.name || defaultName;
709
+ const glob = options.glob || '**/*';
649
710
  try {
650
711
  await addSyncSource({
651
712
  name,
@@ -654,11 +715,17 @@ export function registerSyncCommand(program, defaultDataDir) {
654
715
  project,
655
716
  enabled: true,
656
717
  });
657
- console.log(`\n✓ Added source "${name}"`);
658
- console.log(`\nRun "lore sync" to process files from this source.`);
718
+ console.log('');
719
+ console.log(` ${c.success('✓')} Added "${c.bold(name)}"`);
720
+ console.log(` Path: ${sourcePath}`);
721
+ console.log(` Glob: ${c.dim(glob)}`);
722
+ console.log(` Project: ${project}`);
723
+ console.log('');
724
+ console.log(` Run ${c.bold("'lore sync'")} to index these files now.`);
725
+ console.log('');
659
726
  }
660
727
  catch (error) {
661
- console.error(`\nError: ${error}`);
728
+ console.error(`\n ${c.error(String(error))}\n`);
662
729
  process.exit(1);
663
730
  }
664
731
  });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ASCII Art Logo
3
+ *
4
+ * Renders the `> lore` brand as ASCII art:
5
+ * - Yellow chevron (╲ ▸ ╱) on the left
6
+ * - Cyan bold box-drawing "lore" on the right
7
+ *
8
+ * Used in the welcome screen and setup wizard.
9
+ */
10
+ /**
11
+ * Returns the colored ASCII logo with tagline.
12
+ */
13
+ export declare function getLogo(): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ASCII Art Logo
3
+ *
4
+ * Renders the `> lore` brand as ASCII art:
5
+ * - Yellow chevron (╲ ▸ ╱) on the left
6
+ * - Cyan bold box-drawing "lore" on the right
7
+ *
8
+ * Used in the welcome screen and setup wizard.
9
+ */
10
+ import { colors, c } from './colors.js';
11
+ // Yellow chevron Cyan "lore" in box-drawing
12
+ // ╲ ┃ ┏━┓ ┏━┓ ┏━━
13
+ // ▸ ┃ ┃ ┃ ┣┳┛ ┣━
14
+ // ╱ ┗━ ┗━┛ ┗┻╸ ┗━━
15
+ function yel(s) {
16
+ return `${colors.bold}${colors.yellow}${s}${colors.reset}`;
17
+ }
18
+ function lore(s) {
19
+ return c.title(s);
20
+ }
21
+ /**
22
+ * Returns the colored ASCII logo with tagline.
23
+ */
24
+ export function getLogo() {
25
+ const lines = [
26
+ ` ${yel('╲')} ${lore('┃ ┏━┓ ┏━┓ ┏━━')}`,
27
+ ` ${yel('▸')} ${lore('┃ ┃ ┃ ┣┳┛ ┣━')}`,
28
+ ` ${yel('╱')} ${lore('┗━ ┗━┛ ┗┻╸ ┗━━')}`,
29
+ ].join('\n');
30
+ const tagline = c.dim(' Research Knowledge Repository');
31
+ return `${lines}\n${tagline}`;
32
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Welcome Screen
3
+ *
4
+ * Shown when `lore` is run with no command.
5
+ * Shows every command, categorized. This IS the help.
6
+ */
7
+ export declare function showWelcomeScreen(): void;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Welcome Screen
3
+ *
4
+ * Shown when `lore` is run with no command.
5
+ * Shows every command, categorized. This IS the help.
6
+ */
7
+ import { c } from './colors.js';
8
+ import { getLogo } from './logo.js';
9
+ export function showWelcomeScreen() {
10
+ console.log('');
11
+ console.log(getLogo());
12
+ console.log('');
13
+ console.log(` ${c.bold('Setup:')}`);
14
+ console.log(` ${c.dim('•')} lore setup ${c.dim('— guided first-time setup')}`);
15
+ console.log(` ${c.dim('•')} lore auth login ${c.dim('— sign in with email')}`);
16
+ console.log(` ${c.dim('•')} lore auth logout ${c.dim('— sign out')}`);
17
+ console.log(` ${c.dim('•')} lore auth whoami ${c.dim('— check login status')}`);
18
+ console.log('');
19
+ console.log(` ${c.bold('Search & Research:')}`);
20
+ console.log(` ${c.dim('•')} lore search "query" ${c.dim('— semantic + keyword search')}`);
21
+ console.log(` ${c.dim('•')} lore research "query" ${c.dim('— AI-powered deep research')}`);
22
+ console.log(` ${c.dim('•')} lore ask "question" ${c.dim('— quick question answering')}`);
23
+ console.log(` ${c.dim('•')} lore browse ${c.dim('— interactive terminal browser')}`);
24
+ console.log('');
25
+ console.log(` ${c.bold('Content:')}`);
26
+ console.log(` ${c.dim('•')} lore ingest ${c.dim('— add content (CLI, pipe, or file)')}`);
27
+ console.log(` ${c.dim('•')} lore sync ${c.dim('— sync all sources now')}`);
28
+ console.log(` ${c.dim('•')} lore sync add ${c.dim('— add a source directory')}`);
29
+ console.log(` ${c.dim('•')} lore sync list ${c.dim('— show configured sources')}`);
30
+ console.log(` ${c.dim('•')} lore sync enable <name> ${c.dim('— enable a source')}`);
31
+ console.log(` ${c.dim('•')} lore sync disable <name>${c.dim(' — disable')}`);
32
+ console.log(` ${c.dim('•')} lore sync remove <name> ${c.dim('— remove')}`);
33
+ console.log('');
34
+ console.log(` ${c.bold('Background Sync:')}`);
35
+ console.log(` ${c.dim('•')} lore sync start ${c.dim('— start daemon')}`);
36
+ console.log(` ${c.dim('•')} lore sync stop ${c.dim('— stop daemon')}`);
37
+ console.log(` ${c.dim('•')} lore sync restart ${c.dim('— restart daemon')}`);
38
+ console.log(` ${c.dim('•')} lore sync status ${c.dim('— daemon status')}`);
39
+ console.log(` ${c.dim('•')} lore sync logs ${c.dim('— view daemon logs')}`);
40
+ console.log(` ${c.dim('•')} lore sync watch ${c.dim('— watch in foreground')}`);
41
+ console.log('');
42
+ console.log(` ${c.bold('Documents & Projects:')}`);
43
+ console.log(` ${c.dim('•')} lore docs list ${c.dim('— list indexed documents')}`);
44
+ console.log(` ${c.dim('•')} lore docs get <id> ${c.dim('— view a document')}`);
45
+ console.log(` ${c.dim('•')} lore projects ${c.dim('— list projects')}`);
46
+ console.log(` ${c.dim('•')} lore projects archive ${c.dim('— archive a project')}`);
47
+ console.log('');
48
+ console.log(` ${c.bold('System:')}`);
49
+ console.log(` ${c.dim('•')} lore update ${c.dim('— check for and install updates')}`);
50
+ console.log(` ${c.dim('•')} lore skills install ${c.dim('— install agent integrations')}`);
51
+ console.log(` ${c.dim('•')} lore skills list ${c.dim('— available integrations')}`);
52
+ console.log(` ${c.dim('•')} lore mcp ${c.dim('— start MCP server')}`);
53
+ console.log(` ${c.dim('•')} lore init ${c.dim('— initialize data repository')}`);
54
+ console.log('');
55
+ console.log(c.dim(` Run 'lore <command> --help' for options and flags.`));
56
+ console.log('');
57
+ }
package/dist/core/auth.js CHANGED
@@ -80,7 +80,7 @@ export async function verifyOTP(email, token) {
80
80
  }
81
81
  }
82
82
  // All types failed — return the last error
83
- return { error: 'Token has expired or is invalid. Request a new code with \'lore login\'.' };
83
+ return { error: 'Token has expired or is invalid. Request a new code with \'lore auth login\'.' };
84
84
  }
85
85
  /**
86
86
  * Extract session from a magic link URL (Supabase sends these for new signups).
@@ -45,7 +45,7 @@ export async function getSupabase() {
45
45
  }
46
46
  }
47
47
  // Mode 3: No auth
48
- throw new Error('Not authenticated. Run \'lore login\' to sign in, or set SUPABASE_SERVICE_KEY for service mode.');
48
+ throw new Error('Not authenticated. Run \'lore auth login\' to sign in, or set SUPABASE_SERVICE_KEY for service mode.');
49
49
  }
50
50
  // ============================================================================
51
51
  // Index Management (compatibility layer - not needed for Supabase)
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import { registerIngestCommand } from './cli/commands/ingest.js';
34
34
  import { getExtensionRegistry, getLoreVersionString } from './extensions/registry.js';
35
35
  import { bridgeConfigToEnv } from './core/config.js';
36
36
  import { expandPath } from './sync/config.js';
37
+ import { showWelcomeScreen } from './cli/welcome.js';
37
38
  // Load .env files silently (without the v17 logging)
38
39
  function loadEnvFile(filePath, override = false) {
39
40
  if (!existsSync(filePath))
@@ -95,6 +96,10 @@ try {
95
96
  catch {
96
97
  // Extensions not loaded — fine for initial release
97
98
  }
99
+ // Default action: show welcome screen when no command is given
100
+ program.action(() => {
101
+ showWelcomeScreen();
102
+ });
98
103
  // Global error handler — show friendly messages instead of stack traces
99
104
  process.on('uncaughtException', (error) => {
100
105
  console.error(`\nError: ${error.message}`);
@@ -123,9 +123,10 @@ function mapContentType(sourceType) {
123
123
  }
124
124
  }
125
125
  export async function handleIngest(dbPath, dataDir, args, options = {}) {
126
- const { content, project, source_type: raw_source_type, date, participants = [], tags = [], source_url, source_name, } = args;
126
+ const { content, project: rawProject, source_type: raw_source_type, date, participants = [], tags = [], source_url, source_name, } = args;
127
127
  const { autoPush = true, hookContext } = options;
128
128
  const source_type = normalizeSourceType(raw_source_type);
129
+ const project = rawProject.toLowerCase().trim();
129
130
  // Auto-generate title if not provided
130
131
  const title = args.title || `${source_type.charAt(0).toUpperCase() + source_type.slice(1)}: ${content.slice(0, 50)}${content.length > 50 ? '...' : ''}`;
131
132
  // Content hash deduplication — skip everything if already ingested
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { getAllSources } from '../../core/vector-store.js';
5
5
  export async function handleListSources(dbPath, args) {
6
- const { project, source_type, limit = 20 } = args;
6
+ const { project: rawProject, source_type, limit = 20 } = args;
7
+ const project = rawProject?.toLowerCase().trim();
7
8
  const sources = await getAllSources(dbPath, {
8
9
  project,
9
10
  source_type,
@@ -240,7 +240,8 @@ Respond with only the JSON object.`;
240
240
  }
241
241
  }
242
242
  export async function handleResearch(dbPath, dataDir, args, options = {}) {
243
- const { task, project, include_sources = true } = args;
243
+ const { task, project: rawProject, include_sources = true } = args;
244
+ const project = rawProject?.toLowerCase().trim();
244
245
  const { onProgress } = options;
245
246
  // Check if we should use agentic mode (default) or simple mode (fallback)
246
247
  const useAgenticMode = process.env.LORE_RESEARCH_MODE !== 'simple';
@@ -15,7 +15,8 @@ import { generateEmbedding } from '../../core/embedder.js';
15
15
  import { searchLocalFiles, getMatchSnippet } from '../../core/local-search.js';
16
16
  import { loadArchivedProjects } from './archive-project.js';
17
17
  export async function handleSearch(dbPath, dataDir, args) {
18
- const { query, project, source_type, content_type, limit = 10, include_archived = false, mode = 'hybrid', } = args;
18
+ const { query, project: rawProject, source_type, content_type, limit = 10, include_archived = false, mode = 'hybrid', } = args;
19
+ const project = rawProject?.toLowerCase().trim();
19
20
  // Handle regex mode separately - uses local file search
20
21
  if (mode === 'regex') {
21
22
  return handleRegexSearch(dbPath, dataDir, {
@@ -135,7 +135,7 @@ async function main() {
135
135
  }
136
136
  const server = new Server({
137
137
  name: 'lore',
138
- version: '0.8.0',
138
+ version: '0.8.1',
139
139
  }, {
140
140
  capabilities: {
141
141
  tools: {},
package/dist/mcp/tools.js CHANGED
@@ -271,7 +271,7 @@ BEST PRACTICES:
271
271
  Phase 1 (Discovery — free, no LLM calls): Scans configured directories, computes content hashes, identifies new files.
272
272
  Phase 2 (Processing — only new files): Extracts metadata via LLM, generates embeddings, stores in Supabase.
273
273
 
274
- Use this when source directories have been updated externally, or to refresh the index after manual file changes. Source directories are configured via 'lore sources add' CLI command.
274
+ Use this when source directories have been updated externally, or to refresh the index after manual file changes. Source directories are configured via 'lore sync add' CLI command.
275
275
 
276
276
  Note: For pushing content from agents, use 'ingest' instead — it's the direct path.`,
277
277
  inputSchema: zodToJsonSchema(SyncSchema),
@@ -35,7 +35,7 @@ function getDefaultConfig() {
35
35
  {
36
36
  name: 'Example Source',
37
37
  path: '~/Documents/notes',
38
- glob: '**/*.md',
38
+ glob: '**/*',
39
39
  project: 'notes',
40
40
  enabled: false,
41
41
  },
@@ -103,6 +103,8 @@ export async function initializeSyncConfig() {
103
103
  // ============================================================================
104
104
  export async function addSyncSource(source) {
105
105
  const config = await loadSyncConfig();
106
+ // Normalize project name
107
+ source.project = source.project.toLowerCase().trim();
106
108
  // Check for duplicate names
107
109
  const existingIndex = config.sources.findIndex(s => s.name === source.name);
108
110
  if (existingIndex !== -1) {
@@ -963,6 +963,23 @@ function showProjectDeleteConfirm(state, ui, header) {
963
963
  ui.deleteConfirm.show();
964
964
  ui.screen.render();
965
965
  }
966
+ /**
967
+ * Start an animated spinner in the status bar.
968
+ * Returns a stop function that clears the interval.
969
+ */
970
+ function startSpinner(ui, message) {
971
+ const frames = ['\u280b', '\u2819', '\u2838', '\u2834', '\u2826', '\u2807']; // braille dots spinner
972
+ let i = 0;
973
+ const interval = setInterval(() => {
974
+ ui.statusBar.setContent(` {red-fg}{bold}${frames[i % frames.length]}{/bold}{/red-fg} {black-fg}{bold}${message}{/bold}{/black-fg}`);
975
+ ui.screen.render();
976
+ i++;
977
+ }, 100);
978
+ // Show first frame immediately
979
+ ui.statusBar.setContent(` {red-fg}{bold}${frames[0]}{/bold}{/red-fg} {black-fg}{bold}${message}{/bold}{/black-fg}`);
980
+ ui.screen.render();
981
+ return () => clearInterval(interval);
982
+ }
966
983
  /**
967
984
  * Cancel delete operation
968
985
  */
@@ -996,8 +1013,7 @@ export async function confirmDelete(state, ui, dbPath, dataDir, project, sourceT
996
1013
  // Hide dialog and show progress
997
1014
  ui.deleteConfirm.hide();
998
1015
  state.mode = 'list';
999
- ui.statusBar.setContent(` {yellow-fg}Deleting "${source.title}"...{/yellow-fg}`);
1000
- ui.screen.render();
1016
+ const stopSpinner = startSpinner(ui, `Deleting "${source.title}"...`);
1001
1017
  try {
1002
1018
  // 1. Delete from Supabase (this also handles chunks cascade)
1003
1019
  const { sourcePath: originalPath } = await deleteSource(dbPath, source.id);
@@ -1037,11 +1053,12 @@ export async function confirmDelete(state, ui, dbPath, dataDir, project, sourceT
1037
1053
  state.selectedIndex = Math.max(0, state.filtered.length - 1);
1038
1054
  }
1039
1055
  }
1056
+ stopSpinner();
1040
1057
  // Update UI
1041
1058
  updateStatus(ui, state, state.currentProject, sourceType);
1042
1059
  renderList(ui, state);
1043
1060
  renderPreview(ui, state);
1044
- ui.statusBar.setContent(` {green-fg}Deleted successfully{/green-fg}`);
1061
+ ui.statusBar.setContent(` {black-fg}{bold}Deleted successfully{/bold}{/black-fg}`);
1045
1062
  ui.screen.render();
1046
1063
  // Restore normal status after delay
1047
1064
  setTimeout(() => {
@@ -1050,6 +1067,7 @@ export async function confirmDelete(state, ui, dbPath, dataDir, project, sourceT
1050
1067
  }, 2000);
1051
1068
  }
1052
1069
  catch (error) {
1070
+ stopSpinner();
1053
1071
  ui.statusBar.setContent(` {red-fg}Delete failed: ${error}{/red-fg}`);
1054
1072
  ui.screen.render();
1055
1073
  }
@@ -1058,11 +1076,10 @@ export async function confirmDelete(state, ui, dbPath, dataDir, project, sourceT
1058
1076
  * Confirm and execute project deletion (all documents in a project)
1059
1077
  */
1060
1078
  async function confirmProjectDelete(state, ui, dbPath, dataDir, header, project, sourceType) {
1061
- // Hide dialog and show progress
1079
+ // Hide dialog and show spinner
1062
1080
  ui.deleteConfirm.hide();
1063
1081
  state.mode = 'list';
1064
- ui.statusBar.setContent(` {yellow-fg}Deleting ${header.documentCount} documents from "${header.displayName}"...{/yellow-fg}`);
1065
- ui.screen.render();
1082
+ const stopSpinner = startSpinner(ui, `Deleting ${header.documentCount} documents from "${header.displayName}"...`);
1066
1083
  try {
1067
1084
  // Get all documents in this project
1068
1085
  const docsToDelete = state.filtered.filter(s => {
@@ -1122,15 +1139,16 @@ async function confirmProjectDelete(state, ui, dbPath, dataDir, header, project,
1122
1139
  state.selectedIndex = Math.max(0, state.filtered.length - 1);
1123
1140
  }
1124
1141
  }
1142
+ stopSpinner();
1125
1143
  // Update UI
1126
1144
  updateStatus(ui, state, state.currentProject, sourceType);
1127
1145
  renderList(ui, state);
1128
1146
  renderPreview(ui, state);
1129
1147
  if (errors.length > 0) {
1130
- ui.statusBar.setContent(` {yellow-fg}Deleted ${deleted} documents, ${errors.length} failed{/yellow-fg}`);
1148
+ ui.statusBar.setContent(` {black-fg}{bold}Deleted ${deleted} documents, ${errors.length} failed{/bold}{/black-fg}`);
1131
1149
  }
1132
1150
  else {
1133
- ui.statusBar.setContent(` {green-fg}Deleted ${deleted} documents{/green-fg}`);
1151
+ ui.statusBar.setContent(` {black-fg}{bold}Deleted ${deleted} documents{/bold}{/black-fg}`);
1134
1152
  }
1135
1153
  ui.screen.render();
1136
1154
  // Restore normal status after delay
@@ -1140,6 +1158,7 @@ async function confirmProjectDelete(state, ui, dbPath, dataDir, header, project,
1140
1158
  }, 2000);
1141
1159
  }
1142
1160
  catch (error) {
1161
+ stopSpinner();
1143
1162
  ui.statusBar.setContent(` {red-fg}Delete failed: ${error}{/red-fg}`);
1144
1163
  ui.screen.render();
1145
1164
  }
@@ -54,6 +54,7 @@ export function createUIComponents() {
54
54
  type: 'line',
55
55
  },
56
56
  style: {
57
+ bg: 'black',
57
58
  border: {
58
59
  fg: 'blue',
59
60
  },
@@ -103,6 +104,7 @@ export function createUIComponents() {
103
104
  type: 'line',
104
105
  },
105
106
  style: {
107
+ bg: 'black',
106
108
  border: {
107
109
  fg: 'blue',
108
110
  },
@@ -148,6 +150,7 @@ export function createUIComponents() {
148
150
  type: 'line',
149
151
  },
150
152
  style: {
153
+ bg: 'black',
151
154
  border: {
152
155
  fg: 'blue',
153
156
  },
@@ -258,7 +261,7 @@ export function createUIComponents() {
258
261
  top: 'center',
259
262
  left: 'center',
260
263
  width: 60,
261
- height: 9,
264
+ height: 11,
262
265
  border: {
263
266
  type: 'line',
264
267
  },
@@ -351,7 +354,7 @@ export function createUIComponents() {
351
354
  height: 3,
352
355
  label: ' Ask Lore ',
353
356
  border: { type: 'line' },
354
- style: { border: { fg: 'cyan' }, focus: { border: { fg: 'green' } } },
357
+ style: { bg: 'black', fg: 'white', border: { fg: 'cyan' }, focus: { border: { fg: 'green' } } },
355
358
  hidden: true,
356
359
  inputOnFocus: true,
357
360
  });
@@ -364,7 +367,7 @@ export function createUIComponents() {
364
367
  height: '100%-7',
365
368
  label: ' Response ',
366
369
  border: { type: 'line' },
367
- style: { fg: 'white', border: { fg: 'cyan' } },
370
+ style: { fg: 'white', bg: 'black', border: { fg: 'cyan' } },
368
371
  hidden: true,
369
372
  tags: true,
370
373
  scrollable: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getlore/cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Research knowledge repository with semantic search, citations, and project lineage tracking",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",