@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,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - Sync Configuration
|
|
3
|
+
*
|
|
4
|
+
* Loads and validates sync-sources.json from ~/.config/lore/sync-sources.json
|
|
5
|
+
* This config is machine-specific (NOT in lore-data).
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Config Paths
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const CONFIG_DIR = path.join(os.homedir(), '.config', 'lore');
|
|
15
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'sync-sources.json');
|
|
16
|
+
export function getConfigPath() {
|
|
17
|
+
return CONFIG_FILE;
|
|
18
|
+
}
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Path Expansion
|
|
21
|
+
// ============================================================================
|
|
22
|
+
export function expandPath(p) {
|
|
23
|
+
if (p.startsWith('~')) {
|
|
24
|
+
return path.join(os.homedir(), p.slice(1));
|
|
25
|
+
}
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Default Config
|
|
30
|
+
// ============================================================================
|
|
31
|
+
function getDefaultConfig() {
|
|
32
|
+
return {
|
|
33
|
+
version: 1,
|
|
34
|
+
sources: [
|
|
35
|
+
{
|
|
36
|
+
name: 'Example Source',
|
|
37
|
+
path: '~/Documents/notes',
|
|
38
|
+
glob: '**/*.md',
|
|
39
|
+
project: 'notes',
|
|
40
|
+
enabled: false,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Config Loading
|
|
47
|
+
// ============================================================================
|
|
48
|
+
export async function loadSyncConfig() {
|
|
49
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
50
|
+
return getDefaultConfig();
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const content = await readFile(CONFIG_FILE, 'utf-8');
|
|
54
|
+
const config = JSON.parse(content);
|
|
55
|
+
// Validate version
|
|
56
|
+
if (config.version !== 1) {
|
|
57
|
+
console.warn(`[sync-config] Unknown config version: ${config.version}, expected 1`);
|
|
58
|
+
}
|
|
59
|
+
// Validate sources
|
|
60
|
+
if (!Array.isArray(config.sources)) {
|
|
61
|
+
throw new Error('Invalid config: sources must be an array');
|
|
62
|
+
}
|
|
63
|
+
for (const source of config.sources) {
|
|
64
|
+
if (!source.name || typeof source.name !== 'string') {
|
|
65
|
+
throw new Error(`Invalid source: missing or invalid 'name'`);
|
|
66
|
+
}
|
|
67
|
+
if (!source.path || typeof source.path !== 'string') {
|
|
68
|
+
throw new Error(`Invalid source "${source.name}": missing or invalid 'path'`);
|
|
69
|
+
}
|
|
70
|
+
if (!source.glob || typeof source.glob !== 'string') {
|
|
71
|
+
throw new Error(`Invalid source "${source.name}": missing or invalid 'glob'`);
|
|
72
|
+
}
|
|
73
|
+
if (!source.project || typeof source.project !== 'string') {
|
|
74
|
+
throw new Error(`Invalid source "${source.name}": missing or invalid 'project'`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof source.enabled !== 'boolean') {
|
|
77
|
+
source.enabled = true; // Default to enabled
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return config;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code === 'ENOENT') {
|
|
84
|
+
return getDefaultConfig();
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function saveSyncConfig(config) {
|
|
90
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
91
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
92
|
+
}
|
|
93
|
+
export async function initializeSyncConfig() {
|
|
94
|
+
if (existsSync(CONFIG_FILE)) {
|
|
95
|
+
return loadSyncConfig();
|
|
96
|
+
}
|
|
97
|
+
const config = getDefaultConfig();
|
|
98
|
+
await saveSyncConfig(config);
|
|
99
|
+
return config;
|
|
100
|
+
}
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Config Manipulation
|
|
103
|
+
// ============================================================================
|
|
104
|
+
export async function addSyncSource(source) {
|
|
105
|
+
const config = await loadSyncConfig();
|
|
106
|
+
// Check for duplicate names
|
|
107
|
+
const existingIndex = config.sources.findIndex(s => s.name === source.name);
|
|
108
|
+
if (existingIndex !== -1) {
|
|
109
|
+
throw new Error(`Source with name "${source.name}" already exists`);
|
|
110
|
+
}
|
|
111
|
+
config.sources.push(source);
|
|
112
|
+
await saveSyncConfig(config);
|
|
113
|
+
return config;
|
|
114
|
+
}
|
|
115
|
+
export async function updateSyncSource(name, updates) {
|
|
116
|
+
const config = await loadSyncConfig();
|
|
117
|
+
const sourceIndex = config.sources.findIndex(s => s.name === name);
|
|
118
|
+
if (sourceIndex === -1) {
|
|
119
|
+
throw new Error(`Source "${name}" not found`);
|
|
120
|
+
}
|
|
121
|
+
config.sources[sourceIndex] = {
|
|
122
|
+
...config.sources[sourceIndex],
|
|
123
|
+
...updates,
|
|
124
|
+
};
|
|
125
|
+
await saveSyncConfig(config);
|
|
126
|
+
return config;
|
|
127
|
+
}
|
|
128
|
+
export async function removeSyncSource(name) {
|
|
129
|
+
const config = await loadSyncConfig();
|
|
130
|
+
const sourceIndex = config.sources.findIndex(s => s.name === name);
|
|
131
|
+
if (sourceIndex === -1) {
|
|
132
|
+
throw new Error(`Source "${name}" not found`);
|
|
133
|
+
}
|
|
134
|
+
config.sources.splice(sourceIndex, 1);
|
|
135
|
+
await saveSyncConfig(config);
|
|
136
|
+
return config;
|
|
137
|
+
}
|
|
138
|
+
export function getEnabledSources(config) {
|
|
139
|
+
return config.sources.filter(s => s.enabled);
|
|
140
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - File Discovery (Phase 1)
|
|
3
|
+
*
|
|
4
|
+
* Discovers files from configured sources, computes content hashes,
|
|
5
|
+
* and checks against Supabase for deduplication.
|
|
6
|
+
*
|
|
7
|
+
* NO LLM calls in this phase - it's essentially free to run.
|
|
8
|
+
*/
|
|
9
|
+
import type { SyncSource } from './config.js';
|
|
10
|
+
export interface DiscoveredFile {
|
|
11
|
+
absolutePath: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
contentHash: string;
|
|
14
|
+
size: number;
|
|
15
|
+
modifiedAt: Date;
|
|
16
|
+
sourceName: string;
|
|
17
|
+
project: string;
|
|
18
|
+
existingId?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DiscoveryResult {
|
|
21
|
+
source: SyncSource;
|
|
22
|
+
totalFiles: number;
|
|
23
|
+
newFiles: DiscoveredFile[];
|
|
24
|
+
editedFiles: DiscoveredFile[];
|
|
25
|
+
existingFiles: number;
|
|
26
|
+
errors: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function computeFileHash(filePath: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Simple glob pattern matching for common patterns:
|
|
31
|
+
* - "**\/*.md" - any .md file in any subdirectory
|
|
32
|
+
* - "*.md" - .md files in root only
|
|
33
|
+
* - "**\/*.{md,pdf}" - multiple extensions
|
|
34
|
+
*/
|
|
35
|
+
export declare function matchesGlob(relativePath: string, glob: string): boolean;
|
|
36
|
+
export declare function checkExistingHashes(hashes: string[]): Promise<Set<string>>;
|
|
37
|
+
export declare function discoverSource(source: SyncSource, options?: {
|
|
38
|
+
onProgress?: (found: number, checked: number) => void;
|
|
39
|
+
}): Promise<DiscoveryResult>;
|
|
40
|
+
export declare function discoverAllSources(sources: SyncSource[], options?: {
|
|
41
|
+
onSourceStart?: (source: SyncSource) => void;
|
|
42
|
+
onSourceComplete?: (result: DiscoveryResult) => void;
|
|
43
|
+
}): Promise<DiscoveryResult[]>;
|
|
44
|
+
export declare function summarizeDiscovery(results: DiscoveryResult[]): {
|
|
45
|
+
totalSources: number;
|
|
46
|
+
totalFiles: number;
|
|
47
|
+
newFiles: number;
|
|
48
|
+
editedFiles: number;
|
|
49
|
+
existingFiles: number;
|
|
50
|
+
errors: number;
|
|
51
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - File Discovery (Phase 1)
|
|
3
|
+
*
|
|
4
|
+
* Discovers files from configured sources, computes content hashes,
|
|
5
|
+
* and checks against Supabase for deduplication.
|
|
6
|
+
*
|
|
7
|
+
* NO LLM calls in this phase - it's essentially free to run.
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, readdir, stat } from 'fs/promises';
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { expandPath } from './config.js';
|
|
14
|
+
import { getSourcePathMappings, getExistingContentHashes } from '../core/vector-store.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Hash Computation
|
|
17
|
+
// ============================================================================
|
|
18
|
+
export async function computeFileHash(filePath) {
|
|
19
|
+
const content = await readFile(filePath);
|
|
20
|
+
return createHash('sha256').update(content).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Glob Matching
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Simple glob pattern matching for common patterns:
|
|
27
|
+
* - "**\/*.md" - any .md file in any subdirectory
|
|
28
|
+
* - "*.md" - .md files in root only
|
|
29
|
+
* - "**\/*.{md,pdf}" - multiple extensions
|
|
30
|
+
*/
|
|
31
|
+
export function matchesGlob(relativePath, glob) {
|
|
32
|
+
// Handle {ext1,ext2} extension lists
|
|
33
|
+
const extensionMatch = glob.match(/\.\{([^}]+)\}$/);
|
|
34
|
+
if (extensionMatch) {
|
|
35
|
+
const extensions = extensionMatch[1].split(',');
|
|
36
|
+
const baseGlob = glob.replace(/\.\{[^}]+\}$/, '');
|
|
37
|
+
return extensions.some(ext => matchesGlob(relativePath, `${baseGlob}.${ext}`));
|
|
38
|
+
}
|
|
39
|
+
// Convert glob to regex
|
|
40
|
+
// Important: Replace glob wildcards BEFORE inserting regex patterns with special chars
|
|
41
|
+
let pattern = glob
|
|
42
|
+
.replace(/\?/g, '.') // ? matches single char (do first!)
|
|
43
|
+
.replace(/\*\*\//g, '{{DOUBLE_STAR_SLASH}}') // Placeholder for **/
|
|
44
|
+
.replace(/\*\*/g, '{{DOUBLE_STAR}}') // Placeholder for ** (without trailing /)
|
|
45
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
46
|
+
.replace(/{{DOUBLE_STAR_SLASH}}/g, '(.*\\/)?') // **/ matches any path including empty
|
|
47
|
+
.replace(/{{DOUBLE_STAR}}/g, '.*'); // ** matches anything including /
|
|
48
|
+
// Anchor the pattern
|
|
49
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
50
|
+
return regex.test(relativePath);
|
|
51
|
+
}
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// File Discovery
|
|
54
|
+
// ============================================================================
|
|
55
|
+
async function discoverFilesRecursive(dir, baseDir, glob, results) {
|
|
56
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const fullPath = path.join(dir, entry.name);
|
|
59
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
// Skip hidden directories and common non-content dirs
|
|
62
|
+
if (entry.name.startsWith('.') ||
|
|
63
|
+
entry.name === 'node_modules' ||
|
|
64
|
+
entry.name === '__pycache__') {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
await discoverFilesRecursive(fullPath, baseDir, glob, results);
|
|
68
|
+
}
|
|
69
|
+
else if (entry.isFile()) {
|
|
70
|
+
// Check if file matches glob pattern
|
|
71
|
+
if (matchesGlob(relativePath, glob)) {
|
|
72
|
+
results.push({ path: fullPath, relativePath });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Supabase Deduplication (delegates to vector-store)
|
|
79
|
+
// ============================================================================
|
|
80
|
+
export async function checkExistingHashes(hashes) {
|
|
81
|
+
return getExistingContentHashes('', hashes);
|
|
82
|
+
}
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Main Discovery Function
|
|
85
|
+
// ============================================================================
|
|
86
|
+
export async function discoverSource(source, options = {}) {
|
|
87
|
+
const { onProgress } = options;
|
|
88
|
+
const expandedPath = expandPath(source.path);
|
|
89
|
+
const result = {
|
|
90
|
+
source,
|
|
91
|
+
totalFiles: 0,
|
|
92
|
+
newFiles: [],
|
|
93
|
+
editedFiles: [],
|
|
94
|
+
existingFiles: 0,
|
|
95
|
+
errors: [],
|
|
96
|
+
};
|
|
97
|
+
// Check if source directory exists
|
|
98
|
+
if (!existsSync(expandedPath)) {
|
|
99
|
+
result.errors.push(`Directory not found: ${expandedPath}`);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
// Discover all matching files
|
|
103
|
+
const matchingFiles = [];
|
|
104
|
+
try {
|
|
105
|
+
await discoverFilesRecursive(expandedPath, expandedPath, source.glob, matchingFiles);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
result.errors.push(`Error scanning directory: ${error}`);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
result.totalFiles = matchingFiles.length;
|
|
112
|
+
if (matchingFiles.length === 0) {
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
// Compute hashes for all files
|
|
116
|
+
const filesWithHashes = [];
|
|
117
|
+
for (let i = 0; i < matchingFiles.length; i++) {
|
|
118
|
+
const file = matchingFiles[i];
|
|
119
|
+
try {
|
|
120
|
+
const fileStat = await stat(file.path);
|
|
121
|
+
const contentHash = await computeFileHash(file.path);
|
|
122
|
+
filesWithHashes.push({
|
|
123
|
+
absolutePath: file.path,
|
|
124
|
+
relativePath: file.relativePath,
|
|
125
|
+
contentHash,
|
|
126
|
+
size: fileStat.size,
|
|
127
|
+
modifiedAt: fileStat.mtime,
|
|
128
|
+
sourceName: source.name,
|
|
129
|
+
project: source.project,
|
|
130
|
+
});
|
|
131
|
+
onProgress?.(filesWithHashes.length, matchingFiles.length);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
result.errors.push(`Error processing ${file.path}: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Check which hashes already exist in Supabase (unchanged files)
|
|
138
|
+
const allHashes = filesWithHashes.map(f => f.contentHash);
|
|
139
|
+
const existingHashes = await checkExistingHashes(allHashes);
|
|
140
|
+
// Check which paths already exist in Supabase (for edit detection)
|
|
141
|
+
const allPaths = filesWithHashes.map(f => f.absolutePath);
|
|
142
|
+
const pathMappings = await getSourcePathMappings('', allPaths);
|
|
143
|
+
// Categorize files: existing (unchanged), edited, or new
|
|
144
|
+
for (const file of filesWithHashes) {
|
|
145
|
+
if (existingHashes.has(file.contentHash)) {
|
|
146
|
+
// Content hash matches - file is unchanged
|
|
147
|
+
result.existingFiles++;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Content is different - check if path exists (edit) or not (new)
|
|
151
|
+
const existingSource = pathMappings.get(file.absolutePath);
|
|
152
|
+
if (existingSource) {
|
|
153
|
+
// Same path, different hash = edit
|
|
154
|
+
file.existingId = existingSource.id;
|
|
155
|
+
result.editedFiles.push(file);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// New path = new file
|
|
159
|
+
result.newFiles.push(file);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
export async function discoverAllSources(sources, options = {}) {
|
|
166
|
+
const { onSourceStart, onSourceComplete } = options;
|
|
167
|
+
const results = [];
|
|
168
|
+
for (const source of sources) {
|
|
169
|
+
if (!source.enabled)
|
|
170
|
+
continue;
|
|
171
|
+
onSourceStart?.(source);
|
|
172
|
+
const result = await discoverSource(source);
|
|
173
|
+
onSourceComplete?.(result);
|
|
174
|
+
results.push(result);
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
}
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Summary Statistics
|
|
180
|
+
// ============================================================================
|
|
181
|
+
export function summarizeDiscovery(results) {
|
|
182
|
+
return {
|
|
183
|
+
totalSources: results.length,
|
|
184
|
+
totalFiles: results.reduce((sum, r) => sum + r.totalFiles, 0),
|
|
185
|
+
newFiles: results.reduce((sum, r) => sum + r.newFiles.length, 0),
|
|
186
|
+
editedFiles: results.reduce((sum, r) => sum + r.editedFiles.length, 0),
|
|
187
|
+
existingFiles: results.reduce((sum, r) => sum + r.existingFiles, 0),
|
|
188
|
+
errors: results.reduce((sum, r) => sum + r.errors.length, 0),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - Universal Sync Module
|
|
3
|
+
*
|
|
4
|
+
* Two-phase sync system:
|
|
5
|
+
* 1. Discovery: Find files, compute hashes, check for duplicates
|
|
6
|
+
* 2. Processing: Claude extracts metadata, generates embeddings, stores
|
|
7
|
+
*/
|
|
8
|
+
export * from './config.js';
|
|
9
|
+
export * from './discover.js';
|
|
10
|
+
export * from './processors.js';
|
|
11
|
+
export * from './process.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - Universal Sync Module
|
|
3
|
+
*
|
|
4
|
+
* Two-phase sync system:
|
|
5
|
+
* 1. Discovery: Find files, compute hashes, check for duplicates
|
|
6
|
+
* 2. Processing: Claude extracts metadata, generates embeddings, stores
|
|
7
|
+
*/
|
|
8
|
+
export * from './config.js';
|
|
9
|
+
export * from './discover.js';
|
|
10
|
+
export * from './processors.js';
|
|
11
|
+
export * from './process.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lore - Claude Processing (Phase 2)
|
|
3
|
+
*
|
|
4
|
+
* Uses Claude to extract metadata from new files:
|
|
5
|
+
* - title: Descriptive title
|
|
6
|
+
* - summary: 2-4 sentence summary with key takeaways
|
|
7
|
+
* - date: ISO date if present
|
|
8
|
+
* - participants: List of names if present
|
|
9
|
+
* - content_type: interview|meeting|conversation|document|note|analysis
|
|
10
|
+
*
|
|
11
|
+
* Only called for NEW files (not already in Supabase).
|
|
12
|
+
*/
|
|
13
|
+
import type { DiscoveredFile } from './discover.js';
|
|
14
|
+
import type { ContentType } from '../core/types.js';
|
|
15
|
+
import { type ImageMediaType } from './processors.js';
|
|
16
|
+
export interface ExtractedMetadata {
|
|
17
|
+
title: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
date: string | null;
|
|
20
|
+
participants: string[];
|
|
21
|
+
content_type: ContentType;
|
|
22
|
+
}
|
|
23
|
+
export interface ProcessedFile {
|
|
24
|
+
file: DiscoveredFile;
|
|
25
|
+
metadata: ExtractedMetadata;
|
|
26
|
+
sourceId: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ProcessResult {
|
|
29
|
+
processed: ProcessedFile[];
|
|
30
|
+
errors: Array<{
|
|
31
|
+
file: DiscoveredFile;
|
|
32
|
+
error: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare function extractMetadata(content: string, filePath: string, options?: {
|
|
36
|
+
model?: string;
|
|
37
|
+
image?: {
|
|
38
|
+
base64: string;
|
|
39
|
+
mediaType: ImageMediaType;
|
|
40
|
+
};
|
|
41
|
+
}): Promise<ExtractedMetadata>;
|
|
42
|
+
export declare function processFiles(files: DiscoveredFile[], dataDir: string, options?: {
|
|
43
|
+
onProgress?: (completed: number, total: number, title: string) => void;
|
|
44
|
+
model?: string;
|
|
45
|
+
concurrency?: number;
|
|
46
|
+
gitPush?: boolean;
|
|
47
|
+
hookContext?: {
|
|
48
|
+
mode: 'mcp' | 'cli';
|
|
49
|
+
};
|
|
50
|
+
}): Promise<ProcessResult>;
|