@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,309 @@
1
+ /**
2
+ * Lore Extension Registry + Loader
3
+ */
4
+ import { createRequire } from 'module';
5
+ import path from 'path';
6
+ import { readFile } from 'fs/promises';
7
+ import { fileURLToPath, pathToFileURL } from 'url';
8
+ import { getExtensionsDir, loadExtensionConfig, } from './config.js';
9
+ import { getAllSources } from '../core/vector-store.js';
10
+ import { createProposal } from './proposals.js';
11
+ function getLogger(logger) {
12
+ return logger || ((message) => console.error(message));
13
+ }
14
+ function createQueryFunction() {
15
+ return async (options) => {
16
+ try {
17
+ // Get all sources for project (search with embeddings requires more complex setup)
18
+ const results = await getAllSources('', {
19
+ project: options.project,
20
+ limit: options.limit || 100,
21
+ source_type: options.sourceType,
22
+ });
23
+ return results.map((r) => ({
24
+ id: r.id,
25
+ title: r.title,
26
+ summary: r.summary || '',
27
+ projects: r.projects || [],
28
+ created_at: r.created_at,
29
+ }));
30
+ }
31
+ catch (error) {
32
+ console.error('[extensions] Query failed:', error);
33
+ return [];
34
+ }
35
+ };
36
+ }
37
+ function createAskFunction(dbPath) {
38
+ return async (question, options = {}) => {
39
+ try {
40
+ // Lazy import to avoid circular dependencies
41
+ const { default: Anthropic } = await import('@anthropic-ai/sdk');
42
+ const { searchSources } = await import('../core/vector-store.js');
43
+ const { generateEmbedding } = await import('../core/embedder.js');
44
+ if (!process.env.ANTHROPIC_API_KEY) {
45
+ throw new Error('ANTHROPIC_API_KEY not set');
46
+ }
47
+ // Search for relevant sources
48
+ const embedding = await generateEmbedding(question);
49
+ const sources = await searchSources(dbPath, embedding, {
50
+ limit: options.maxSources || 10,
51
+ project: options.project,
52
+ queryText: question,
53
+ mode: 'hybrid',
54
+ });
55
+ if (sources.length === 0) {
56
+ return 'No relevant sources found.';
57
+ }
58
+ // Build context
59
+ const sourceContext = sources.map((s, i) => {
60
+ return `[Source ${i + 1}: ${s.title}]\n${s.summary}`;
61
+ }).join('\n\n---\n\n');
62
+ // Call AI
63
+ const anthropic = new Anthropic();
64
+ const response = await anthropic.messages.create({
65
+ model: 'claude-sonnet-4-20250514',
66
+ max_tokens: 2048,
67
+ system: 'You are a research assistant. Answer based on the provided sources. Be concise.',
68
+ messages: [{
69
+ role: 'user',
70
+ content: `Question: ${question}\n\n---\nSources:\n${sourceContext}`
71
+ }],
72
+ });
73
+ const textBlocks = response.content.filter(block => block.type === 'text');
74
+ return textBlocks.map(block => block.text).join('\n');
75
+ }
76
+ catch (error) {
77
+ console.error('[extensions] Ask failed:', error);
78
+ throw error;
79
+ }
80
+ };
81
+ }
82
+ export function createProposeFunction(extensionName, permissions) {
83
+ return async (change) => {
84
+ // Enforce permissions
85
+ const perms = permissions || {};
86
+ if (change.type === 'create_source' || change.type === 'retain_insight') {
87
+ if (!perms.proposeCreate) {
88
+ throw new Error(`Extension "${extensionName}" does not have permission to propose creating documents. Add permissions.proposeCreate = true to the extension.`);
89
+ }
90
+ }
91
+ if (change.type === 'update_source' || change.type === 'add_tags') {
92
+ if (!perms.proposeModify) {
93
+ throw new Error(`Extension "${extensionName}" does not have permission to propose modifications. Add permissions.proposeModify = true to the extension.`);
94
+ }
95
+ }
96
+ if (change.type === 'delete_source') {
97
+ if (!perms.proposeDelete) {
98
+ throw new Error(`Extension "${extensionName}" does not have permission to propose deletions. Add permissions.proposeDelete = true to the extension.`);
99
+ }
100
+ }
101
+ return createProposal(extensionName, change);
102
+ };
103
+ }
104
+ async function resolveLoreExtension(mod) {
105
+ const candidate = mod.loreExtension ||
106
+ mod.default;
107
+ if (!candidate) {
108
+ return null;
109
+ }
110
+ if (typeof candidate === 'function') {
111
+ const result = await candidate();
112
+ return result;
113
+ }
114
+ return candidate;
115
+ }
116
+ function validateExtension(extension, logger) {
117
+ if (!extension.name || !extension.version) {
118
+ logger(`[extensions] Skipping extension with missing name/version`);
119
+ return false;
120
+ }
121
+ return true;
122
+ }
123
+ function checkCompatibility(extension, loreVersion, logger) {
124
+ const required = extension.compatibility?.loreVersion;
125
+ if (!required || !loreVersion) {
126
+ return;
127
+ }
128
+ if (required !== loreVersion) {
129
+ logger(`[extensions] Compatibility warning: ${extension.name} requires Lore ${required}, current ${loreVersion}`);
130
+ }
131
+ }
132
+ async function getLoreVersion() {
133
+ const explicit = process.env.LORE_VERSION || process.env.npm_package_version;
134
+ if (explicit) {
135
+ return explicit;
136
+ }
137
+ try {
138
+ const here = path.dirname(fileURLToPath(import.meta.url));
139
+ const packageJsonPath = path.resolve(here, '../../package.json');
140
+ const content = await readFile(packageJsonPath, 'utf-8');
141
+ const parsed = JSON.parse(content);
142
+ return parsed.version;
143
+ }
144
+ catch {
145
+ return undefined;
146
+ }
147
+ }
148
+ export class ExtensionRegistry {
149
+ extensions;
150
+ logger;
151
+ options;
152
+ constructor(extensions, logger, options) {
153
+ this.extensions = extensions;
154
+ this.logger = logger;
155
+ this.options = options;
156
+ }
157
+ listExtensions() {
158
+ return [...this.extensions];
159
+ }
160
+ collectMiddleware() {
161
+ const chain = [];
162
+ for (const loaded of this.extensions) {
163
+ const middleware = loaded.extension.middleware || [];
164
+ for (const entry of middleware) {
165
+ if (!entry?.name) {
166
+ this.logger(`[extensions] Skipping unnamed middleware in ${loaded.extension.name}`);
167
+ continue;
168
+ }
169
+ chain.push(entry);
170
+ }
171
+ }
172
+ return chain;
173
+ }
174
+ collectEventHandlers(type) {
175
+ const handlers = [];
176
+ for (const loaded of this.extensions) {
177
+ const handler = loaded.extension.events?.[type];
178
+ if (handler) {
179
+ handlers.push(handler);
180
+ }
181
+ }
182
+ return handlers;
183
+ }
184
+ async emitEvent(type, payload, context) {
185
+ const event = {
186
+ type,
187
+ payload,
188
+ timestamp: Date.now(),
189
+ };
190
+ const handlers = this.collectEventHandlers(type);
191
+ for (const handler of handlers) {
192
+ try {
193
+ await handler(event, {
194
+ ...context,
195
+ logger: context.logger || this.logger,
196
+ });
197
+ }
198
+ catch (error) {
199
+ this.logger(`[extensions] Event ${type} handler failed: ${error instanceof Error ? error.message : String(error)}`);
200
+ }
201
+ }
202
+ }
203
+ registerCommands(program, context) {
204
+ for (const loaded of this.extensions) {
205
+ const commands = loaded.extension.commands || [];
206
+ for (const command of commands) {
207
+ try {
208
+ command.register(program, {
209
+ ...context,
210
+ logger: context.logger || this.logger,
211
+ });
212
+ }
213
+ catch (error) {
214
+ this.logger(`[extensions] Failed to register command ${command.name} from ${loaded.extension.name}: ${String(error)}`);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ async runHook(hookName, payload, context) {
220
+ for (const loaded of this.extensions) {
221
+ const hook = loaded.extension.hooks?.[hookName];
222
+ if (!hook) {
223
+ continue;
224
+ }
225
+ try {
226
+ await hook(payload, {
227
+ ...context,
228
+ logger: context.logger || this.logger,
229
+ });
230
+ }
231
+ catch (error) {
232
+ this.logger(`[extensions] Hook ${String(hookName)} failed in ${loaded.extension.name}: ${error instanceof Error ? error.message : String(error)}`);
233
+ }
234
+ }
235
+ }
236
+ async reload() {
237
+ this.logger('[extensions] Reloading extensions');
238
+ const cacheBust = `${Date.now()}`;
239
+ const updated = await loadExtensionRegistry({
240
+ ...this.options,
241
+ logger: this.logger,
242
+ cacheBust,
243
+ });
244
+ this.extensions = updated.extensions;
245
+ this.logger('[extensions] Extensions reloaded');
246
+ }
247
+ }
248
+ export async function loadExtensionRegistry(options = {}) {
249
+ const logger = getLogger(options.logger);
250
+ const loreVersion = options.loreVersion ?? (await getLoreVersion());
251
+ const extensionsDir = options.extensionsDir || getExtensionsDir();
252
+ const resolvedOptions = {
253
+ ...options,
254
+ logger,
255
+ loreVersion,
256
+ extensionsDir,
257
+ };
258
+ const config = await loadExtensionConfig();
259
+ const enabledExtensions = config.extensions.filter((ext) => ext.enabled !== false);
260
+ const loadedExtensions = [];
261
+ const require = createRequire(import.meta.url);
262
+ for (const entry of enabledExtensions) {
263
+ const loaded = await loadSingleExtension(entry, extensionsDir, require, loreVersion, logger, options.cacheBust);
264
+ if (!loaded) {
265
+ continue;
266
+ }
267
+ loadedExtensions.push(loaded);
268
+ }
269
+ return new ExtensionRegistry(loadedExtensions, logger, resolvedOptions);
270
+ }
271
+ async function loadSingleExtension(entry, extensionsDir, require, loreVersion, logger, cacheBust) {
272
+ try {
273
+ const resolved = require.resolve(entry.name, { paths: [extensionsDir] });
274
+ const moduleUrl = pathToFileURL(resolved).href;
275
+ const importUrl = cacheBust ? `${moduleUrl}?t=${cacheBust}` : moduleUrl;
276
+ const mod = await import(importUrl);
277
+ const extension = await resolveLoreExtension(mod);
278
+ if (!extension) {
279
+ logger(`[extensions] ${entry.name} does not export a Lore extension`);
280
+ return null;
281
+ }
282
+ if (!validateExtension(extension, logger)) {
283
+ return null;
284
+ }
285
+ checkCompatibility(extension, loreVersion, logger);
286
+ return {
287
+ extension,
288
+ packageName: entry.name,
289
+ modulePath: resolved,
290
+ };
291
+ }
292
+ catch (error) {
293
+ logger(`[extensions] Failed to load ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
294
+ return null;
295
+ }
296
+ }
297
+ let registryPromise = null;
298
+ export async function getExtensionRegistry(options = {}) {
299
+ if (!registryPromise) {
300
+ registryPromise = loadExtensionRegistry(options);
301
+ }
302
+ return registryPromise;
303
+ }
304
+ export function clearExtensionRegistry() {
305
+ registryPromise = null;
306
+ }
307
+ export async function getLoreVersionString() {
308
+ return getLoreVersion();
309
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extension Sandbox
3
+ *
4
+ * Placeholder for potential future sandboxed execution of extension hooks/middleware.
5
+ * Tool handling has been removed - extensions are now event-driven middleware only.
6
+ */
7
+ export interface ExtensionSandboxOptions {
8
+ logger: (message: string) => void;
9
+ timeoutMs?: number;
10
+ }
11
+ export declare class ExtensionSandbox {
12
+ private readonly logger;
13
+ private readonly timeoutMs;
14
+ constructor(options: ExtensionSandboxOptions);
15
+ dispose(): void;
16
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Extension Sandbox
3
+ *
4
+ * Placeholder for potential future sandboxed execution of extension hooks/middleware.
5
+ * Tool handling has been removed - extensions are now event-driven middleware only.
6
+ */
7
+ export class ExtensionSandbox {
8
+ logger;
9
+ timeoutMs;
10
+ constructor(options) {
11
+ this.logger = options.logger;
12
+ this.timeoutMs = options.timeoutMs ?? 30_000;
13
+ }
14
+ dispose() {
15
+ // No-op for now
16
+ }
17
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Lore Extension Types
3
+ */
4
+ import type { Command } from 'commander';
5
+ import type { ResearchPackage } from '../core/types.js';
6
+ import type { ProposedChange, PendingProposal } from './proposals.js';
7
+ export interface ExtensionQueryOptions {
8
+ query?: string;
9
+ project?: string;
10
+ limit?: number;
11
+ sourceType?: string;
12
+ }
13
+ export interface ExtensionQueryResult {
14
+ id: string;
15
+ title: string;
16
+ summary: string;
17
+ content?: string;
18
+ projects: string[];
19
+ participants?: string[];
20
+ created_at: string;
21
+ score?: number;
22
+ }
23
+ export interface ExtensionPermissions {
24
+ read?: boolean;
25
+ proposeCreate?: boolean;
26
+ proposeModify?: boolean;
27
+ proposeDelete?: boolean;
28
+ }
29
+ export interface AskOptions {
30
+ project?: string;
31
+ maxSources?: number;
32
+ }
33
+ export interface ExtensionToolContext {
34
+ mode: 'mcp' | 'cli';
35
+ dataDir?: string;
36
+ dbPath?: string;
37
+ logger?: (message: string) => void;
38
+ query?: (options: ExtensionQueryOptions) => Promise<ExtensionQueryResult[]>;
39
+ ask?: (question: string, options?: AskOptions) => Promise<string>;
40
+ propose?: (change: ProposedChange) => Promise<PendingProposal>;
41
+ }
42
+ export interface ExtensionMiddleware {
43
+ name: string;
44
+ beforeToolCall?: (toolName: string, args: Record<string, unknown>, context: ExtensionToolContext) => Promise<{
45
+ args?: Record<string, unknown>;
46
+ skip?: boolean;
47
+ result?: unknown;
48
+ }>;
49
+ afterToolCall?: (toolName: string, args: Record<string, unknown>, result: unknown, context: ExtensionToolContext) => Promise<unknown>;
50
+ }
51
+ export type LoreEventType = 'search' | 'ingest' | 'sync' | 'tool.call' | 'tool.result' | 'startup' | 'shutdown';
52
+ export interface LoreEvent {
53
+ type: LoreEventType;
54
+ payload: unknown;
55
+ timestamp: number;
56
+ }
57
+ export type EventHandler = (event: LoreEvent, context: ExtensionToolContext) => void | Promise<void>;
58
+ export interface ExtensionCommandContext {
59
+ defaultDataDir: string;
60
+ logger?: (message: string) => void;
61
+ }
62
+ export interface ExtensionCommand {
63
+ name: string;
64
+ description?: string;
65
+ register: (program: Command, context: ExtensionCommandContext) => void | Promise<void>;
66
+ }
67
+ export interface SourceCreatedEvent {
68
+ id: string;
69
+ title: string;
70
+ source_type: string;
71
+ content_type: string;
72
+ created_at: string;
73
+ imported_at: string;
74
+ projects: string[];
75
+ tags: string[];
76
+ source_path?: string;
77
+ content_hash?: string;
78
+ sync_source?: string;
79
+ original_file?: string;
80
+ }
81
+ export interface SyncCompletedEvent {
82
+ newDocs: number;
83
+ updatedDocs: number;
84
+ totalDocs: number;
85
+ project?: string;
86
+ }
87
+ export interface ExtensionHooks {
88
+ /** Called when a new source is indexed */
89
+ onSourceCreated?: (event: SourceCreatedEvent, context: ExtensionToolContext) => void | Promise<void>;
90
+ /** Called when research completes */
91
+ onResearchCompleted?: (result: ResearchPackage, context: ExtensionToolContext) => void | Promise<void>;
92
+ /** Called when sync completes */
93
+ onSyncCompleted?: (event: SyncCompletedEvent, context: ExtensionToolContext) => void | Promise<void>;
94
+ }
95
+ export interface ComponentDefinition {
96
+ id: string;
97
+ description?: string;
98
+ }
99
+ export interface ExtensionCompatibility {
100
+ loreVersion?: string;
101
+ }
102
+ export interface LoreExtension {
103
+ name: string;
104
+ version: string;
105
+ compatibility?: ExtensionCompatibility;
106
+ permissions?: ExtensionPermissions;
107
+ commands?: ExtensionCommand[];
108
+ hooks?: ExtensionHooks;
109
+ components?: ComponentDefinition[];
110
+ middleware?: ExtensionMiddleware[];
111
+ events?: {
112
+ [K in LoreEventType]?: EventHandler;
113
+ };
114
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Lore Extension Types
3
+ */
4
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { parentPort, workerData } from 'worker_threads';
2
+ import { pathToFileURL } from 'url';
3
+ const initData = workerData;
4
+ function getErrorMessage(error) {
5
+ if (error instanceof Error) {
6
+ return error.message;
7
+ }
8
+ return String(error);
9
+ }
10
+ async function resolveLoreExtension(mod) {
11
+ const candidate = mod.loreExtension ||
12
+ mod.default;
13
+ if (!candidate) {
14
+ return null;
15
+ }
16
+ if (typeof candidate === 'function') {
17
+ const result = await candidate();
18
+ return result;
19
+ }
20
+ return candidate;
21
+ }
22
+ let extension = null;
23
+ let extensionLoadError = null;
24
+ const extensionReady = (async () => {
25
+ try {
26
+ const moduleUrl = pathToFileURL(initData.modulePath).href;
27
+ const importUrl = initData.cacheBust ? `${moduleUrl}?t=${initData.cacheBust}` : moduleUrl;
28
+ const mod = await import(importUrl);
29
+ const resolved = await resolveLoreExtension(mod);
30
+ if (!resolved) {
31
+ throw new Error(`${initData.modulePath} does not export a Lore extension`);
32
+ }
33
+ extension = resolved;
34
+ }
35
+ catch (error) {
36
+ extensionLoadError = getErrorMessage(error);
37
+ console.error(`[extensions] Worker failed to load extension: ${extensionLoadError}`);
38
+ }
39
+ })();
40
+ if (!parentPort) {
41
+ throw new Error('Worker must be started with a parent port');
42
+ }
43
+ // Worker is now primarily used for sandboxed hook/middleware execution
44
+ // Tool handling has been removed - extensions are event-driven middleware only
45
+ const port = parentPort;
46
+ port.on('message', async () => {
47
+ await extensionReady;
48
+ // No-op for now - hooks and middleware are executed in the main thread
49
+ });
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lore CLI
4
+ *
5
+ * Research knowledge repository with semantic search and citations.
6
+ *
7
+ * Commands:
8
+ * - sync: Sync and manage the knowledge repository (daemon, watch, sources, import)
9
+ * - search: Search the knowledge base
10
+ * - research: Deep AI-powered research
11
+ * - browse: Interactive TUI browser
12
+ * - docs: Document CRUD (list, get, create, delete)
13
+ * - projects: Project management (list, archive, delete)
14
+ * - init: Initialize a data repository
15
+ * - serve: Start the MCP server
16
+ */
17
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lore CLI
4
+ *
5
+ * Research knowledge repository with semantic search and citations.
6
+ *
7
+ * Commands:
8
+ * - sync: Sync and manage the knowledge repository (daemon, watch, sources, import)
9
+ * - search: Search the knowledge base
10
+ * - research: Deep AI-powered research
11
+ * - browse: Interactive TUI browser
12
+ * - docs: Document CRUD (list, get, create, delete)
13
+ * - projects: Project management (list, archive, delete)
14
+ * - init: Initialize a data repository
15
+ * - serve: Start the MCP server
16
+ */
17
+ // Load environment variables from .env files
18
+ // .env.local takes precedence over .env
19
+ import { existsSync, readFileSync } from 'fs';
20
+ import { parse } from 'dotenv';
21
+ import { Command } from 'commander';
22
+ import { registerSyncCommand } from './cli/commands/sync.js';
23
+ import { registerSearchCommand } from './cli/commands/search.js';
24
+ import { registerMiscCommands } from './cli/commands/misc.js';
25
+ import { registerDocsCommand } from './cli/commands/docs.js';
26
+ import { registerProjectsCommand } from './cli/commands/projects.js';
27
+ import { registerExtensionCommands } from './cli/commands/extensions.js';
28
+ import { registerPendingCommand } from './cli/commands/pending.js';
29
+ import { registerAskCommand } from './cli/commands/ask.js';
30
+ import { registerAuthCommands } from './cli/commands/auth.js';
31
+ import { registerSkillsCommand } from './cli/commands/skills.js';
32
+ import { getExtensionRegistry, getLoreVersionString } from './extensions/registry.js';
33
+ import { bridgeConfigToEnv } from './core/config.js';
34
+ import { expandPath } from './sync/config.js';
35
+ // Load .env files silently (without the v17 logging)
36
+ function loadEnvFile(filePath, override = false) {
37
+ if (!existsSync(filePath))
38
+ return;
39
+ try {
40
+ const content = readFileSync(filePath, 'utf-8');
41
+ const parsed = parse(content);
42
+ for (const [key, value] of Object.entries(parsed)) {
43
+ if (override || process.env[key] === undefined) {
44
+ process.env[key] = value;
45
+ }
46
+ }
47
+ }
48
+ catch {
49
+ // Silently ignore errors
50
+ }
51
+ }
52
+ // Load .env first, then .env.local (overrides)
53
+ loadEnvFile('.env');
54
+ loadEnvFile('.env.local', true);
55
+ // Bridge config.json values into process.env (env vars take precedence)
56
+ try {
57
+ await bridgeConfigToEnv();
58
+ }
59
+ catch {
60
+ // Config not set up yet — fine, user may be running `lore setup`
61
+ }
62
+ // Default data directory
63
+ const DEFAULT_DATA_DIR = expandPath(process.env.LORE_DATA_DIR || '~/.lore');
64
+ // Create program
65
+ const program = new Command();
66
+ program
67
+ .name('lore')
68
+ .description('Research knowledge repository with semantic search and citations')
69
+ .version((await getLoreVersionString()) || '0.1.0');
70
+ // Register all commands
71
+ registerSyncCommand(program, DEFAULT_DATA_DIR);
72
+ registerSearchCommand(program, DEFAULT_DATA_DIR);
73
+ registerDocsCommand(program, DEFAULT_DATA_DIR);
74
+ registerProjectsCommand(program, DEFAULT_DATA_DIR);
75
+ registerMiscCommands(program, DEFAULT_DATA_DIR);
76
+ registerAskCommand(program, DEFAULT_DATA_DIR);
77
+ registerAuthCommands(program);
78
+ registerSkillsCommand(program);
79
+ // Extension system — hidden from top-level help for now
80
+ const extensionCmd = registerExtensionCommands(program);
81
+ extensionCmd._hidden = true;
82
+ registerPendingCommand(extensionCmd, DEFAULT_DATA_DIR);
83
+ try {
84
+ const extensionRegistry = await getExtensionRegistry({
85
+ logger: (message) => console.error(message),
86
+ });
87
+ extensionRegistry.registerCommands(program, {
88
+ defaultDataDir: DEFAULT_DATA_DIR,
89
+ });
90
+ }
91
+ catch {
92
+ // Extensions not loaded — fine for initial release
93
+ }
94
+ // Global error handler — show friendly messages instead of stack traces
95
+ process.on('uncaughtException', (error) => {
96
+ console.error(`\nError: ${error.message}`);
97
+ process.exit(1);
98
+ });
99
+ process.on('unhandledRejection', (reason) => {
100
+ const message = reason instanceof Error ? reason.message : String(reason);
101
+ console.error(`\nError: ${message}`);
102
+ process.exit(1);
103
+ });
104
+ // Parse and run
105
+ program.parse();
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Archive Project Handler - Mark a project as archived
3
+ *
4
+ * Archived projects are excluded from search by default but preserved for history.
5
+ * This is a human-triggered curation action, not automatic.
6
+ * Auto-pushes to git remote if configured.
7
+ */
8
+ interface ArchivedProject {
9
+ project: string;
10
+ archived_at: string;
11
+ reason?: string;
12
+ successor_project?: string;
13
+ }
14
+ interface ArchiveProjectArgs {
15
+ project: string;
16
+ reason?: string;
17
+ successor_project?: string;
18
+ }
19
+ interface ArchiveProjectResult {
20
+ success: boolean;
21
+ project: string;
22
+ archived_at: string;
23
+ reason?: string;
24
+ successor_project?: string;
25
+ sources_affected: number;
26
+ synced?: boolean;
27
+ error?: string;
28
+ }
29
+ /**
30
+ * Load archived projects list
31
+ */
32
+ export declare function loadArchivedProjects(dataDir: string): Promise<ArchivedProject[]>;
33
+ /**
34
+ * Check if a project is archived
35
+ */
36
+ export declare function isProjectArchived(dataDir: string, project: string): Promise<boolean>;
37
+ /**
38
+ * Get archived project info
39
+ */
40
+ export declare function getArchivedProjectInfo(dataDir: string, project: string): Promise<ArchivedProject | null>;
41
+ export declare function handleArchiveProject(dbPath: string, dataDir: string, args: ArchiveProjectArgs, options?: {
42
+ autoPush?: boolean;
43
+ }): Promise<ArchiveProjectResult>;
44
+ /**
45
+ * Unarchive a project (restore to active)
46
+ */
47
+ export declare function handleUnarchiveProject(dataDir: string, project: string): Promise<{
48
+ success: boolean;
49
+ error?: string;
50
+ }>;
51
+ export {};