@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,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>;