@getlore/cli 0.2.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 (148) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +80 -0
  3. package/dist/cli/colors.d.ts +48 -0
  4. package/dist/cli/colors.js +48 -0
  5. package/dist/cli/commands/ask.d.ts +7 -0
  6. package/dist/cli/commands/ask.js +97 -0
  7. package/dist/cli/commands/auth.d.ts +10 -0
  8. package/dist/cli/commands/auth.js +484 -0
  9. package/dist/cli/commands/daemon.d.ts +22 -0
  10. package/dist/cli/commands/daemon.js +244 -0
  11. package/dist/cli/commands/docs.d.ts +7 -0
  12. package/dist/cli/commands/docs.js +188 -0
  13. package/dist/cli/commands/extensions.d.ts +7 -0
  14. package/dist/cli/commands/extensions.js +204 -0
  15. package/dist/cli/commands/misc.d.ts +7 -0
  16. package/dist/cli/commands/misc.js +172 -0
  17. package/dist/cli/commands/pending.d.ts +7 -0
  18. package/dist/cli/commands/pending.js +63 -0
  19. package/dist/cli/commands/projects.d.ts +7 -0
  20. package/dist/cli/commands/projects.js +136 -0
  21. package/dist/cli/commands/search.d.ts +7 -0
  22. package/dist/cli/commands/search.js +102 -0
  23. package/dist/cli/commands/skills.d.ts +24 -0
  24. package/dist/cli/commands/skills.js +447 -0
  25. package/dist/cli/commands/sources.d.ts +7 -0
  26. package/dist/cli/commands/sources.js +121 -0
  27. package/dist/cli/commands/sync.d.ts +31 -0
  28. package/dist/cli/commands/sync.js +768 -0
  29. package/dist/cli/helpers.d.ts +30 -0
  30. package/dist/cli/helpers.js +119 -0
  31. package/dist/core/auth.d.ts +62 -0
  32. package/dist/core/auth.js +330 -0
  33. package/dist/core/config.d.ts +41 -0
  34. package/dist/core/config.js +96 -0
  35. package/dist/core/data-repo.d.ts +31 -0
  36. package/dist/core/data-repo.js +146 -0
  37. package/dist/core/embedder.d.ts +22 -0
  38. package/dist/core/embedder.js +104 -0
  39. package/dist/core/git.d.ts +37 -0
  40. package/dist/core/git.js +140 -0
  41. package/dist/core/index.d.ts +4 -0
  42. package/dist/core/index.js +5 -0
  43. package/dist/core/insight-extractor.d.ts +26 -0
  44. package/dist/core/insight-extractor.js +114 -0
  45. package/dist/core/local-search.d.ts +43 -0
  46. package/dist/core/local-search.js +221 -0
  47. package/dist/core/themes.d.ts +15 -0
  48. package/dist/core/themes.js +77 -0
  49. package/dist/core/types.d.ts +177 -0
  50. package/dist/core/types.js +9 -0
  51. package/dist/core/user-settings.d.ts +15 -0
  52. package/dist/core/user-settings.js +42 -0
  53. package/dist/core/vector-store-lance.d.ts +98 -0
  54. package/dist/core/vector-store-lance.js +384 -0
  55. package/dist/core/vector-store-supabase.d.ts +89 -0
  56. package/dist/core/vector-store-supabase.js +295 -0
  57. package/dist/core/vector-store.d.ts +131 -0
  58. package/dist/core/vector-store.js +503 -0
  59. package/dist/daemon-runner.d.ts +8 -0
  60. package/dist/daemon-runner.js +246 -0
  61. package/dist/extensions/config.d.ts +22 -0
  62. package/dist/extensions/config.js +102 -0
  63. package/dist/extensions/proposals.d.ts +30 -0
  64. package/dist/extensions/proposals.js +178 -0
  65. package/dist/extensions/registry.d.ts +35 -0
  66. package/dist/extensions/registry.js +309 -0
  67. package/dist/extensions/sandbox.d.ts +16 -0
  68. package/dist/extensions/sandbox.js +17 -0
  69. package/dist/extensions/types.d.ts +114 -0
  70. package/dist/extensions/types.js +4 -0
  71. package/dist/extensions/worker.d.ts +1 -0
  72. package/dist/extensions/worker.js +49 -0
  73. package/dist/index.d.ts +17 -0
  74. package/dist/index.js +105 -0
  75. package/dist/mcp/handlers/archive-project.d.ts +51 -0
  76. package/dist/mcp/handlers/archive-project.js +112 -0
  77. package/dist/mcp/handlers/get-quotes.d.ts +27 -0
  78. package/dist/mcp/handlers/get-quotes.js +61 -0
  79. package/dist/mcp/handlers/get-source.d.ts +9 -0
  80. package/dist/mcp/handlers/get-source.js +40 -0
  81. package/dist/mcp/handlers/ingest.d.ts +25 -0
  82. package/dist/mcp/handlers/ingest.js +305 -0
  83. package/dist/mcp/handlers/list-projects.d.ts +4 -0
  84. package/dist/mcp/handlers/list-projects.js +16 -0
  85. package/dist/mcp/handlers/list-sources.d.ts +11 -0
  86. package/dist/mcp/handlers/list-sources.js +20 -0
  87. package/dist/mcp/handlers/research-agent.d.ts +21 -0
  88. package/dist/mcp/handlers/research-agent.js +369 -0
  89. package/dist/mcp/handlers/research.d.ts +22 -0
  90. package/dist/mcp/handlers/research.js +225 -0
  91. package/dist/mcp/handlers/retain.d.ts +18 -0
  92. package/dist/mcp/handlers/retain.js +92 -0
  93. package/dist/mcp/handlers/search.d.ts +52 -0
  94. package/dist/mcp/handlers/search.js +145 -0
  95. package/dist/mcp/handlers/sync.d.ts +47 -0
  96. package/dist/mcp/handlers/sync.js +211 -0
  97. package/dist/mcp/server.d.ts +10 -0
  98. package/dist/mcp/server.js +268 -0
  99. package/dist/mcp/tools.d.ts +16 -0
  100. package/dist/mcp/tools.js +297 -0
  101. package/dist/sync/config.d.ts +26 -0
  102. package/dist/sync/config.js +140 -0
  103. package/dist/sync/discover.d.ts +51 -0
  104. package/dist/sync/discover.js +190 -0
  105. package/dist/sync/index.d.ts +11 -0
  106. package/dist/sync/index.js +11 -0
  107. package/dist/sync/process.d.ts +50 -0
  108. package/dist/sync/process.js +285 -0
  109. package/dist/sync/processors.d.ts +24 -0
  110. package/dist/sync/processors.js +351 -0
  111. package/dist/tui/browse-handlers-ask.d.ts +30 -0
  112. package/dist/tui/browse-handlers-ask.js +372 -0
  113. package/dist/tui/browse-handlers-autocomplete.d.ts +49 -0
  114. package/dist/tui/browse-handlers-autocomplete.js +270 -0
  115. package/dist/tui/browse-handlers-extensions.d.ts +18 -0
  116. package/dist/tui/browse-handlers-extensions.js +107 -0
  117. package/dist/tui/browse-handlers-pending.d.ts +22 -0
  118. package/dist/tui/browse-handlers-pending.js +100 -0
  119. package/dist/tui/browse-handlers-research.d.ts +32 -0
  120. package/dist/tui/browse-handlers-research.js +363 -0
  121. package/dist/tui/browse-handlers-tools.d.ts +42 -0
  122. package/dist/tui/browse-handlers-tools.js +289 -0
  123. package/dist/tui/browse-handlers.d.ts +239 -0
  124. package/dist/tui/browse-handlers.js +1944 -0
  125. package/dist/tui/browse-render-extensions.d.ts +14 -0
  126. package/dist/tui/browse-render-extensions.js +114 -0
  127. package/dist/tui/browse-render-tools.d.ts +18 -0
  128. package/dist/tui/browse-render-tools.js +259 -0
  129. package/dist/tui/browse-render.d.ts +51 -0
  130. package/dist/tui/browse-render.js +599 -0
  131. package/dist/tui/browse-types.d.ts +142 -0
  132. package/dist/tui/browse-types.js +70 -0
  133. package/dist/tui/browse-ui.d.ts +10 -0
  134. package/dist/tui/browse-ui.js +432 -0
  135. package/dist/tui/browse.d.ts +17 -0
  136. package/dist/tui/browse.js +625 -0
  137. package/dist/tui/markdown.d.ts +22 -0
  138. package/dist/tui/markdown.js +223 -0
  139. package/package.json +71 -0
  140. package/plugins/claude-code/.claude-plugin/plugin.json +10 -0
  141. package/plugins/claude-code/.mcp.json +6 -0
  142. package/plugins/claude-code/skills/lore/SKILL.md +63 -0
  143. package/plugins/codex/SKILL.md +36 -0
  144. package/plugins/codex/agents/openai.yaml +10 -0
  145. package/plugins/gemini/GEMINI.md +31 -0
  146. package/plugins/gemini/gemini-extension.json +11 -0
  147. package/skills/generic-agent.md +99 -0
  148. package/skills/openclaw.md +67 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Lore - Data Repository Helpers
