@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,268 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lore - MCP Server
4
+ *
5
+ * Exposes knowledge repository tools via Model Context Protocol.
6
+ * Supports both simple query tools and agentic research capabilities.
7
+ *
8
+ * Auto-syncs: Periodically checks for new sources and syncs them.
9
+ */
10
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
13
+ import chokidar from 'chokidar';
14
+ import { exec } from 'child_process';
15
+ import { promisify } from 'util';
16
+ import { readdir } from 'fs/promises';
17
+ import path from 'path';
18
+ import { toolDefinitions } from './tools.js';
19
+ import { handleSearch } from './handlers/search.js';
20
+ import { handleGetSource } from './handlers/get-source.js';
21
+ import { handleListSources } from './handlers/list-sources.js';
22
+ import { handleRetain } from './handlers/retain.js';
23
+ import { handleIngest } from './handlers/ingest.js';
24
+ import { handleResearch } from './handlers/research.js';
25
+ import { handleListProjects } from './handlers/list-projects.js';
26
+ import { handleSync } from './handlers/sync.js';
27
+ import { handleArchiveProject } from './handlers/archive-project.js';
28
+ import { indexExists, getAllSources } from '../core/vector-store.js';
29
+ import { expandPath } from '../sync/config.js';
30
+ import { getExtensionRegistry } from '../extensions/registry.js';
31
+ import { getExtensionsDir } from '../extensions/config.js';
32
+ import { bridgeConfigToEnv } from '../core/config.js';
33
+ const execAsync = promisify(exec);
34
+ // Configuration from environment
35
+ const LORE_DATA_DIR = expandPath(process.env.LORE_DATA_DIR || '~/.lore');
36
+ const DB_PATH = path.join(LORE_DATA_DIR, 'lore.lance');
37
+ const SYNC_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
38
+ // Auto-sync is DISABLED by default - use `lore watch` for visible sync
39
+ // Set LORE_AUTO_SYNC=true to enable background sync in MCP server
40
+ const AUTO_SYNC = process.env.LORE_AUTO_SYNC === 'true';
41
+ const AUTO_GIT_PULL = process.env.LORE_AUTO_GIT_PULL !== 'false';
42
+ const AUTO_GIT_PUSH = process.env.LORE_AUTO_GIT_PUSH !== 'false';
43
+ const AUTO_INDEX = process.env.LORE_AUTO_INDEX !== 'false';
44
+ const WATCH_EXTENSIONS = process.env.LORE_EXTENSION_WATCH === 'true' || process.argv.includes('--watch');
45
+ /**
46
+ * Try to git pull, handling conflicts gracefully
47
+ */
48
+ async function tryGitPull() {
49
+ try {
50
+ // Check if we're in a git repo
51
+ await execAsync('git rev-parse --git-dir', { cwd: LORE_DATA_DIR });
52
+ // Stash any local changes (shouldn't be any, but just in case)
53
+ await execAsync('git stash', { cwd: LORE_DATA_DIR }).catch(() => { });
54
+ // Pull with rebase to avoid merge commits
55
+ const { stdout } = await execAsync('git pull --rebase', { cwd: LORE_DATA_DIR });
56
+ const pulled = !stdout.includes('Already up to date');
57
+ return { pulled };
58
+ }
59
+ catch (error) {
60
+ // Not a git repo or pull failed - that's okay, continue without sync
61
+ return { pulled: false, error: String(error) };
62
+ }
63
+ }
64
+ /**
65
+ * Find sources on disk that aren't in the index
66
+ */
67
+ async function findUnsyncedSources() {
68
+ const sourcesDir = path.join(LORE_DATA_DIR, 'sources');
69
+ try {
70
+ // Get source IDs from disk
71
+ const diskSources = await readdir(sourcesDir, { withFileTypes: true });
72
+ const diskIds = diskSources
73
+ .filter(d => d.isDirectory() && !d.name.startsWith('.'))
74
+ .map(d => d.name);
75
+ // Get source IDs from index
76
+ const indexedSources = await getAllSources(DB_PATH, {});
77
+ const indexedIds = new Set(indexedSources.map(s => s.id));
78
+ // Find the difference
79
+ return diskIds.filter(id => !indexedIds.has(id));
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ }
85
+ /**
86
+ * Periodic sync check
87
+ */
88
+ async function syncCheck() {
89
+ try {
90
+ // Use the sync handler for actual sync
91
+ const result = await handleSync(DB_PATH, LORE_DATA_DIR, {
92
+ git_pull: AUTO_GIT_PULL,
93
+ index_new: AUTO_INDEX,
94
+ }, { hookContext: { mode: 'mcp' } });
95
+ if (result.git_pulled) {
96
+ console.error('[lore] Git pulled new changes');
97
+ }
98
+ if (result.sources_indexed > 0) {
99
+ console.error(`[lore] Auto-indexed ${result.sources_indexed} new source(s)`);
100
+ }
101
+ else if (!AUTO_INDEX) {
102
+ // Check for unsynced without indexing
103
+ const unsynced = await findUnsyncedSources();
104
+ if (unsynced.length > 0) {
105
+ console.error(`[lore] Found ${unsynced.length} unsynced sources. Use 'sync' tool or set LORE_AUTO_INDEX=true.`);
106
+ }
107
+ }
108
+ }
109
+ catch (error) {
110
+ console.error('[lore] Sync check error:', error);
111
+ }
112
+ }
113
+ async function main() {
114
+ // Bridge config.json values into process.env (env vars take precedence)
115
+ try {
116
+ await bridgeConfigToEnv();
117
+ }
118
+ catch {
119
+ // Config not set up yet — fine
120
+ }
121
+ // Check if index exists
122
+ const hasIndex = await indexExists(DB_PATH);
123
+ if (!hasIndex) {
124
+ console.error(`[lore] No index found at ${DB_PATH}. Run 'lore ingest' to add sources.`);
125
+ }
126
+ // Auto-sync (disabled by default - use `lore watch` instead)
127
+ if (AUTO_SYNC) {
128
+ // Initial sync check
129
+ await syncCheck();
130
+ // Periodic sync check
131
+ setInterval(syncCheck, SYNC_INTERVAL_MS);
132
+ console.error(`[lore] Auto-sync enabled (every ${SYNC_INTERVAL_MS / 60000} minutes)`);
133
+ }
134
+ else {
135
+ console.error(`[lore] Auto-sync disabled. Use 'lore watch' for visible sync, or set LORE_AUTO_SYNC=true`);
136
+ }
137
+ const server = new Server({
138
+ name: 'lore',
139
+ version: '0.1.0',
140
+ }, {
141
+ capabilities: {
142
+ tools: {},
143
+ },
144
+ });
145
+ const extensionRegistry = await getExtensionRegistry({
146
+ logger: (message) => console.error(message),
147
+ });
148
+ if (WATCH_EXTENSIONS) {
149
+ const extensionsDir = getExtensionsDir();
150
+ const watchPatterns = [
151
+ path.join(extensionsDir, '**/package.json'),
152
+ path.join(extensionsDir, '**/dist/**'),
153
+ ];
154
+ let reloadTimer = null;
155
+ const scheduleReload = (event, filePath) => {
156
+ if (reloadTimer) {
157
+ clearTimeout(reloadTimer);
158
+ }
159
+ reloadTimer = setTimeout(async () => {
160
+ reloadTimer = null;
161
+ try {
162
+ const relativePath = path.relative(extensionsDir, filePath);
163
+ console.error(`[extensions] Change detected (${event}: ${relativePath})`);
164
+ await extensionRegistry.reload();
165
+ }
166
+ catch (error) {
167
+ console.error('[extensions] Reload failed:', error);
168
+ }
169
+ }, 500);
170
+ };
171
+ const watcher = chokidar.watch(watchPatterns, {
172
+ ignoreInitial: true,
173
+ });
174
+ watcher.on('all', (event, filePath) => {
175
+ if (!filePath) {
176
+ return;
177
+ }
178
+ scheduleReload(event, filePath);
179
+ });
180
+ console.error('[extensions] Watching for changes in installed extensions');
181
+ }
182
+ // List available tools (core tools only - extensions are event-driven middleware)
183
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
184
+ return { tools: toolDefinitions };
185
+ });
186
+ // Handle tool calls (core tools only)
187
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
188
+ const { name, arguments: args } = request.params;
189
+ try {
190
+ let result;
191
+ switch (name) {
192
+ // Simple query tools (cheap, fast)
193
+ case 'search':
194
+ result = await handleSearch(DB_PATH, LORE_DATA_DIR, args);
195
+ break;
196
+ case 'get_source':
197
+ result = await handleGetSource(DB_PATH, LORE_DATA_DIR, args);
198
+ break;
199
+ case 'list_sources':
200
+ result = await handleListSources(DB_PATH, args);
201
+ break;
202
+ case 'list_projects':
203
+ result = await handleListProjects(DB_PATH);
204
+ break;
205
+ // Push-based retention
206
+ case 'retain':
207
+ result = await handleRetain(DB_PATH, LORE_DATA_DIR, args, {
208
+ autoPush: AUTO_GIT_PUSH,
209
+ });
210
+ break;
211
+ // Direct document ingestion
212
+ case 'ingest':
213
+ result = await handleIngest(DB_PATH, LORE_DATA_DIR, args, {
214
+ autoPush: AUTO_GIT_PUSH,
215
+ hookContext: { mode: 'mcp' },
216
+ });
217
+ break;
218
+ // Agentic research tool (uses Claude Agent SDK internally)
219
+ case 'research':
220
+ result = await handleResearch(DB_PATH, LORE_DATA_DIR, args, {
221
+ hookContext: { mode: 'mcp' },
222
+ });
223
+ break;
224
+ // Sync tool
225
+ case 'sync':
226
+ result = await handleSync(DB_PATH, LORE_DATA_DIR, args, {
227
+ hookContext: { mode: 'mcp' },
228
+ });
229
+ break;
230
+ // Project management
231
+ case 'archive_project':
232
+ result = await handleArchiveProject(DB_PATH, LORE_DATA_DIR, args, {
233
+ autoPush: AUTO_GIT_PUSH,
234
+ });
235
+ break;
236
+ default:
237
+ throw new Error(`Unknown tool: ${name}`);
238
+ }
239
+ return {
240
+ content: [
241
+ {
242
+ type: 'text',
243
+ text: JSON.stringify(result, null, 2),
244
+ },
245
+ ],
246
+ };
247
+ }
248
+ catch (error) {
249
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: JSON.stringify({ error: errorMessage }),
255
+ },
256
+ ],
257
+ isError: true,
258
+ };
259
+ }
260
+ });
261
+ // Start server
262
+ const transport = new StdioServerTransport();
263
+ await server.connect(transport);
264
+ }
265
+ main().catch((error) => {
266
+ console.error('Lore server error:', error);
267
+ process.exit(1);
268
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lore - MCP Tool Definitions
3
+ *
4
+ * Defines the tools exposed by the Lore MCP server.
5
+ * Two categories:
6
+ * 1. Simple tools - Direct database queries, cheap and fast
7
+ * 2. Agentic tools - Use Claude Agent SDK for complex research
8
+ *
9
+ * Descriptions are written for agent consumption. Agents without a skill file
10
+ * should understand Lore purely from these tool descriptions.
11
+ */
12
+ export declare const toolDefinitions: {
13
+ name: string;
14
+ description: string;
15
+ inputSchema: Record<string, unknown>;
16
+ }[];
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Lore - MCP Tool Definitions
3
+ *
4
+ * Defines the tools exposed by the Lore MCP server.
5
+ * Two categories:
6
+ * 1. Simple tools - Direct database queries, cheap and fast
7
+ * 2. Agentic tools - Use Claude Agent SDK for complex research
8
+ *
9
+ * Descriptions are written for agent consumption. Agents without a skill file
10
+ * should understand Lore purely from these tool descriptions.
11
+ */
12
+ import { z } from 'zod';
13
+ // Helper to convert Zod schema to JSON Schema
14
+ function zodToJsonSchema(schema) {
15
+ // Simplified conversion - in production use zod-to-json-schema
16
+ if (schema instanceof z.ZodObject) {
17
+ const shape = schema.shape;
18
+ const properties = {};
19
+ const required = [];
20
+ for (const [key, value] of Object.entries(shape)) {
21
+ const zodValue = value;
22
+ properties[key] = zodToJsonSchema(zodValue);
23
+ if (!(zodValue instanceof z.ZodOptional)) {
24
+ required.push(key);
25
+ }
26
+ }
27
+ return {
28
+ type: 'object',
29
+ properties,
30
+ required: required.length > 0 ? required : undefined,
31
+ };
32
+ }
33
+ if (schema instanceof z.ZodString) {
34
+ return { type: 'string', description: schema.description };
35
+ }
36
+ if (schema instanceof z.ZodNumber) {
37
+ return { type: 'number', description: schema.description };
38
+ }
39
+ if (schema instanceof z.ZodBoolean) {
40
+ return { type: 'boolean', description: schema.description };
41
+ }
42
+ if (schema instanceof z.ZodOptional) {
43
+ return zodToJsonSchema(schema.unwrap());
44
+ }
45
+ if (schema instanceof z.ZodEnum) {
46
+ return { type: 'string', enum: schema.options, description: schema.description };
47
+ }
48
+ if (schema instanceof z.ZodArray) {
49
+ return { type: 'array', items: zodToJsonSchema(schema.element) };
50
+ }
51
+ return { type: 'string' };
52
+ }
53
+ // ============================================================================
54
+ // Simple Query Tools
55
+ // ============================================================================
56
+ const SearchSchema = z.object({
57
+ query: z.string().describe('Search query'),
58
+ project: z.string().optional().describe('Filter to specific project'),
59
+ source_type: z
60
+ .string()
61
+ .optional()
62
+ .describe('Filter by source type (matches the source_type passed during ingest, e.g. "meeting", "slack", "github-issue")'),
63
+ content_type: z
64
+ .enum(['interview', 'meeting', 'conversation', 'document', 'note', 'analysis'])
65
+ .optional()
66
+ .describe('Filter by content type'),
67
+ limit: z.number().optional().describe('Max results (default 10)'),
68
+ include_archived: z
69
+ .boolean()
70
+ .optional()
71
+ .describe('Include sources from archived projects (default: false)'),
72
+ mode: z
73
+ .enum(['semantic', 'keyword', 'hybrid', 'regex'])
74
+ .optional()
75
+ .describe('Search mode: semantic (vector), keyword (full-text), hybrid (RRF fusion, default), regex (local grep)'),
76
+ });
77
+ const GetSourceSchema = z.object({
78
+ source_id: z.string().describe('ID of the source document'),
79
+ include_content: z.boolean().optional().describe('Include full original content/transcript (default: false - set to true for raw text)'),
80
+ });
81
+ const ListSourcesSchema = z.object({
82
+ project: z.string().optional().describe('Filter to specific project'),
83
+ source_type: z
84
+ .string()
85
+ .optional()
86
+ .describe('Filter by source type (matches the source_type passed during ingest, e.g. "meeting", "slack", "github-issue")'),
87
+ limit: z.number().optional().describe('Max results (default 20)'),
88
+ });
89
+ const RetainSchema = z.object({
90
+ content: z.string().describe('The insight, decision, or note to retain'),
91
+ project: z.string().describe('Project this belongs to'),
92
+ type: z
93
+ .enum(['insight', 'decision', 'requirement', 'note'])
94
+ .describe('Type of knowledge being retained'),
95
+ source_context: z
96
+ .string()
97
+ .optional()
98
+ .describe('Where this came from (e.g., "user interview with Sarah")'),
99
+ tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
100
+ });
101
+ // ============================================================================
102
+ // Agentic Research Tool
103
+ // ============================================================================
104
+ const ResearchSchema = z.object({
105
+ task: z
106
+ .string()
107
+ .describe('Research task description (e.g., "Find all user feedback about export performance")'),
108
+ project: z.string().optional().describe('Focus research on specific project'),
109
+ include_sources: z
110
+ .boolean()
111
+ .optional()
112
+ .describe('Include source document references (default: true)'),
113
+ });
114
+ // ============================================================================
115
+ // Ingest Tool
116
+ // ============================================================================
117
+ const IngestSchema = z.object({
118
+ content: z.string().describe('The document content to ingest'),
119
+ title: z.string().describe('Title for the document'),
120
+ project: z.string().describe('Project this document belongs to'),
121
+ source_type: z
122
+ .string()
123
+ .optional()
124
+ .describe('Content category. Use one of these canonical values: meeting, interview, document, notes, analysis, conversation, article, slack, email, github-issue, github-pr, notion, spec, rfc, transcript, pdf, image, video, audio. Defaults to "document". Variants are auto-normalized (e.g. "Slack Thread" → "slack", "blog post" → "article").'),
125
+ date: z.string().optional().describe('Date of the document (ISO format, defaults to now)'),
126
+ participants: z
127
+ .array(z.string())
128
+ .optional()
129
+ .describe('People involved (for meetings/interviews)'),
130
+ tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
131
+ source_url: z
132
+ .string()
133
+ .optional()
134
+ .describe('Original URL for citation linking (e.g., Slack permalink, Notion page URL, GitHub issue URL). Stored for traceability.'),
135
+ source_name: z
136
+ .string()
137
+ .optional()
138
+ .describe('Human-readable origin label (e.g., "Slack #product-team", "GitHub issue #42", "Notion: Sprint Planning")'),
139
+ });
140
+ // ============================================================================
141
+ // Sync Tool
142
+ // ============================================================================
143
+ const SyncSchema = z.object({
144
+ git_pull: z
145
+ .boolean()
146
+ .optional()
147
+ .describe('Pull latest changes from git remote (default: true)'),
148
+ git_push: z
149
+ .boolean()
150
+ .optional()
151
+ .describe('Push local changes to git remote (default: true)'),
152
+ index_new: z
153
+ .boolean()
154
+ .optional()
155
+ .describe('Index any new sources found on disk (default: true)'),
156
+ dry_run: z
157
+ .boolean()
158
+ .optional()
159
+ .describe('Show what would be synced without actually processing (default: false)'),
160
+ use_legacy: z
161
+ .boolean()
162
+ .optional()
163
+ .describe('Use only legacy disk-based sync, skip universal sync (default: false)'),
164
+ });
165
+ // ============================================================================
166
+ // Project Management Tools
167
+ // ============================================================================
168
+ const ArchiveProjectSchema = z.object({
169
+ project: z.string().describe('Name of the project to archive'),
170
+ reason: z
171
+ .string()
172
+ .optional()
173
+ .describe('Reason for archiving (e.g., "Pivoted to new approach", "Project completed")'),
174
+ successor_project: z
175
+ .string()
176
+ .optional()
177
+ .describe('Name of the project that supersedes this one, if any'),
178
+ });
179
+ // ============================================================================
180
+ // Tool Definitions for MCP
181
+ // ============================================================================
182
+ export const toolDefinitions = [
183
+ // Simple tools
184
+ {
185
+ name: 'search',
186
+ description: `Search the Lore knowledge base. Returns source summaries with relevance scores, matching quotes, and themes.
187
+
188
+ SEARCH MODES (pick based on your query):
189
+ - hybrid (default): Best for most queries. Combines vector similarity + full-text search via RRF fusion.
190
+ - semantic: Vector similarity only. Use for conceptual queries ("pain points", "user frustrations") where exact terms don't matter.
191
+ - keyword: Full-text search only. Use for exact terms, identifiers, or proper nouns ("TS-01", "OAuth", specific names).
192
+ - regex: Pattern matching in local files. Use for code patterns or complex text matching ("OAuth.*config").
193
+
194
+ WHEN TO USE THIS TOOL:
195
+ - Quick lookups of specific information
196
+ - Finding sources related to a topic
197
+ - Gathering context before answering a question
198
+ - Any query where you expect 1-3 relevant sources to answer it
199
+
200
+ USE 'research' INSTEAD when the question requires cross-referencing multiple sources, detecting patterns across documents, or synthesizing findings with citations. 'research' costs more API calls — avoid it for simple lookups.`,
201
+ inputSchema: zodToJsonSchema(SearchSchema),
202
+ },
203
+ {
204
+ name: 'get_source',
205
+ description: `Retrieve full details of a specific source document by ID. Returns metadata, summary, quotes, themes, and optionally the complete original content.
206
+
207
+ Set include_content=true to get the full raw text (transcript, document body, etc.). By default only metadata and summary are returned.
208
+
209
+ USE THIS AFTER 'search' returns a relevant source_id and you need the full document for detailed analysis or quoting.`,
210
+ inputSchema: zodToJsonSchema(GetSourceSchema),
211
+ },
212
+ {
213
+ name: 'list_sources',
214
+ description: `List all sources in the knowledge base, optionally filtered by project or type. Returns summaries sorted by date (newest first).
215
+
216
+ Use this to browse what exists in a project, understand the scope of available knowledge, or check if content has already been ingested before calling 'ingest'.`,
217
+ inputSchema: zodToJsonSchema(ListSourcesSchema),
218
+ },
219
+ {
220
+ name: 'list_projects',
221
+ description: `List all projects with source counts and latest activity dates. Use this to discover what knowledge domains exist before searching or ingesting content.`,
222
+ inputSchema: {
223
+ type: 'object',
224
+ properties: {},
225
+ },
226
+ },
227
+ {
228
+ name: 'retain',
229
+ description: `Save a discrete insight, decision, requirement, or note to the knowledge base. These are short, synthesized pieces of knowledge — NOT full documents.
230
+
231
+ Examples of what to retain:
232
+ - A decision: "We chose JWT over session cookies because of mobile app requirements"
233
+ - An insight: "3 out of 5 users mentioned export speed as their top frustration"
234
+ - A requirement: "Must support SSO for enterprise customers"
235
+
236
+ USE 'ingest' INSTEAD for full documents, meeting notes, transcripts, or any content longer than a few paragraphs.`,
237
+ inputSchema: zodToJsonSchema(RetainSchema),
238
+ },
239
+ // Agentic tool
240
+ {
241
+ name: 'research',
242
+ description: `Run a comprehensive research query across the knowledge base. An internal agent iteratively searches, reads sources, cross-references findings, and synthesizes a research package with full citations.
243
+
244
+ Returns: summary, key findings, supporting quotes with citations, conflicts detected between sources, and suggested follow-up queries.
245
+
246
+ WHEN TO USE:
247
+ - Questions that span multiple sources ("What do we know about authentication?")
248
+ - Detecting patterns or contradictions across documents
249
+ - Building a cited research package for decision-making
250
+ - Open-ended exploration of a topic
251
+
252
+ COST: This tool makes multiple LLM calls internally (typically 3-8 search + read cycles). For simple lookups, use 'search' instead — it's 10x cheaper and faster.`,
253
+ inputSchema: zodToJsonSchema(ResearchSchema),
254
+ },
255
+ // Ingest tool
256
+ {
257
+ name: 'ingest',
258
+ description: `Push content into the Lore knowledge base. This is the primary way to add documents from external systems (Slack threads, Notion pages, GitHub issues, meeting notes, emails, etc.).
259
+
260
+ IDEMPOTENT: Content is deduplicated by SHA256 hash. Calling ingest with identical content returns {deduplicated: true} immediately — no LLM calls, no disk writes. Safe to call repeatedly.
261
+
262
+ WHAT HAPPENS:
263
+ 1. Content hash checked for deduplication
264
+ 2. Document saved to disk
265
+ 3. LLM extracts summary, themes, and key quotes
266
+ 4. Embedding generated for semantic search
267
+ 5. Indexed in Supabase for instant retrieval
268
+
269
+ BEST PRACTICES:
270
+ - Always pass source_url when available (enables citation linking back to the original)
271
+ - Use source_name for human-readable origin context (e.g., "Slack #product-team")
272
+ - source_type is a free-form hint — use whatever describes the content (slack, email, notion, github-issue, etc.)
273
+ - Use 'retain' instead for short discrete insights/decisions (not full documents)`,
274
+ inputSchema: zodToJsonSchema(IngestSchema),
275
+ },
276
+ // Sync tool
277
+ {
278
+ name: 'sync',
279
+ description: `Sync the knowledge base from configured source directories. Two-phase process:
280
+
281
+ Phase 1 (Discovery — free, no LLM calls): Scans configured directories, computes content hashes, identifies new files.
282
+ Phase 2 (Processing — only new files): Extracts metadata via LLM, generates embeddings, stores in Supabase.
283
+
284
+ Use this when source directories have been updated externally, or to refresh the index after manual file changes. Source directories are configured via 'lore sources add' CLI command.
285
+
286
+ Note: For pushing content from agents, use 'ingest' instead — it's the direct path.`,
287
+ inputSchema: zodToJsonSchema(SyncSchema),
288
+ },
289
+ // Project management
290
+ {
291
+ name: 'archive_project',
292
+ description: `Archive a project and exclude its sources from default search results. Archived sources are preserved for historical reference and can be included with include_archived=true in search.
293
+
294
+ Only use when explicitly requested — this is a curation action, not an automatic cleanup.`,
295
+ inputSchema: zodToJsonSchema(ArchiveProjectSchema),
296
+ },
297
+ ];
@@ -0,0 +1,26 @@
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
+ export interface SyncSource {
8
+ name: string;
9
+ path: string;
10
+ glob: string;
11
+ project: string;
12
+ enabled: boolean;
13
+ }
14
+ export interface SyncConfig {
15
+ version: number;
16
+ sources: SyncSource[];
17
+ }
18
+ export declare function getConfigPath(): string;
19
+ export declare function expandPath(p: string): string;
20
+ export declare function loadSyncConfig(): Promise<SyncConfig>;
21
+ export declare function saveSyncConfig(config: SyncConfig): Promise<void>;
22
+ export declare function initializeSyncConfig(): Promise<SyncConfig>;
23
+ export declare function addSyncSource(source: SyncSource): Promise<SyncConfig>;
24
+ export declare function updateSyncSource(name: string, updates: Partial<SyncSource>): Promise<SyncConfig>;
25
+ export declare function removeSyncSource(name: string): Promise<SyncConfig>;
26
+ export declare function getEnabledSources(config: SyncConfig): SyncSource[];