@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.
- package/LICENSE +13 -0
- package/README.md +80 -0
- package/dist/cli/colors.d.ts +48 -0
- package/dist/cli/colors.js +48 -0
- package/dist/cli/commands/ask.d.ts +7 -0
- package/dist/cli/commands/ask.js +97 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.js +484 -0
- package/dist/cli/commands/daemon.d.ts +22 -0
- package/dist/cli/commands/daemon.js +244 -0
- package/dist/cli/commands/docs.d.ts +7 -0
- package/dist/cli/commands/docs.js +188 -0
- package/dist/cli/commands/extensions.d.ts +7 -0
- package/dist/cli/commands/extensions.js +204 -0
- package/dist/cli/commands/misc.d.ts +7 -0
- package/dist/cli/commands/misc.js +172 -0
- package/dist/cli/commands/pending.d.ts +7 -0
- package/dist/cli/commands/pending.js +63 -0
- package/dist/cli/commands/projects.d.ts +7 -0
- package/dist/cli/commands/projects.js +136 -0
- package/dist/cli/commands/search.d.ts +7 -0
- package/dist/cli/commands/search.js +102 -0
- package/dist/cli/commands/skills.d.ts +24 -0
- package/dist/cli/commands/skills.js +447 -0
- package/dist/cli/commands/sources.d.ts +7 -0
- package/dist/cli/commands/sources.js +121 -0
- package/dist/cli/commands/sync.d.ts +31 -0
- package/dist/cli/commands/sync.js +768 -0
- package/dist/cli/helpers.d.ts +30 -0
- package/dist/cli/helpers.js +119 -0
- package/dist/core/auth.d.ts +62 -0
- package/dist/core/auth.js +330 -0
- package/dist/core/config.d.ts +41 -0
- package/dist/core/config.js +96 -0
- package/dist/core/data-repo.d.ts +31 -0
- package/dist/core/data-repo.js +146 -0
- package/dist/core/embedder.d.ts +22 -0
- package/dist/core/embedder.js +104 -0
- package/dist/core/git.d.ts +37 -0
- package/dist/core/git.js +140 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +5 -0
- package/dist/core/insight-extractor.d.ts +26 -0
- package/dist/core/insight-extractor.js +114 -0
- package/dist/core/local-search.d.ts +43 -0
- package/dist/core/local-search.js +221 -0
- package/dist/core/themes.d.ts +15 -0
- package/dist/core/themes.js +77 -0
- package/dist/core/types.d.ts +177 -0
- package/dist/core/types.js +9 -0
- package/dist/core/user-settings.d.ts +15 -0
- package/dist/core/user-settings.js +42 -0
- package/dist/core/vector-store-lance.d.ts +98 -0
- package/dist/core/vector-store-lance.js +384 -0
- package/dist/core/vector-store-supabase.d.ts +89 -0
- package/dist/core/vector-store-supabase.js +295 -0
- package/dist/core/vector-store.d.ts +131 -0
- package/dist/core/vector-store.js +503 -0
- package/dist/daemon-runner.d.ts +8 -0
- package/dist/daemon-runner.js +246 -0
- package/dist/extensions/config.d.ts +22 -0
- package/dist/extensions/config.js +102 -0
- package/dist/extensions/proposals.d.ts +30 -0
- package/dist/extensions/proposals.js +178 -0
- package/dist/extensions/registry.d.ts +35 -0
- package/dist/extensions/registry.js +309 -0
- package/dist/extensions/sandbox.d.ts +16 -0
- package/dist/extensions/sandbox.js +17 -0
- package/dist/extensions/types.d.ts +114 -0
- package/dist/extensions/types.js +4 -0
- package/dist/extensions/worker.d.ts +1 -0
- package/dist/extensions/worker.js +49 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +105 -0
- package/dist/mcp/handlers/archive-project.d.ts +51 -0
- package/dist/mcp/handlers/archive-project.js +112 -0
- package/dist/mcp/handlers/get-quotes.d.ts +27 -0
- package/dist/mcp/handlers/get-quotes.js +61 -0
- package/dist/mcp/handlers/get-source.d.ts +9 -0
- package/dist/mcp/handlers/get-source.js +40 -0
- package/dist/mcp/handlers/ingest.d.ts +25 -0
- package/dist/mcp/handlers/ingest.js +305 -0
- package/dist/mcp/handlers/list-projects.d.ts +4 -0
- package/dist/mcp/handlers/list-projects.js +16 -0
- package/dist/mcp/handlers/list-sources.d.ts +11 -0
- package/dist/mcp/handlers/list-sources.js +20 -0
- package/dist/mcp/handlers/research-agent.d.ts +21 -0
- package/dist/mcp/handlers/research-agent.js +369 -0
- package/dist/mcp/handlers/research.d.ts +22 -0
- package/dist/mcp/handlers/research.js +225 -0
- package/dist/mcp/handlers/retain.d.ts +18 -0
- package/dist/mcp/handlers/retain.js +92 -0
- package/dist/mcp/handlers/search.d.ts +52 -0
- package/dist/mcp/handlers/search.js +145 -0
- package/dist/mcp/handlers/sync.d.ts +47 -0
- package/dist/mcp/handlers/sync.js +211 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +268 -0
- package/dist/mcp/tools.d.ts +16 -0
- package/dist/mcp/tools.js +297 -0
- package/dist/sync/config.d.ts +26 -0
- package/dist/sync/config.js +140 -0
- package/dist/sync/discover.d.ts +51 -0
- package/dist/sync/discover.js +190 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/index.js +11 -0
- package/dist/sync/process.d.ts +50 -0
- package/dist/sync/process.js +285 -0
- package/dist/sync/processors.d.ts +24 -0
- package/dist/sync/processors.js +351 -0
- package/dist/tui/browse-handlers-ask.d.ts +30 -0
- package/dist/tui/browse-handlers-ask.js +372 -0
- package/dist/tui/browse-handlers-autocomplete.d.ts +49 -0
- package/dist/tui/browse-handlers-autocomplete.js +270 -0
- package/dist/tui/browse-handlers-extensions.d.ts +18 -0
- package/dist/tui/browse-handlers-extensions.js +107 -0
- package/dist/tui/browse-handlers-pending.d.ts +22 -0
- package/dist/tui/browse-handlers-pending.js +100 -0
- package/dist/tui/browse-handlers-research.d.ts +32 -0
- package/dist/tui/browse-handlers-research.js +363 -0
- package/dist/tui/browse-handlers-tools.d.ts +42 -0
- package/dist/tui/browse-handlers-tools.js +289 -0
- package/dist/tui/browse-handlers.d.ts +239 -0
- package/dist/tui/browse-handlers.js +1944 -0
- package/dist/tui/browse-render-extensions.d.ts +14 -0
- package/dist/tui/browse-render-extensions.js +114 -0
- package/dist/tui/browse-render-tools.d.ts +18 -0
- package/dist/tui/browse-render-tools.js +259 -0
- package/dist/tui/browse-render.d.ts +51 -0
- package/dist/tui/browse-render.js +599 -0
- package/dist/tui/browse-types.d.ts +142 -0
- package/dist/tui/browse-types.js +70 -0
- package/dist/tui/browse-ui.d.ts +10 -0
- package/dist/tui/browse-ui.js +432 -0
- package/dist/tui/browse.d.ts +17 -0
- package/dist/tui/browse.js +625 -0
- package/dist/tui/markdown.d.ts +22 -0
- package/dist/tui/markdown.js +223 -0
- package/package.json +71 -0
- package/plugins/claude-code/.claude-plugin/plugin.json +10 -0
- package/plugins/claude-code/.mcp.json +6 -0
- package/plugins/claude-code/skills/lore/SKILL.md +63 -0
- package/plugins/codex/SKILL.md +36 -0
- package/plugins/codex/agents/openai.yaml +10 -0
- package/plugins/gemini/GEMINI.md +31 -0
- package/plugins/gemini/gemini-extension.json +11 -0
- package/skills/generic-agent.md +99 -0
- 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>;
|
package/dist/core/git.js
ADDED
|
@@ -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,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;
|