@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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles extension listing and display.
|
|
5
|
+
*/
|
|
6
|
+
import type { BrowserState, UIComponents } from './browse-types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Show the extensions list view
|
|
9
|
+
*/
|
|
10
|
+
export declare function showExtensions(state: BrowserState, ui: UIComponents): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Update extension selection and refresh display
|
|
13
|
+
*/
|
|
14
|
+
export declare function selectExtension(state: BrowserState, ui: UIComponents): void;
|
|
15
|
+
/**
|
|
16
|
+
* Toggle the selected extension's enabled state
|
|
17
|
+
*/
|
|
18
|
+
export declare function toggleExtension(state: BrowserState, ui: UIComponents): Promise<void>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles extension listing and display.
|
|
5
|
+
*/
|
|
6
|
+
import { renderExtensionsList, renderExtensionDetails } from './browse-render-extensions.js';
|
|
7
|
+
import { getExtensionRegistry } from '../extensions/registry.js';
|
|
8
|
+
import { loadExtensionConfig, addExtensionToConfig } from '../extensions/config.js';
|
|
9
|
+
/**
|
|
10
|
+
* Show the extensions list view
|
|
11
|
+
*/
|
|
12
|
+
export async function showExtensions(state, ui) {
|
|
13
|
+
state.mode = 'extensions';
|
|
14
|
+
ui.fullViewPane.hide();
|
|
15
|
+
ui.listPane.show();
|
|
16
|
+
ui.previewPane.show();
|
|
17
|
+
ui.listTitle.setContent(' Extensions');
|
|
18
|
+
ui.previewTitle.setContent(' Extension Details');
|
|
19
|
+
ui.footer.setContent(' j/k: navigate Enter: toggle Esc: back');
|
|
20
|
+
ui.statusBar.setContent(' Loading extensions...');
|
|
21
|
+
ui.screen.render();
|
|
22
|
+
try {
|
|
23
|
+
const registry = await getExtensionRegistry();
|
|
24
|
+
const config = await loadExtensionConfig();
|
|
25
|
+
const loaded = registry.listExtensions();
|
|
26
|
+
// Build extension info list
|
|
27
|
+
const extensionsList = loaded.map((ext) => {
|
|
28
|
+
const configEntry = config.extensions.find((e) => e.name === ext.packageName);
|
|
29
|
+
const hooks = [];
|
|
30
|
+
if (ext.extension.hooks?.onSourceCreated)
|
|
31
|
+
hooks.push('onSourceCreated');
|
|
32
|
+
if (ext.extension.hooks?.onResearchCompleted)
|
|
33
|
+
hooks.push('onResearchCompleted');
|
|
34
|
+
if (ext.extension.hooks?.onSyncCompleted)
|
|
35
|
+
hooks.push('onSyncCompleted');
|
|
36
|
+
const middleware = (ext.extension.middleware || []).map((m) => m.name);
|
|
37
|
+
const commands = (ext.extension.commands || []).map((c) => c.name);
|
|
38
|
+
return {
|
|
39
|
+
name: ext.extension.name,
|
|
40
|
+
version: ext.extension.version,
|
|
41
|
+
packageName: ext.packageName,
|
|
42
|
+
enabled: configEntry?.enabled !== false,
|
|
43
|
+
hooks,
|
|
44
|
+
middleware,
|
|
45
|
+
commands,
|
|
46
|
+
permissions: ext.extension.permissions,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
// Also include disabled extensions from config that aren't loaded
|
|
50
|
+
for (const configEntry of config.extensions) {
|
|
51
|
+
if (!extensionsList.some((e) => e.packageName === configEntry.name)) {
|
|
52
|
+
extensionsList.push({
|
|
53
|
+
name: configEntry.name,
|
|
54
|
+
version: configEntry.version || 'unknown',
|
|
55
|
+
packageName: configEntry.name,
|
|
56
|
+
enabled: false,
|
|
57
|
+
hooks: [],
|
|
58
|
+
middleware: [],
|
|
59
|
+
commands: [],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
state.extensionsList = extensionsList;
|
|
64
|
+
state.selectedExtensionIndex = 0;
|
|
65
|
+
ui.statusBar.setContent(` ${extensionsList.length} extension${extensionsList.length !== 1 ? 's' : ''}`);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
state.extensionsList = [];
|
|
69
|
+
state.selectedExtensionIndex = 0;
|
|
70
|
+
ui.statusBar.setContent(` {red-fg}Failed to load extensions: ${error}{/red-fg}`);
|
|
71
|
+
}
|
|
72
|
+
renderExtensionsList(ui, state);
|
|
73
|
+
renderExtensionDetails(ui, state);
|
|
74
|
+
ui.listContent.focus();
|
|
75
|
+
ui.screen.render();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update extension selection and refresh display
|
|
79
|
+
*/
|
|
80
|
+
export function selectExtension(state, ui) {
|
|
81
|
+
renderExtensionsList(ui, state);
|
|
82
|
+
renderExtensionDetails(ui, state);
|
|
83
|
+
ui.screen.render();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Toggle the selected extension's enabled state
|
|
87
|
+
*/
|
|
88
|
+
export async function toggleExtension(state, ui) {
|
|
89
|
+
const ext = state.extensionsList[state.selectedExtensionIndex];
|
|
90
|
+
if (!ext)
|
|
91
|
+
return;
|
|
92
|
+
const newEnabled = !ext.enabled;
|
|
93
|
+
ui.statusBar.setContent(` ${newEnabled ? 'Enabling' : 'Disabling'} ${ext.name}...`);
|
|
94
|
+
ui.screen.render();
|
|
95
|
+
try {
|
|
96
|
+
await addExtensionToConfig(ext.packageName, ext.version, newEnabled);
|
|
97
|
+
ext.enabled = newEnabled;
|
|
98
|
+
ui.statusBar.setContent(` ${newEnabled ? 'Enabled' : 'Disabled'} ${ext.name} (restart to apply)`);
|
|
99
|
+
renderExtensionsList(ui, state);
|
|
100
|
+
renderExtensionDetails(ui, state);
|
|
101
|
+
ui.screen.render();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
ui.statusBar.setContent(` {red-fg}Failed: ${error}{/red-fg}`);
|
|
105
|
+
ui.screen.render();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending proposal handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles viewing, approving, and rejecting extension proposals.
|
|
5
|
+
*/
|
|
6
|
+
import type { BrowserState, UIComponents } from './browse-types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Show the pending proposals view
|
|
9
|
+
*/
|
|
10
|
+
export declare function showPendingView(state: BrowserState, ui: UIComponents, dbPath: string, dataDir: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Refresh the pending proposals list
|
|
13
|
+
*/
|
|
14
|
+
export declare function refreshPendingView(state: BrowserState, ui: UIComponents): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Approve the currently selected proposal
|
|
17
|
+
*/
|
|
18
|
+
export declare function approveSelectedProposal(state: BrowserState, ui: UIComponents, dbPath: string, dataDir: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Reject the currently selected proposal
|
|
21
|
+
*/
|
|
22
|
+
export declare function rejectSelectedProposal(state: BrowserState, ui: UIComponents): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending proposal handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles viewing, approving, and rejecting extension proposals.
|
|
5
|
+
*/
|
|
6
|
+
import { renderPendingList, renderPendingPreview, updateStatus } from './browse-render.js';
|
|
7
|
+
import { listPendingProposals, approveProposal, rejectProposal } from '../extensions/proposals.js';
|
|
8
|
+
/**
|
|
9
|
+
* Show the pending proposals view
|
|
10
|
+
*/
|
|
11
|
+
export async function showPendingView(state, ui, dbPath, dataDir) {
|
|
12
|
+
ui.statusBar.setContent(' Loading pending proposals...');
|
|
13
|
+
ui.screen.render();
|
|
14
|
+
state.pendingList = await listPendingProposals();
|
|
15
|
+
state.selectedPendingIndex = 0;
|
|
16
|
+
state.mode = 'pending';
|
|
17
|
+
state.pendingConfirmAction = null;
|
|
18
|
+
ui.listTitle.setContent(' Pending');
|
|
19
|
+
ui.previewTitle.setContent(' Proposal');
|
|
20
|
+
ui.footer.setContent(' ↑↓ Navigate │ a Approve │ x Reject │ Esc Back');
|
|
21
|
+
renderPendingList(ui, state);
|
|
22
|
+
renderPendingPreview(ui, state);
|
|
23
|
+
updateStatus(ui, state);
|
|
24
|
+
ui.screen.render();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Refresh the pending proposals list
|
|
28
|
+
*/
|
|
29
|
+
export async function refreshPendingView(state, ui) {
|
|
30
|
+
state.pendingList = await listPendingProposals();
|
|
31
|
+
if (state.selectedPendingIndex >= state.pendingList.length) {
|
|
32
|
+
state.selectedPendingIndex = Math.max(0, state.pendingList.length - 1);
|
|
33
|
+
}
|
|
34
|
+
renderPendingList(ui, state);
|
|
35
|
+
renderPendingPreview(ui, state);
|
|
36
|
+
updateStatus(ui, state);
|
|
37
|
+
ui.screen.render();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Show confirmation dialog for pending action
|
|
41
|
+
*/
|
|
42
|
+
function confirmPendingAction(state, ui, prompt, onConfirm) {
|
|
43
|
+
state.pendingConfirmAction = prompt.includes('Reject') ? 'reject' : 'approve';
|
|
44
|
+
ui.statusBar.setContent(` ${prompt} (y/n)`);
|
|
45
|
+
ui.screen.render();
|
|
46
|
+
const handler = async (_ch, key) => {
|
|
47
|
+
if (!key?.name)
|
|
48
|
+
return;
|
|
49
|
+
if (key.name === 'y') {
|
|
50
|
+
ui.screen.removeListener('keypress', handler);
|
|
51
|
+
state.pendingConfirmAction = null;
|
|
52
|
+
await onConfirm();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (key.name === 'n' || key.name === 'escape') {
|
|
56
|
+
ui.screen.removeListener('keypress', handler);
|
|
57
|
+
state.pendingConfirmAction = null;
|
|
58
|
+
updateStatus(ui, state);
|
|
59
|
+
renderPendingPreview(ui, state);
|
|
60
|
+
ui.screen.render();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
ui.screen.on('keypress', handler);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Approve the currently selected proposal
|
|
67
|
+
*/
|
|
68
|
+
export function approveSelectedProposal(state, ui, dbPath, dataDir) {
|
|
69
|
+
const proposal = state.pendingList[state.selectedPendingIndex];
|
|
70
|
+
if (!proposal || proposal.status !== 'pending')
|
|
71
|
+
return;
|
|
72
|
+
confirmPendingAction(state, ui, 'Approve proposal', async () => {
|
|
73
|
+
await approveProposal(proposal.id, dbPath, dataDir);
|
|
74
|
+
await refreshPendingView(state, ui);
|
|
75
|
+
ui.statusBar.setContent(` {green-fg}Approved ${proposal.id}{/green-fg}`);
|
|
76
|
+
ui.screen.render();
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
updateStatus(ui, state);
|
|
79
|
+
ui.screen.render();
|
|
80
|
+
}, 1200);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reject the currently selected proposal
|
|
85
|
+
*/
|
|
86
|
+
export function rejectSelectedProposal(state, ui) {
|
|
87
|
+
const proposal = state.pendingList[state.selectedPendingIndex];
|
|
88
|
+
if (!proposal || proposal.status !== 'pending')
|
|
89
|
+
return;
|
|
90
|
+
confirmPendingAction(state, ui, 'Reject proposal', async () => {
|
|
91
|
+
await rejectProposal(proposal.id, 'Rejected in TUI');
|
|
92
|
+
await refreshPendingView(state, ui);
|
|
93
|
+
ui.statusBar.setContent(` {yellow-fg}Rejected ${proposal.id}{/yellow-fg}`);
|
|
94
|
+
ui.screen.render();
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
updateStatus(ui, state);
|
|
97
|
+
ui.screen.render();
|
|
98
|
+
}, 1200);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Spawns the full agentic research mode for comprehensive,
|
|
5
|
+
* iterative research with citations.
|
|
6
|
+
*
|
|
7
|
+
* Supports slash commands and maintains research history.
|
|
8
|
+
*
|
|
9
|
+
* Slash commands:
|
|
10
|
+
* /project <name> or /p <name> - Set project filter
|
|
11
|
+
* /type <type> or /t <type> - Set content type filter
|
|
12
|
+
* /clear - Clear all filters
|
|
13
|
+
* /new - Clear research history
|
|
14
|
+
* /help or /? - Show available commands
|
|
15
|
+
*/
|
|
16
|
+
import type { BrowserState, UIComponents } from './browse-types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Enter research mode - show input for research task
|
|
19
|
+
*/
|
|
20
|
+
export declare function enterResearchMode(state: BrowserState, ui: UIComponents): void;
|
|
21
|
+
/**
|
|
22
|
+
* Exit research mode - return to list
|
|
23
|
+
*/
|
|
24
|
+
export declare function exitResearchMode(state: BrowserState, ui: UIComponents): void;
|
|
25
|
+
/**
|
|
26
|
+
* Prompt for next input after research completes
|
|
27
|
+
*/
|
|
28
|
+
export declare function promptForFollowUpResearch(state: BrowserState, ui: UIComponents): void;
|
|
29
|
+
/**
|
|
30
|
+
* Execute the research task
|
|
31
|
+
*/
|
|
32
|
+
export declare function executeResearch(state: BrowserState, ui: UIComponents, dbPath: string, dataDir: string, query: string): Promise<void>;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Spawns the full agentic research mode for comprehensive,
|
|
5
|
+
* iterative research with citations.
|
|
6
|
+
*
|
|
7
|
+
* Supports slash commands and maintains research history.
|
|
8
|
+
*
|
|
9
|
+
* Slash commands:
|
|
10
|
+
* /project <name> or /p <name> - Set project filter
|
|
11
|
+
* /type <type> or /t <type> - Set content type filter
|
|
12
|
+
* /clear - Clear all filters
|
|
13
|
+
* /new - Clear research history
|
|
14
|
+
* /help or /? - Show available commands
|
|
15
|
+
*/
|
|
16
|
+
import { handleResearch } from '../mcp/handlers/research.js';
|
|
17
|
+
import { showProjectPicker, showContentTypeFilter } from './browse-handlers.js';
|
|
18
|
+
const CONTENT_TYPES = ['interview', 'meeting', 'conversation', 'document', 'note', 'analysis'];
|
|
19
|
+
/**
|
|
20
|
+
* Build the filter display string
|
|
21
|
+
*/
|
|
22
|
+
function getFilterDisplay(state) {
|
|
23
|
+
const filters = [];
|
|
24
|
+
if (state.currentProject)
|
|
25
|
+
filters.push(`project: ${state.currentProject}`);
|
|
26
|
+
if (state.currentContentType)
|
|
27
|
+
filters.push(`type: ${state.currentContentType}`);
|
|
28
|
+
const filterInfo = filters.length > 0
|
|
29
|
+
? `{yellow-fg}Scope: ${filters.join(', ')}{/yellow-fg}`
|
|
30
|
+
: '{blue-fg}No filters{/blue-fg}';
|
|
31
|
+
const footerNote = filters.length > 0
|
|
32
|
+
? `{yellow-fg}${filters.join(', ')}{/yellow-fg}`
|
|
33
|
+
: '{blue-fg}all sources{/blue-fg}';
|
|
34
|
+
return { filters, filterInfo, footerNote };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Render the research pane with history
|
|
38
|
+
*/
|
|
39
|
+
function renderResearchPane(state, ui) {
|
|
40
|
+
const { filterInfo } = getFilterDisplay(state);
|
|
41
|
+
const lines = [];
|
|
42
|
+
// Show filter status at top
|
|
43
|
+
lines.push(`${filterInfo} {blue-fg}│{/blue-fg} {white-fg}/help{/white-fg} for commands {blue-fg}│{/blue-fg} {white-fg}/new{/white-fg} to start fresh`);
|
|
44
|
+
lines.push('');
|
|
45
|
+
// Show research history
|
|
46
|
+
if (state.researchHistory.length > 0) {
|
|
47
|
+
for (const item of state.researchHistory) {
|
|
48
|
+
lines.push(`{cyan-fg}Research:{/cyan-fg} ${item.query}`);
|
|
49
|
+
lines.push('');
|
|
50
|
+
// Escape blessed tags in summary
|
|
51
|
+
const escaped = item.summary
|
|
52
|
+
.replace(/\{/g, '\\{')
|
|
53
|
+
.replace(/\}/g, '\\}');
|
|
54
|
+
lines.push(escaped);
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push('{blue-fg}───────────────────────────────────────{/blue-fg}');
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Show current research if running
|
|
61
|
+
if (state.researchRunning) {
|
|
62
|
+
lines.push(`{cyan-fg}Research:{/cyan-fg} ${state.researchQuery}`);
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('{yellow-fg}Researching... (agent is exploring sources){/yellow-fg}');
|
|
65
|
+
}
|
|
66
|
+
else if (state.researchHistory.length === 0) {
|
|
67
|
+
lines.push('{blue-fg}Enter a research task to begin comprehensive analysis...{/blue-fg}');
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push('{blue-fg}The research agent will iteratively explore sources,{/blue-fg}');
|
|
70
|
+
lines.push('{blue-fg}cross-reference findings, and synthesize results.{/blue-fg}');
|
|
71
|
+
}
|
|
72
|
+
ui.askPane.setContent(lines.join('\n'));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Update the footer for research mode
|
|
76
|
+
*/
|
|
77
|
+
function updateResearchFooter(ui, state, status) {
|
|
78
|
+
const { footerNote } = getFilterDisplay(state);
|
|
79
|
+
if (status) {
|
|
80
|
+
ui.footer.setContent(` ${status} │ Scope: ${footerNote}`);
|
|
81
|
+
}
|
|
82
|
+
else if (state.researchRunning) {
|
|
83
|
+
ui.footer.setContent(` Researching... │ Scope: ${footerNote}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const historyNote = state.researchHistory.length > 0 ? `${state.researchHistory.length} tasks │ ` : '';
|
|
87
|
+
ui.footer.setContent(` ${historyNote}Enter: Research │ Esc: Back │ Scope: ${footerNote}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Enter research mode - show input for research task
|
|
92
|
+
*/
|
|
93
|
+
export function enterResearchMode(state, ui) {
|
|
94
|
+
state.mode = 'research';
|
|
95
|
+
state.researchQuery = '';
|
|
96
|
+
state.researchRunning = false;
|
|
97
|
+
// Don't clear history - allow viewing previous research
|
|
98
|
+
// Hide list/preview panes
|
|
99
|
+
ui.listPane.hide();
|
|
100
|
+
ui.previewPane.hide();
|
|
101
|
+
// Reuse ask UI components
|
|
102
|
+
ui.askInput.show();
|
|
103
|
+
ui.askInput.setValue('');
|
|
104
|
+
ui.askPane.show();
|
|
105
|
+
ui.askPane.setLabel(' Research Agent ');
|
|
106
|
+
renderResearchPane(state, ui);
|
|
107
|
+
updateResearchFooter(ui, state);
|
|
108
|
+
ui.askInput.focus();
|
|
109
|
+
ui.askInput.readInput();
|
|
110
|
+
ui.screen.render();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Exit research mode - return to list
|
|
114
|
+
*/
|
|
115
|
+
export function exitResearchMode(state, ui) {
|
|
116
|
+
state.mode = 'list';
|
|
117
|
+
state.researchQuery = '';
|
|
118
|
+
state.researchRunning = false;
|
|
119
|
+
state.researchResponse = '';
|
|
120
|
+
// Keep researchHistory for when they come back
|
|
121
|
+
// Clear research-mode filters so they don't affect list operations
|
|
122
|
+
state.currentProject = undefined;
|
|
123
|
+
state.currentContentType = undefined;
|
|
124
|
+
ui.askInput.hide();
|
|
125
|
+
ui.askInput.setValue('');
|
|
126
|
+
ui.askPane.hide();
|
|
127
|
+
ui.askPane.setLabel(' Response ');
|
|
128
|
+
ui.listPane.show();
|
|
129
|
+
ui.previewPane.show();
|
|
130
|
+
ui.footer.setContent(' j/k Nav │ / Search │ a Ask │ R Research │ p Proj │ c Type │ m Move │ i Edit │ Esc Quit │ ? Help');
|
|
131
|
+
ui.listContent.focus();
|
|
132
|
+
ui.screen.render();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Show help for slash commands
|
|
136
|
+
*/
|
|
137
|
+
function showCommandHelp(ui, state) {
|
|
138
|
+
const helpText = `{bold}Slash Commands:{/bold}
|
|
139
|
+
|
|
140
|
+
/p or /project Show project picker
|
|
141
|
+
/p <name> Set project filter directly
|
|
142
|
+
/t or /type Show content type picker
|
|
143
|
+
/t <type> Set type filter directly
|
|
144
|
+
(interview, meeting, conversation,
|
|
145
|
+
document, note, analysis)
|
|
146
|
+
/clear Clear all filters
|
|
147
|
+
/new Clear research history
|
|
148
|
+
/help or /? Show this help
|
|
149
|
+
|
|
150
|
+
{bold}Current filters:{/bold}
|
|
151
|
+
Project: ${state.currentProject || '(none)'}
|
|
152
|
+
Type: ${state.currentContentType || '(none)'}
|
|
153
|
+
|
|
154
|
+
{blue-fg}Press Enter to continue...{/blue-fg}`;
|
|
155
|
+
ui.askPane.setContent(helpText);
|
|
156
|
+
ui.screen.render();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Handle slash commands
|
|
160
|
+
* Returns true if input was a command (handled), false if it's a research task
|
|
161
|
+
*/
|
|
162
|
+
async function handleSlashCommand(input, state, ui, dbPath) {
|
|
163
|
+
const trimmed = input.trim();
|
|
164
|
+
if (!trimmed.startsWith('/'))
|
|
165
|
+
return false;
|
|
166
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
167
|
+
const cmd = parts[0].toLowerCase();
|
|
168
|
+
const arg = parts.slice(1).join(' ');
|
|
169
|
+
switch (cmd) {
|
|
170
|
+
case 'help':
|
|
171
|
+
case '?':
|
|
172
|
+
showCommandHelp(ui, state);
|
|
173
|
+
return true;
|
|
174
|
+
case 'new':
|
|
175
|
+
state.researchHistory = [];
|
|
176
|
+
state.researchResponse = '';
|
|
177
|
+
renderResearchPane(state, ui);
|
|
178
|
+
updateResearchFooter(ui, state, 'Research history cleared');
|
|
179
|
+
ui.screen.render();
|
|
180
|
+
return true;
|
|
181
|
+
case 'clear':
|
|
182
|
+
state.currentProject = undefined;
|
|
183
|
+
state.currentContentType = undefined;
|
|
184
|
+
renderResearchPane(state, ui);
|
|
185
|
+
updateResearchFooter(ui, state, 'Filters cleared');
|
|
186
|
+
ui.screen.render();
|
|
187
|
+
return true;
|
|
188
|
+
case 'p':
|
|
189
|
+
case 'project': {
|
|
190
|
+
if (!arg) {
|
|
191
|
+
// Show interactive project picker
|
|
192
|
+
state.pickerReturnMode = 'research';
|
|
193
|
+
ui.askInput.hide();
|
|
194
|
+
ui.askPane.hide();
|
|
195
|
+
showProjectPicker(state, ui, dbPath);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
state.currentProject = arg;
|
|
199
|
+
renderResearchPane(state, ui);
|
|
200
|
+
updateResearchFooter(ui, state, `Project set to: ${arg}`);
|
|
201
|
+
ui.screen.render();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
case 't':
|
|
205
|
+
case 'type': {
|
|
206
|
+
if (!arg) {
|
|
207
|
+
// Show interactive content type picker
|
|
208
|
+
state.pickerReturnMode = 'research';
|
|
209
|
+
ui.askInput.hide();
|
|
210
|
+
ui.askPane.hide();
|
|
211
|
+
showContentTypeFilter(state, ui);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (!CONTENT_TYPES.includes(arg)) {
|
|
215
|
+
ui.askPane.setContent(`{red-fg}Unknown type: ${arg}{/red-fg}\n\nAvailable: ${CONTENT_TYPES.join(', ')}`);
|
|
216
|
+
ui.screen.render();
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
state.currentContentType = arg;
|
|
220
|
+
renderResearchPane(state, ui);
|
|
221
|
+
updateResearchFooter(ui, state, `Type set to: ${arg}`);
|
|
222
|
+
ui.screen.render();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
ui.askPane.setContent(`{red-fg}Unknown command: /${cmd}{/red-fg}\n\nType /help for available commands.`);
|
|
227
|
+
ui.screen.render();
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Prompt for next input after research completes
|
|
233
|
+
*/
|
|
234
|
+
export function promptForFollowUpResearch(state, ui) {
|
|
235
|
+
ui.askInput.show();
|
|
236
|
+
ui.askInput.setValue('');
|
|
237
|
+
ui.askInput.focus();
|
|
238
|
+
ui.askInput.readInput();
|
|
239
|
+
updateResearchFooter(ui, state);
|
|
240
|
+
ui.screen.render();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Format research package for display
|
|
244
|
+
*/
|
|
245
|
+
function formatResearchResult(result) {
|
|
246
|
+
const lines = [];
|
|
247
|
+
// Summary
|
|
248
|
+
lines.push('{bold}Summary{/bold}');
|
|
249
|
+
lines.push('');
|
|
250
|
+
lines.push(result.summary);
|
|
251
|
+
lines.push('');
|
|
252
|
+
// Key findings
|
|
253
|
+
if (result.key_findings?.length) {
|
|
254
|
+
lines.push('{bold}Key Findings{/bold}');
|
|
255
|
+
for (const finding of result.key_findings) {
|
|
256
|
+
lines.push(`• ${finding}`);
|
|
257
|
+
}
|
|
258
|
+
lines.push('');
|
|
259
|
+
}
|
|
260
|
+
// Conflicts resolved
|
|
261
|
+
if (result.conflicts_resolved?.length) {
|
|
262
|
+
lines.push('{bold}Conflicts Resolved{/bold}');
|
|
263
|
+
for (const conflict of result.conflicts_resolved) {
|
|
264
|
+
lines.push(`• ${conflict}`);
|
|
265
|
+
}
|
|
266
|
+
lines.push('');
|
|
267
|
+
}
|
|
268
|
+
// Supporting quotes (limit to 5 for brevity in history)
|
|
269
|
+
if (result.supporting_quotes?.length) {
|
|
270
|
+
lines.push('{bold}Key Quotes{/bold}');
|
|
271
|
+
for (const quote of result.supporting_quotes.slice(0, 5)) {
|
|
272
|
+
const speaker = quote.speaker_name || quote.speaker || 'Unknown';
|
|
273
|
+
lines.push(`"${quote.text}"`);
|
|
274
|
+
lines.push(` — ${speaker}${quote.citation?.context ? ` (${quote.citation.context})` : ''}`);
|
|
275
|
+
}
|
|
276
|
+
if (result.supporting_quotes.length > 5) {
|
|
277
|
+
lines.push(` ... and ${result.supporting_quotes.length - 5} more quotes`);
|
|
278
|
+
}
|
|
279
|
+
lines.push('');
|
|
280
|
+
}
|
|
281
|
+
// Sources consulted
|
|
282
|
+
if (result.sources_consulted?.length) {
|
|
283
|
+
lines.push('{bold}Sources ({/bold}' + result.sources_consulted.length + '{bold}){/bold}');
|
|
284
|
+
for (const source of result.sources_consulted.slice(0, 5)) {
|
|
285
|
+
lines.push(`• ${source.title}`);
|
|
286
|
+
}
|
|
287
|
+
if (result.sources_consulted.length > 5) {
|
|
288
|
+
lines.push(` ... and ${result.sources_consulted.length - 5} more`);
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
// Gaps identified
|
|
293
|
+
if (result.gaps_identified?.length) {
|
|
294
|
+
lines.push('{bold}Gaps Identified{/bold}');
|
|
295
|
+
for (const gap of result.gaps_identified) {
|
|
296
|
+
lines.push(`• ${gap}`);
|
|
297
|
+
}
|
|
298
|
+
lines.push('');
|
|
299
|
+
}
|
|
300
|
+
// Suggested queries
|
|
301
|
+
if (result.suggested_queries?.length) {
|
|
302
|
+
lines.push('{bold}Suggested Follow-ups{/bold}');
|
|
303
|
+
for (const query of result.suggested_queries) {
|
|
304
|
+
lines.push(`• ${query}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return lines.join('\n');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Execute the research task
|
|
311
|
+
*/
|
|
312
|
+
export async function executeResearch(state, ui, dbPath, dataDir, query) {
|
|
313
|
+
const trimmed = query.trim();
|
|
314
|
+
if (!trimmed) {
|
|
315
|
+
renderResearchPane(state, ui);
|
|
316
|
+
promptForFollowUpResearch(state, ui);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// Check for slash commands
|
|
320
|
+
if (await handleSlashCommand(trimmed, state, ui, dbPath)) {
|
|
321
|
+
promptForFollowUpResearch(state, ui);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
325
|
+
ui.askPane.setContent('{red-fg}Error: ANTHROPIC_API_KEY not set{/red-fg}');
|
|
326
|
+
ui.screen.render();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
state.researchQuery = trimmed;
|
|
330
|
+
state.researchRunning = true;
|
|
331
|
+
ui.askInput.hide();
|
|
332
|
+
renderResearchPane(state, ui);
|
|
333
|
+
updateResearchFooter(ui, state);
|
|
334
|
+
ui.screen.render();
|
|
335
|
+
try {
|
|
336
|
+
// Run the full research agent
|
|
337
|
+
const result = await handleResearch(dbPath, dataDir, {
|
|
338
|
+
task: trimmed,
|
|
339
|
+
project: state.currentProject || undefined,
|
|
340
|
+
content_type: state.currentContentType || undefined,
|
|
341
|
+
include_sources: true,
|
|
342
|
+
}, { hookContext: { mode: 'cli' } });
|
|
343
|
+
state.researchRunning = false;
|
|
344
|
+
// Format and add to history
|
|
345
|
+
const formatted = formatResearchResult(result);
|
|
346
|
+
state.researchHistory.push({
|
|
347
|
+
query: trimmed,
|
|
348
|
+
summary: formatted,
|
|
349
|
+
});
|
|
350
|
+
renderResearchPane(state, ui);
|
|
351
|
+
promptForFollowUpResearch(state, ui);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
state.researchRunning = false;
|
|
355
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
356
|
+
state.researchHistory.push({
|
|
357
|
+
query: trimmed,
|
|
358
|
+
summary: `Error: ${errorMsg}`,
|
|
359
|
+
});
|
|
360
|
+
renderResearchPane(state, ui);
|
|
361
|
+
promptForFollowUpResearch(state, ui);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool handlers for the Lore Document Browser TUI
|
|
3
|
+
*
|
|
4
|
+
* Handles tool listing, form display, and execution.
|
|
5
|
+
*/
|
|
6
|
+
import type { BrowserState, UIComponents, ToolFormField } from './browse-types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Parse tool input schema into form fields
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseInputSchema(inputSchema: Record<string, unknown>): ToolFormField[];
|
|
11
|
+
/**
|
|
12
|
+
* Show the tools list view
|
|
13
|
+
*/
|
|
14
|
+
export declare function showTools(state: BrowserState, ui: UIComponents): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Update tool selection and refresh display
|
|
17
|
+
*/
|
|
18
|
+
export declare function selectTool(state: BrowserState, ui: UIComponents): void;
|
|
19
|
+
/**
|
|
20
|
+
* Show the tool input form
|
|
21
|
+
*/
|
|
22
|
+
export declare function showToolForm(state: BrowserState, ui: UIComponents): void;
|
|
23
|
+
/**
|
|
24
|
+
* Hide the tool input form
|
|
25
|
+
*/
|
|
26
|
+
export declare function hideToolForm(state: BrowserState, ui: UIComponents): void;
|
|
27
|
+
/**
|
|
28
|
+
* Move to next form field
|
|
29
|
+
*/
|
|
30
|
+
export declare function formFieldNext(state: BrowserState, ui: UIComponents): void;
|
|
31
|
+
/**
|
|
32
|
+
* Move to previous form field
|
|
33
|
+
*/
|
|
34
|
+
export declare function formFieldPrev(state: BrowserState, ui: UIComponents): void;
|
|
35
|
+
/**
|
|
36
|
+
* Update current form field value
|
|
37
|
+
*/
|
|
38
|
+
export declare function formFieldUpdate(state: BrowserState, ui: UIComponents, value: ToolFormField['value']): void;
|
|
39
|
+
/**
|
|
40
|
+
* Execute the selected tool with current form values
|
|
41
|
+
*/
|
|
42
|
+
export declare function callTool(state: BrowserState, ui: UIComponents, dbPath: string, dataDir: string): Promise<boolean>;
|