3
+ *
4
+ * Shared logic for initializing and managing the lore data directory.
5
+ * Used by both `lore setup` and `lore init`.
6
+ */
7
+ /**
8
+ * Initialize a lore data repository at the given path.
9
+ * Creates directory structure, .gitignore, README, and git init.
10
+ * Idempotent — safe to call on an existing directory.
11
+ */
12
+ export declare function initDataRepo(dirPath: string): Promise<void>;
13
+ export declare const WELCOME_DOC_CONTENT = "# Getting Started with Lore\n\nWelcome to Lore \u2014 your research knowledge repository.\n\n## What is Lore?\n\nLore preserves your original sources (meeting notes, interviews, documents) and makes them searchable with full citations. Unlike a memory system, Lore keeps the original context so you can always trace back to the source.\n\n## Quick Start\n\n- **Search**: Run `lore search \"your query\"` to find relevant documents\n- **Sync**: Run `lore sync` to discover and index new files from your sync sources\n- **Browse**: Run `lore browse` to explore your knowledge base in the terminal\n- **Research**: Use the `research` MCP tool for deep, multi-step research with citations\n\n## Adding Documents\n\n1. **Sync sources**: Run `lore sync add` to watch a directory for new files\n2. **Direct ingest**: Use the `ingest` MCP tool to add documents from any agent\n3. **Manual sync**: Run `lore sync` after adding files to your data directory\n\n## Background Daemon\n\nRun `lore sync start` to launch a background daemon that watches for new files and auto-indexes them. Check status with `lore sync status`.\n\n## Tips\n\n- Lore extracts metadata using AI at sync time, so your documents are enriched automatically\n- Use projects to organize related documents together\n- The research tool can cross-reference multiple sources and synthesize findings with citations\n";
14
+ /**
15
+ * Check if GitHub CLI is available and authenticated.
16
+ */
17
+ export declare function isGhAvailable(): Promise<boolean>;
18
+ /**
19
+ * Create a private GitHub repo from the given local directory.
20
+ * Returns the repo URL on success, null on failure.
21
+ */
22
+ export declare function createGithubRepo(dirPath: string, name: string): Promise<string | null>;
23
+ /**
24
+ * Get the git remote origin URL for a directory.
25
+ * Returns null if no remote is configured.
26
+ */
27
+ export declare function getGitRemoteUrl(dirPath: string): string | null;
28
+ /**
29
+ * Check if a directory is a git repository.
30
+ */
31
+ export declare function isGitRepo(dirPath: string): boolean;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Lore - Data Repository Helpers
3
+ *
4
+ * Shared logic for initializing and managing the lore data directory.
5
+ * Used by both `lore setup` and `lore init`.
6
+ */
7
+ import { mkdir, writeFile } from 'fs/promises';
8
+ import { existsSync } from 'fs';
9
+ import { execSync } from 'child_process';
10
+ import path from 'path';
11
+ // ============================================================================
12
+ // Data Repo Init
13
+ // ============================================================================
14
+ /**
15
+ * Initialize a lore data repository at the given path.
16
+ * Creates directory structure, .gitignore, README, and git init.
17
+ * Idempotent — safe to call on an existing directory.
18
+ */
19
+ export async function initDataRepo(dirPath) {
20
+ await mkdir(dirPath, { recursive: true });
21
+ await mkdir(path.join(dirPath, 'sources'), { recursive: true });
22
+ await mkdir(path.join(dirPath, 'retained'), { recursive: true });
23
+ // Create .gitignore if missing
24
+ const gitignorePath = path.join(dirPath, '.gitignore');
25
+ if (!existsSync(gitignorePath)) {
26
+ await writeFile(gitignorePath, `.env\n.env.local\n`);
27
+ }
28
+ // Create README if missing
29
+ const readmePath = path.join(dirPath, 'README.md');
30
+ if (!existsSync(readmePath)) {
31
+ await writeFile(readmePath, `# Lore Data Repository
32
+
33
+ Your personal knowledge repository for Lore.
34
+
35
+ ## Structure
36
+
37
+ - \`sources/\` - Ingested documents
38
+ - \`retained/\` - Explicitly saved insights
39
+
40
+ Vector embeddings are stored in Supabase (cloud) for multi-machine access.
41
+ `);
42
+ }
43
+ // Git init if not already a repo
44
+ if (!existsSync(path.join(dirPath, '.git'))) {
45
+ try {
46
+ execSync('git init', { cwd: dirPath, stdio: 'pipe' });
47
+ execSync('git add .', { cwd: dirPath, stdio: 'pipe' });
48
+ execSync('git commit -m "Initial lore data repository"', {
49
+ cwd: dirPath,
50
+ stdio: 'pipe',
51
+ });
52
+ }
53
+ catch {
54
+ // git not installed or commit failed — non-fatal
55
+ }
56
+ }
57
+ }
58
+ // ============================================================================
59
+ // Welcome Document (seeded during setup)
60
+ // ============================================================================
61
+ export const WELCOME_DOC_CONTENT = `# Getting Started with Lore
62
+
63
+ Welcome to Lore — your research knowledge repository.
64
+
65
+ ## What is Lore?
66
+
67
+ Lore preserves your original sources (meeting notes, interviews, documents) and makes them searchable with full citations. Unlike a memory system, Lore keeps the original context so you can always trace back to the source.
68
+
69
+ ## Quick Start
70
+
71
+ - **Search**: Run \`lore search "your query"\` to find relevant documents
72
+ - **Sync**: Run \`lore sync\` to discover and index new files from your sync sources
73
+ - **Browse**: Run \`lore browse\` to explore your knowledge base in the terminal
74
+ - **Research**: Use the \`research\` MCP tool for deep, multi-step research with citations
75
+
76
+ ## Adding Documents
77
+
78
+ 1. **Sync sources**: Run \`lore sync add\` to watch a directory for new files
79
+ 2. **Direct ingest**: Use the \`ingest\` MCP tool to add documents from any agent
80
+ 3. **Manual sync**: Run \`lore sync\` after adding files to your data directory
81
+
82
+ ## Background Daemon
83
+
84
+ Run \`lore sync start\` to launch a background daemon that watches for new files and auto-indexes them. Check status with \`lore sync status\`.
85
+
86
+ ## Tips
87
+
88
+ - Lore extracts metadata using AI at sync time, so your documents are enriched automatically
89
+ - Use projects to organize related documents together
90
+ - The research tool can cross-reference multiple sources and synthesize findings with citations
91
+ `;
92
+ // ============================================================================
93
+ // GitHub CLI helpers
94
+ // ============================================================================
95
+ /**
96
+ * Check if GitHub CLI is available and authenticated.
97
+ */
98
+ export async function isGhAvailable() {
99
+ try {
100
+ execSync('which gh', { stdio: 'pipe' });
101
+ execSync('gh auth status', { stdio: 'pipe' });
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ /**
109
+ * Create a private GitHub repo from the given local directory.
110
+ * Returns the repo URL on success, null on failure.
111
+ */
112
+ export async function createGithubRepo(dirPath, name) {
113
+ try {
114
+ execSync(`gh repo create ${name} --private --source=. --push`, {
115
+ cwd: dirPath,
116
+ stdio: 'pipe',
117
+ });
118
+ return getGitRemoteUrl(dirPath);
119
+ }
120
+ catch {
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Get the git remote origin URL for a directory.
126
+ * Returns null if no remote is configured.
127
+ */
128
+ export function getGitRemoteUrl(dirPath) {
129
+ try {
130
+ return execSync('git remote get-url origin', {
131
+ cwd: dirPath,
132
+ stdio: 'pipe',
133
+ })
134
+ .toString()
135
+ .trim();
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }
141
+ /**
142
+ * Check if a directory is a git repository.
143
+ */
144
+ export function isGitRepo(dirPath) {
145
+ return existsSync(path.join(dirPath, '.git'));
146
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Lore - Embedding Generation
3
+ *
4
+ * Handles generating embeddings for semantic search using OpenAI's API.
5
+ * Adapted from granola-extractor with retry logic and batching.
6
+ */
7
+ export declare function getEmbeddingDimensions(): number;
8
+ export declare function generateEmbedding(text: string, model?: string): Promise<number[]>;
9
+ export declare function generateEmbeddings(texts: string[], model?: string, options?: {
10
+ batchSize?: number;
11
+ onProgress?: (completed: number, total: number) => void;
12
+ }): Promise<number[][]>;
13
+ /**
14
+ * Create searchable text with context prefix for better embedding quality
15
+ */
16
+ export declare function createSearchableText(content: {
17
+ type: 'summary' | 'theme' | 'quote' | 'decision' | 'requirement' | 'note';
18
+ text: string;
19
+ theme_name?: string;
20
+ context?: string;
21
+ project?: string;
22
+ }): string;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Lore - Embedding Generation
3
+ *
4
+ * Handles generating embeddings for semantic search using OpenAI's API.
5
+ * Adapted from granola-extractor with retry logic and batching.
6
+ */
7
+ import OpenAI from 'openai';
8
+ let openaiClient = null;
9
+ function getOpenAI() {
10
+ if (!openaiClient) {
11
+ const apiKey = process.env.OPENAI_API_KEY;
12
+ if (!apiKey) {
13
+ throw new Error('OPENAI_API_KEY is not configured. Run "lore setup" to set your API keys.');
14
+ }
15
+ openaiClient = new OpenAI({ apiKey });
16
+ }
17
+ return openaiClient;
18
+ }
19
+ const DEFAULT_MODEL = 'text-embedding-3-small';
20
+ const EMBEDDING_DIMENSIONS = 1536;
21
+ export function getEmbeddingDimensions() {
22
+ return EMBEDDING_DIMENSIONS;
23
+ }
24
+ export async function generateEmbedding(text, model = DEFAULT_MODEL) {
25
+ const openai = getOpenAI();
26
+ // Truncate very long texts (embedding model has token limits)
27
+ const truncatedText = text.substring(0, 8000);
28
+ // Retry logic for transient errors
29
+ const maxRetries = 3;
30
+ let lastError = null;
31
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
32
+ try {
33
+ const response = await openai.embeddings.create({
34
+ model,
35
+ input: truncatedText,
36
+ });
37
+ return response.data[0].embedding;
38
+ }
39
+ catch (error) {
40
+ lastError = error;
41
+ const isRetryable = lastError.message?.includes('Connection') ||
42
+ lastError.message?.includes('timeout') ||
43
+ lastError.message?.includes('ECONNRESET') ||
44
+ lastError.message?.includes('rate limit');
45
+ if (isRetryable && attempt < maxRetries) {
46
+ const delay = Math.pow(2, attempt) * 1000;
47
+ await new Promise((resolve) => setTimeout(resolve, delay));
48
+ continue;
49
+ }
50
+ throw error;
51
+ }
52
+ }
53
+ throw lastError;
54
+ }
55
+ export async function generateEmbeddings(texts, model = DEFAULT_MODEL, options = {}) {
56
+ const openai = getOpenAI();
57
+ const { batchSize = 100, onProgress } = options;
58
+ const results = [];
59
+ // Process in batches
60
+ for (let i = 0; i < texts.length; i += batchSize) {
61
+ const batch = texts.slice(i, i + batchSize);
62
+ // Truncate each text
63
+ const truncatedBatch = batch.map((t) => t.substring(0, 8000));
64
+ const response = await openai.embeddings.create({
65
+ model,
66
+ input: truncatedBatch,
67
+ });
68
+ // Sort by index to maintain order
69
+ const sortedData = response.data.sort((a, b) => a.index - b.index);
70
+ for (const item of sortedData) {
71
+ results.push(item.embedding);
72
+ }
73
+ onProgress?.(Math.min(i + batchSize, texts.length), texts.length);
74
+ // Small delay between batches to avoid rate limits
75
+ if (i + batchSize < texts.length) {
76
+ await new Promise((resolve) => setTimeout(resolve, 100));
77
+ }
78
+ }
79
+ return results;
80
+ }
81
+ /**
82
+ * Create searchable text with context prefix for better embedding quality
83
+ */
84
+ export function createSearchableText(content) {
85
+ const projectPrefix = content.project ? `[Project: ${content.project}] ` : '';
86
+ switch (content.type) {
87
+ case 'summary':
88
+ return `${projectPrefix}Summary: ${content.text}`;
89
+ case 'theme':
90
+ return `${projectPrefix}Theme ${content.theme_name}: ${content.text}`;
91
+ case 'quote':
92
+ return content.context
93
+ ? `${projectPrefix}Quote about ${content.context}: "${content.text}"`
94
+ : `${projectPrefix}Quote: "${content.text}"`;
95
+ case 'decision':
96
+ return `${projectPrefix}Decision: ${content.text}`;
97
+ case 'requirement':
98
+ return `${projectPrefix}Requirement: ${content.text}`;
99
+ case 'note':
100
+ return `${projectPrefix}Note: ${content.text}`;
101
+ default:
102
+ return content.text;
103
+ }
104
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Git utilities for Lore data synchronization
3
+ */
4
+ export interface GitResult {
5
+ success: boolean;
6
+ message?: string;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Check if a directory is a git repository
11
+ */
12
+ export declare function isGitRepo(dir: string): Promise<boolean>;
13
+ /**
14
+ * Check if the repo has a remote configured
15
+ */
16
+ export declare function hasRemote(dir: string): Promise<boolean>;
17
+ /**
18
+ * Check if there are uncommitted changes
19
+ */
20
+ export declare function hasChanges(dir: string): Promise<boolean>;
21
+ /**
22
+ * Git pull with rebase
23
+ */
24
+ export declare function gitPull(dir: string): Promise<GitResult>;
25
+ /**
26
+ * Git add, commit, and push
27
+ */
28
+ export declare function gitCommitAndPush(dir: string, message: string): Promise<GitResult>;
29
+ /**
30
+ * Delete a file and commit the removal to its git repo (if git-tracked).
31
+ * No-op if the file doesn't exist.
32
+ */
33
+ export declare function deleteFileAndCommit(filePath: string, commitMessage: string): Promise<void>;
34
+ /**
35
+ * Sync: pull, then push any local changes
36
+ */
37
+ export declare function gitSync(dir: string): Promise<GitResult>;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Git utilities for Lore data synchronization
3
+ */
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ const execAsync = promisify(exec);
7
+ /**
8
+ * Check if a directory is a git repository
9
+ */
10
+ export async function isGitRepo(dir) {
11
+ try {
12
+ await execAsync('git rev-parse --git-dir', { cwd: dir });
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ /**
20
+ * Check if the repo has a remote configured
21
+ */
22
+ export async function hasRemote(dir) {
23
+ try {
24
+ const { stdout } = await execAsync('git remote', { cwd: dir });
25
+ return stdout.trim().length > 0;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /**
32
+ * Check if there are uncommitted changes
33
+ */
34
+ export async function hasChanges(dir) {
35
+ try {
36
+ const { stdout } = await execAsync('git status --porcelain', { cwd: dir });
37
+ return stdout.trim().length > 0;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ /**
44
+ * Git pull with rebase
45
+ */
46
+ export async function gitPull(dir) {
47
+ try {
48
+ if (!(await isGitRepo(dir))) {
49
+ return { success: false, error: 'Not a git repository' };
50
+ }
51
+ if (!(await hasRemote(dir))) {
52
+ return { success: false, error: 'No remote configured' };
53
+ }
54
+ // Stash any local changes
55
+ await execAsync('git stash', { cwd: dir }).catch(() => { });
56
+ // Pull with rebase
57
+ const { stdout } = await execAsync('git pull --rebase', { cwd: dir });
58
+ const pulled = !stdout.includes('Already up to date');
59
+ return {
60
+ success: true,
61
+ message: pulled ? 'Pulled new changes' : 'Already up to date'
62
+ };
63
+ }
64
+ catch (error) {
65
+ return { success: false, error: String(error) };
66
+ }
67
+ }
68
+ /**
69
+ * Git add, commit, and push
70
+ */
71
+ export async function gitCommitAndPush(dir, message) {
72
+ try {
73
+ if (!(await isGitRepo(dir))) {
74
+ return { success: false, error: 'Not a git repository' };
75
+ }
76
+ // Check for changes
77
+ if (!(await hasChanges(dir))) {
78
+ return { success: true, message: 'No changes to commit' };
79
+ }
80
+ // Stage all changes
81
+ await execAsync('git add -A', { cwd: dir });
82
+ // Commit
83
+ await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: dir });
84
+ // Push if remote exists
85
+ if (await hasRemote(dir)) {
86
+ await execAsync('git push', { cwd: dir });
87
+ return { success: true, message: 'Committed and pushed' };
88
+ }
89
+ else {
90
+ return { success: true, message: 'Committed (no remote to push)' };
91
+ }
92
+ }
93
+ catch (error) {
94
+ return { success: false, error: String(error) };
95
+ }
96
+ }
97
+ /**
98
+ * Delete a file and commit the removal to its git repo (if git-tracked).
99
+ * No-op if the file doesn't exist.
100
+ */
101
+ export async function deleteFileAndCommit(filePath, commitMessage) {
102
+ const { existsSync } = await import('fs');
103
+ if (!existsSync(filePath))
104
+ return;
105
+ const { rm } = await import('fs/promises');
106
+ const dir = (await import('path')).dirname(filePath);
107
+ await rm(filePath);
108
+ if (await isGitRepo(dir)) {
109
+ await gitCommitAndPush(dir, commitMessage);
110
+ }
111
+ }
112
+ /**
113
+ * Sync: pull, then push any local changes
114
+ */
115
+ export async function gitSync(dir) {
116
+ try {
117
+ if (!(await isGitRepo(dir))) {
118
+ return { success: false, error: 'Not a git repository' };
119
+ }
120
+ if (!(await hasRemote(dir))) {
121
+ return { success: false, error: 'No remote configured' };
122
+ }
123
+ // Pull first
124
+ const pullResult = await gitPull(dir);
125
+ if (!pullResult.success) {
126
+ return pullResult;
127
+ }
128
+ // Push any local changes
129
+ if (await hasChanges(dir)) {
130
+ await execAsync('git add -A', { cwd: dir });
131
+ await execAsync('git commit -m "Auto-sync from Lore"', { cwd: dir });
132
+ await execAsync('git push', { cwd: dir });
133
+ return { success: true, message: 'Pulled and pushed changes' };
134
+ }
135
+ return { success: true, message: pullResult.message };
136
+ }
137
+ catch (error) {
138
+ return { success: false, error: String(error) };
139
+ }
140
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './embedder.js';
3
+ export * from './vector-store.js';
4
+ export * from './insight-extractor.js';
@@ -0,0 +1,5 @@
1
+ // Core exports
2
+ export * from './types.js';
3
+ export * from './embedder.js';
4
+ export * from './vector-store.js';
5
+ export * from './insight-extractor.js';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Lore - Insight Extraction
3
+ *
4
+ * Generates summaries for source content. Keeps it simple - the agent
5
+ * does the real analysis at query time with full context.
6
+ */
7
+ import type { Quote, Theme } from './types.js';
8
+ export interface ExtractedInsights {
9
+ summary: string;
10
+ themes: Theme[];
11
+ quotes: Quote[];
12
+ }
13
+ export declare function extractInsights(content: string, title: string, sourceId: string, options?: {
14
+ contentType?: string;
15
+ model?: string;
16
+ }): Promise<ExtractedInsights>;
17
+ export declare function extractInsightsBatch(documents: Array<{
18
+ id: string;
19
+ title: string;
20
+ content: string;
21
+ contentType?: string;
22
+ }>, options?: {
23
+ model?: string;
24
+ concurrency?: number;
25
+ onProgress?: (completed: number, total: number, title: string) => void;
26
+ }): Promise<Map<string, ExtractedInsights>>;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Lore - Insight Extraction
3
+ *
4
+ * Generates summaries for source content. Keeps it simple - the agent
5
+ * does the real analysis at query time with full context.
6
+ */
7
+ import OpenAI from 'openai';
8
+ let openaiClient = null;
9
+ function getOpenAI() {
10
+ if (!openaiClient) {
11
+ const apiKey = process.env.OPENAI_API_KEY;
12
+ if (!apiKey) {
13
+ throw new Error('OPENAI_API_KEY environment variable is required');
14
+ }
15
+ openaiClient = new OpenAI({ apiKey });
16
+ }
17
+ return openaiClient;
18
+ }
19
+ export async function extractInsights(content, title, sourceId, options = {}) {
20
+ const { contentType = 'document', model = 'gpt-4o-mini' } = options;
21
+ const openai = getOpenAI();
22
+ // Simple, content-type aware summary prompt
23
+ const contextHint = {
24
+ interview: 'This is a user interview or research call.',
25
+ meeting: 'This is a meeting transcript.',
26
+ conversation: 'This is an AI conversation (Claude, ChatGPT, etc.).',
27
+ analysis: 'This is a research or competitor analysis document.',
28
+ survey: 'This is survey results or user feedback data.',
29
+ document: 'This is a document or notes.',
30
+ note: 'This is a note or memo.',
31
+ }[contentType] || 'This is a document.';
32
+ const systemPrompt = `You generate concise, information-dense summaries.
33
+
34
+ ${contextHint}
35
+
36
+ Write a 2-4 sentence summary that captures:
37
+ - What this content is about
38
+ - The key takeaways or findings
39
+ - Any important decisions, insights, or action items
40
+
41
+ Be specific and factual. Include names, numbers, and concrete details when present.
42
+ Return only the summary text, no JSON or formatting.`;
43
+ const userPrompt = `Title: ${title}
44
+
45
+ ${content.substring(0, 30000)}`;
46
+ // Retry logic for transient errors
47
+ const maxRetries = 3;
48
+ let lastError = null;
49
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
50
+ try {
51
+ const response = await openai.chat.completions.create({
52
+ model,
53
+ messages: [
54
+ { role: 'system', content: systemPrompt },
55
+ { role: 'user', content: userPrompt },
56
+ ],
57
+ temperature: 0.3,
58
+ max_tokens: 500,
59
+ });
60
+ const summary = response.choices[0]?.message?.content?.trim();
61
+ if (!summary) {
62
+ throw new Error('No content in OpenAI response');
63
+ }
64
+ return {
65
+ summary,
66
+ themes: [], // Agent extracts at query time
67
+ quotes: [], // Agent extracts at query time
68
+ };
69
+ }
70
+ catch (error) {
71
+ lastError = error;
72
+ const isRetryable = lastError.message?.includes('Connection') ||
73
+ lastError.message?.includes('timeout') ||
74
+ lastError.message?.includes('ECONNRESET') ||
75
+ lastError.message?.includes('rate limit');
76
+ if (isRetryable && attempt < maxRetries) {
77
+ const delay = Math.pow(2, attempt) * 1000;
78
+ await new Promise((resolve) => setTimeout(resolve, delay));
79
+ continue;
80
+ }
81
+ break;
82
+ }
83
+ }
84
+ // All retries failed - use first 200 chars as fallback
85
+ console.error(`Error generating summary for "${title}":`, lastError);
86
+ return {
87
+ summary: content.substring(0, 200).trim() + '...',
88
+ themes: [],
89
+ quotes: [],
90
+ };
91
+ }
92
+ export async function extractInsightsBatch(documents, options = {}) {
93
+ const { model = 'gpt-4o-mini', concurrency = 3, onProgress } = options;
94
+ const results = new Map();
95
+ for (let i = 0; i < documents.length; i += concurrency) {
96
+ const batch = documents.slice(i, i + concurrency);
97
+ const batchResults = await Promise.all(batch.map(async (doc) => {
98
+ const insights = await extractInsights(doc.content, doc.title, doc.id, {
99
+ contentType: doc.contentType,
100
+ model,
101
+ });
102
+ onProgress?.(i + batch.indexOf(doc) + 1, documents.length, doc.title);
103
+ return { id: doc.id, insights };
104
+ }));
105
+ for (const { id, insights } of batchResults) {
106
+ results.set(id, insights);
107
+ }
108
+ // Delay between batches
109
+ if (i + concurrency < documents.length) {
110
+ await new Promise((resolve) => setTimeout(resolve, 500));
111
+ }
112
+ }
113
+ return results;
114
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Local Search - Regex/pattern search in local source files
3
+ *
4
+ * Provides grep-like functionality for searching source content
5
+ * stored in the data directory. Tries ripgrep first for speed,
6
+ * falls back to native JavaScript regex.
7
+ */
8
+ export interface LocalSearchMatch {
9
+ line_number: number;
10
+ line_content: string;
11
+ match_start: number;
12
+ match_end: number;
13
+ }
14
+ export interface LocalSearchResult {
15
+ source_id: string;
16
+ file_path: string;
17
+ matches: LocalSearchMatch[];
18
+ }
19
+ export interface LocalSearchOptions {
20
+ /** Maximum results per file */
21
+ maxMatchesPerFile?: number;
22
+ /** Maximum total results */
23
+ maxTotalResults?: number;
24
+ /** Case-insensitive search */
25
+ ignoreCase?: boolean;
26
+ /** Lines of context before match */
27
+ contextBefore?: number;
28
+ /** Lines of context after match */
29
+ contextAfter?: number;
30
+ }
31
+ /**
32
+ * Search local source files using regex pattern
33
+ *
34
+ * @param dataDir - The lore data directory
35
+ * @param pattern - Regex pattern to search for
36
+ * @param options - Search options
37
+ * @returns Array of search results with matches
38
+ */
39
+ export declare function searchLocalFiles(dataDir: string, pattern: string, options?: LocalSearchOptions): Promise<LocalSearchResult[]>;
40
+ /**
41
+ * Get a snippet of text around a match for display
42
+ */
43
+ export declare function getMatchSnippet(lineContent: string, matchStart: number, matchEnd: number, maxLength?: number): string;