@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,625 @@
1
+ /**
2
+ * Lore Document Browser TUI
3
+ *
4
+ * A Tig-like terminal UI for browsing the knowledge repository.
5
+ * Features:
6
+ * - Split-pane layout with document list and preview
7
+ * - Vim-style navigation (j/k, gg, G)
8
+ * - Full document view with scrolling
9
+ * - Editor integration
10
+ * - Search/filter (hybrid and regex)
11
+ */
12
+ import path from 'path';
13
+ import { createUIComponents } from './browse-ui.js';
14
+ import { updateStatus, renderList, renderPreview, buildListItems } from './browse-render.js';
15
+ import { enterFullView, exitFullView, enterSearch, enterRegexSearch, exitSearch, enterDocSearch, exitDocSearch, applyDocSearch, nextMatch, prevMatch, applyFilter, showHelp, hideHelp, openInEditor, moveDown, moveUp, pageDown, pageUp, jumpToEnd, jumpToStart, triggerSync, showProjectPicker, projectPickerDown, projectPickerUp, selectProject, cancelProjectPicker, clearProjectFilter, showDeleteConfirm, cancelDelete, confirmDelete, copyCurrentContent, toggleProjectExpand, renderReturnToAskOrResearch, expandCurrentProject, collapseCurrentProject, toggleGroupedView, isDocumentSelected,
16
+ // Move picker
17
+ showMovePicker, movePickerDown, movePickerUp, confirmMove, cancelMovePicker,
18
+ // Edit info
19
+ enterEditInfo, saveEditInfo, exitEditInfo,
20
+ // Type picker
21
+ showTypePicker, typePickerDown, typePickerUp, confirmTypeChange, cancelTypePicker,
22
+ // Content type filter
23
+ showContentTypeFilter, contentTypeFilterDown, contentTypeFilterUp, applyContentTypeFilter, cancelContentTypeFilter, clearContentTypeFilter, } from './browse-handlers.js';
24
+ import { showExtensions, selectExtension, toggleExtension, } from './browse-handlers-extensions.js';
25
+ import { enterAskMode, exitAskMode, executeAsk, promptForFollowUp, } from './browse-handlers-ask.js';
26
+ import { enterResearchMode, exitResearchMode, executeResearch, promptForFollowUpResearch, } from './browse-handlers-research.js';
27
+ import { updateAutocomplete, hideAutocomplete, handleAutocompleteKey, } from './browse-handlers-autocomplete.js';
28
+ import { getAllSources } from '../core/vector-store.js';
29
+ /**
30
+ * Start the document browser TUI
31
+ */
32
+ export async function startBrowser(options) {
33
+ const { project, sourceType, limit = 100, dataDir } = options;
34
+ const dbPath = path.join(dataDir, 'lore.lance');
35
+ const sourcesDir = path.join(dataDir, 'sources');
36
+ // Initialize state
37
+ const state = {
38
+ sources: [],
39
+ filtered: [],
40
+ selectedIndex: 0,
41
+ mode: 'list',
42
+ searchQuery: '',
43
+ searchMode: 'hybrid',
44
+ scrollOffset: 0,
45
+ fullContent: '',
46
+ fullContentLines: [],
47
+ fullContentLinesRaw: [],
48
+ gPressed: false,
49
+ docSearchPattern: '',
50
+ docSearchMatches: [],
51
+ docSearchCurrentIdx: 0,
52
+ projects: [],
53
+ projectPickerIndex: 0,
54
+ currentProject: project, // Start with CLI-provided project filter
55
+ extensionsList: [],
56
+ selectedExtensionIndex: 0,
57
+ askQuery: '',
58
+ askResponse: '',
59
+ askStreaming: false,
60
+ askHistory: [],
61
+ researchQuery: '',
62
+ researchRunning: false,
63
+ researchResponse: '',
64
+ researchHistory: [],
65
+ // Grouped view (enabled by default)
66
+ groupByProject: true,
67
+ expandedProjects: new Set(),
68
+ listItems: [],
69
+ // Move picker state
70
+ movePickerIndex: 0,
71
+ movePickerProjects: [],
72
+ moveTargetSource: undefined,
73
+ // Edit info state
74
+ editSource: undefined,
75
+ editTitle: '',
76
+ editProjects: [],
77
+ editFieldIndex: 0,
78
+ // Type picker state
79
+ typePickerIndex: 0,
80
+ typePickerSource: undefined,
81
+ // Content type filter state
82
+ contentTypeFilterIndex: 0,
83
+ currentContentType: undefined,
84
+ // Return mode after picker
85
+ pickerReturnMode: undefined,
86
+ // Autocomplete state
87
+ autocompleteVisible: false,
88
+ autocompleteOptions: [],
89
+ autocompleteIndex: 0,
90
+ autocompleteType: null,
91
+ autocompleteJustSelected: false,
92
+ };
93
+ // Create UI components
94
+ const ui = createUIComponents();
95
+ const { screen, helpPane, searchInput, regexInput, docSearchInput, listContent } = ui;
96
+ // Key bindings
97
+ screen.key(['?'], () => {
98
+ if (state.mode === 'help') {
99
+ hideHelp(state, ui);
100
+ }
101
+ else if (state.mode === 'list') {
102
+ showHelp(state, ui);
103
+ }
104
+ });
105
+ screen.key(['escape'], () => {
106
+ if (state.mode === 'fullview') {
107
+ if (state.docSearchPattern) {
108
+ // Clear document search first
109
+ exitDocSearch(state, ui, true);
110
+ }
111
+ else {
112
+ exitFullView(state, ui);
113
+ }
114
+ }
115
+ else if (state.mode === 'doc-search') {
116
+ exitDocSearch(state, ui, false);
117
+ }
118
+ else if (state.mode === 'search' || state.mode === 'regex-search') {
119
+ exitSearch(state, ui);
120
+ }
121
+ else if (state.mode === 'help') {
122
+ hideHelp(state, ui);
123
+ }
124
+ else if (state.mode === 'project-picker') {
125
+ cancelProjectPicker(state, ui);
126
+ }
127
+ else if (state.mode === 'move-picker') {
128
+ cancelMovePicker(state, ui);
129
+ }
130
+ else if (state.mode === 'type-picker') {
131
+ cancelTypePicker(state, ui);
132
+ }
133
+ else if (state.mode === 'content-type-filter') {
134
+ cancelContentTypeFilter(state, ui);
135
+ }
136
+ else if (state.mode === 'edit-info') {
137
+ exitEditInfo(state, ui);
138
+ }
139
+ else if (state.mode === 'delete-confirm') {
140
+ cancelDelete(state, ui);
141
+ }
142
+ else if (state.mode === 'extensions') {
143
+ state.mode = 'list';
144
+ ui.listTitle.setContent(' Documents');
145
+ ui.previewTitle.setContent(' Preview');
146
+ ui.footer.setContent(' j/k Nav │ / Search │ a Ask │ R Research │ p Proj │ c Type │ m Move │ i Edit │ Esc Quit │ ? Help');
147
+ updateStatus(ui, state, state.currentProject, sourceType);
148
+ renderList(ui, state);
149
+ renderPreview(ui, state);
150
+ screen.render();
151
+ }
152
+ else if (state.mode === 'ask') {
153
+ if (!state.askStreaming) {
154
+ exitAskMode(state, ui);
155
+ }
156
+ }
157
+ else if (state.mode === 'research') {
158
+ if (!state.researchRunning) {
159
+ exitResearchMode(state, ui);
160
+ }
161
+ }
162
+ else if (state.mode === 'list' && state.searchQuery) {
163
+ // Clear search filter
164
+ applyFilter(state, ui, '', 'hybrid', dbPath, dataDir, state.currentProject, sourceType);
165
+ screen.render();
166
+ }
167
+ else if (state.mode === 'list') {
168
+ // Quit from main list
169
+ screen.destroy();
170
+ process.exit(0);
171
+ }
172
+ });
173
+ screen.key(['j', 'down'], () => {
174
+ if (state.mode === 'project-picker') {
175
+ projectPickerDown(state, ui);
176
+ }
177
+ else if (state.mode === 'move-picker') {
178
+ movePickerDown(state, ui);
179
+ }
180
+ else if (state.mode === 'type-picker') {
181
+ typePickerDown(state, ui);
182
+ }
183
+ else if (state.mode === 'content-type-filter') {
184
+ contentTypeFilterDown(state, ui);
185
+ }
186
+ else if (state.mode === 'extensions') {
187
+ if (state.selectedExtensionIndex < state.extensionsList.length - 1) {
188
+ state.selectedExtensionIndex++;
189
+ selectExtension(state, ui);
190
+ }
191
+ }
192
+ else if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'edit-info') {
193
+ state.gPressed = false;
194
+ moveDown(state, ui);
195
+ }
196
+ });
197
+ screen.key(['k', 'up'], () => {
198
+ if (state.mode === 'project-picker') {
199
+ projectPickerUp(state, ui);
200
+ }
201
+ else if (state.mode === 'move-picker') {
202
+ movePickerUp(state, ui);
203
+ }
204
+ else if (state.mode === 'type-picker') {
205
+ typePickerUp(state, ui);
206
+ }
207
+ else if (state.mode === 'content-type-filter') {
208
+ contentTypeFilterUp(state, ui);
209
+ }
210
+ else if (state.mode === 'extensions') {
211
+ if (state.selectedExtensionIndex > 0) {
212
+ state.selectedExtensionIndex--;
213
+ selectExtension(state, ui);
214
+ }
215
+ }
216
+ else if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'edit-info') {
217
+ state.gPressed = false;
218
+ moveUp(state, ui);
219
+ }
220
+ });
221
+ screen.key(['C-d'], () => {
222
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
223
+ state.gPressed = false;
224
+ pageDown(state, ui);
225
+ }
226
+ });
227
+ screen.key(['C-u', 'pageup'], () => {
228
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
229
+ state.gPressed = false;
230
+ pageUp(state, ui);
231
+ }
232
+ });
233
+ screen.key(['pagedown'], () => {
234
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
235
+ state.gPressed = false;
236
+ pageDown(state, ui);
237
+ }
238
+ });
239
+ screen.key(['home'], () => {
240
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
241
+ state.gPressed = false;
242
+ jumpToStart(state, ui);
243
+ }
244
+ });
245
+ screen.key(['end', 'S-g'], () => {
246
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
247
+ state.gPressed = false;
248
+ jumpToEnd(state, ui);
249
+ }
250
+ });
251
+ screen.key(['g'], () => {
252
+ if (state.mode !== 'search' && state.mode !== 'help' && state.mode !== 'extensions') {
253
+ if (state.gPressed) {
254
+ jumpToStart(state, ui);
255
+ state.gPressed = false;
256
+ }
257
+ else {
258
+ state.gPressed = true;
259
+ // Reset after 500ms
260
+ setTimeout(() => { state.gPressed = false; }, 500);
261
+ }
262
+ }
263
+ });
264
+ screen.key(['enter'], async () => {
265
+ if (state.mode === 'list') {
266
+ // In grouped view, Enter on header toggles expand/collapse
267
+ if (state.groupByProject && state.listItems.length > 0) {
268
+ const item = state.listItems[state.selectedIndex];
269
+ if (item?.type === 'header') {
270
+ toggleProjectExpand(state, ui);
271
+ return;
272
+ }
273
+ }
274
+ // Enter on document opens full view
275
+ if (isDocumentSelected(state)) {
276
+ await enterFullView(state, ui, dbPath, sourcesDir);
277
+ }
278
+ }
279
+ else if (state.mode === 'project-picker') {
280
+ await selectProject(state, ui, dbPath, dataDir, sourceType);
281
+ }
282
+ else if (state.mode === 'move-picker') {
283
+ await confirmMove(state, ui, dbPath, dataDir, sourceType);
284
+ }
285
+ else if (state.mode === 'type-picker') {
286
+ await confirmTypeChange(state, ui, dbPath, sourceType);
287
+ }
288
+ else if (state.mode === 'content-type-filter') {
289
+ await applyContentTypeFilter(state, ui, dbPath, dataDir, sourceType);
290
+ }
291
+ else if (state.mode === 'edit-info') {
292
+ await saveEditInfo(state, ui, dbPath, sourceType);
293
+ }
294
+ else if (state.mode === 'extensions') {
295
+ await toggleExtension(state, ui);
296
+ }
297
+ });
298
+ // Space toggles expand/collapse on project headers
299
+ screen.key(['space'], () => {
300
+ if (state.mode === 'list' && state.groupByProject) {
301
+ toggleProjectExpand(state, ui);
302
+ }
303
+ });
304
+ // h collapses current project (vim-style: left = collapse)
305
+ screen.key(['h', 'left'], () => {
306
+ if (state.mode === 'list' && state.groupByProject) {
307
+ collapseCurrentProject(state, ui);
308
+ }
309
+ });
310
+ // l expands current project (vim-style: right = expand)
311
+ screen.key(['l', 'right'], () => {
312
+ if (state.mode === 'list' && state.groupByProject) {
313
+ expandCurrentProject(state, ui);
314
+ }
315
+ });
316
+ // Tab toggles between grouped and flat view
317
+ screen.key(['tab'], () => {
318
+ if (state.mode === 'list') {
319
+ toggleGroupedView(state, ui);
320
+ }
321
+ });
322
+ screen.key(['/'], () => {
323
+ if (state.mode === 'list') {
324
+ enterSearch(state, ui);
325
+ }
326
+ else if (state.mode === 'fullview') {
327
+ enterDocSearch(state, ui);
328
+ }
329
+ });
330
+ screen.key([':'], () => {
331
+ if (state.mode === 'list') {
332
+ enterRegexSearch(state, ui);
333
+ }
334
+ });
335
+ screen.key(['n'], () => {
336
+ if (state.mode === 'fullview' && state.docSearchMatches.length > 0) {
337
+ nextMatch(state, ui);
338
+ }
339
+ });
340
+ screen.key(['S-n'], () => {
341
+ if (state.mode === 'fullview' && state.docSearchMatches.length > 0) {
342
+ prevMatch(state, ui);
343
+ }
344
+ });
345
+ screen.key(['e'], () => {
346
+ if (state.mode === 'list' || state.mode === 'fullview') {
347
+ openInEditor(state, ui, sourcesDir);
348
+ }
349
+ });
350
+ // Move document to different project
351
+ screen.key(['m'], () => {
352
+ if (state.mode === 'list') {
353
+ // Only allow move on documents, not headers
354
+ if (isDocumentSelected(state)) {
355
+ showMovePicker(state, ui, dbPath);
356
+ }
357
+ }
358
+ });
359
+ // Edit document info
360
+ screen.key(['i'], () => {
361
+ if (state.mode === 'list') {
362
+ // Only allow edit on documents, not headers
363
+ if (isDocumentSelected(state)) {
364
+ enterEditInfo(state, ui);
365
+ }
366
+ }
367
+ });
368
+ // Change content type
369
+ screen.key(['t'], () => {
370
+ if (state.mode === 'list') {
371
+ // Only allow type change on documents, not headers
372
+ if (isDocumentSelected(state)) {
373
+ showTypePicker(state, ui);
374
+ }
375
+ }
376
+ });
377
+ // Copy to clipboard (y for yank)
378
+ screen.key(['y'], () => {
379
+ if (state.mode === 'fullview' || state.mode === 'ask' || state.mode === 'research') {
380
+ copyCurrentContent(state, ui);
381
+ }
382
+ });
383
+ screen.key(['s'], () => {
384
+ if (state.mode === 'list') {
385
+ triggerSync(state, ui, dbPath, dataDir, state.currentProject, sourceType);
386
+ }
387
+ });
388
+ // Project picker keybindings
389
+ screen.key(['p'], () => {
390
+ if (state.mode === 'list') {
391
+ showProjectPicker(state, ui, dbPath);
392
+ }
393
+ else if (state.mode === 'project-picker') {
394
+ // In picker, 'p' also closes (toggle behavior)
395
+ cancelProjectPicker(state, ui);
396
+ }
397
+ });
398
+ screen.key(['C-p'], () => {
399
+ if (state.mode === 'list') {
400
+ clearProjectFilter(state, ui, dbPath, dataDir, sourceType);
401
+ }
402
+ });
403
+ // Content type filter
404
+ screen.key(['c'], () => {
405
+ if (state.mode === 'list') {
406
+ showContentTypeFilter(state, ui);
407
+ }
408
+ else if (state.mode === 'content-type-filter') {
409
+ // Toggle behavior
410
+ cancelContentTypeFilter(state, ui);
411
+ }
412
+ });
413
+ screen.key(['C-c'], () => {
414
+ if (state.mode === 'list') {
415
+ clearContentTypeFilter(state, ui, dbPath, dataDir, sourceType);
416
+ }
417
+ });
418
+ screen.key(['a'], () => {
419
+ if (state.mode === 'list') {
420
+ enterAskMode(state, ui);
421
+ }
422
+ else if (state.mode === 'ask' && !state.askStreaming) {
423
+ // In ask mode, 'a' refocuses input for follow-up (use /new to clear)
424
+ promptForFollowUp(state, ui);
425
+ }
426
+ });
427
+ screen.key(['S-r'], () => {
428
+ if (state.mode === 'list') {
429
+ enterResearchMode(state, ui);
430
+ }
431
+ else if (state.mode === 'research' && !state.researchRunning) {
432
+ // In research mode, 'R' refocuses input for follow-up (use /new to clear)
433
+ promptForFollowUpResearch(state, ui);
434
+ }
435
+ });
436
+ screen.key(['x'], () => {
437
+ if (state.mode === 'list') {
438
+ showExtensions(state, ui);
439
+ }
440
+ });
441
+ // Delete keybindings (Delete key or backspace)
442
+ screen.key(['delete', 'backspace'], () => {
443
+ if (state.mode === 'list') {
444
+ showDeleteConfirm(state, ui);
445
+ }
446
+ });
447
+ screen.key(['y'], async () => {
448
+ if (state.mode === 'delete-confirm') {
449
+ await confirmDelete(state, ui, dbPath, dataDir, state.currentProject, sourceType);
450
+ }
451
+ });
452
+ screen.key(['n'], () => {
453
+ if (state.mode === 'delete-confirm') {
454
+ cancelDelete(state, ui);
455
+ }
456
+ });
457
+ // Mouse wheel scrolling (ask/research use blessed's native scrolling via askPane)
458
+ screen.on('wheeldown', () => {
459
+ if (state.mode === 'list') {
460
+ moveDown(state, ui);
461
+ }
462
+ else if (state.mode === 'fullview') {
463
+ moveDown(state, ui);
464
+ moveDown(state, ui);
465
+ moveDown(state, ui);
466
+ }
467
+ else if (state.mode === 'ask' || state.mode === 'research') {
468
+ ui.askPane.scroll(3);
469
+ ui.screen.render();
470
+ }
471
+ });
472
+ screen.on('wheelup', () => {
473
+ if (state.mode === 'list') {
474
+ moveUp(state, ui);
475
+ }
476
+ else if (state.mode === 'fullview') {
477
+ moveUp(state, ui);
478
+ moveUp(state, ui);
479
+ moveUp(state, ui);
480
+ }
481
+ else if (state.mode === 'ask' || state.mode === 'research') {
482
+ ui.askPane.scroll(-3);
483
+ ui.screen.render();
484
+ }
485
+ });
486
+ // Any key closes help
487
+ helpPane.on('keypress', () => {
488
+ hideHelp(state, ui);
489
+ });
490
+ // Search input handlers
491
+ searchInput.on('submit', async (value) => {
492
+ const query = value.startsWith('/') ? value.slice(1) : value;
493
+ exitSearch(state, ui);
494
+ await applyFilter(state, ui, query, 'hybrid', dbPath, dataDir, state.currentProject, sourceType);
495
+ });
496
+ searchInput.on('cancel', () => {
497
+ exitSearch(state, ui);
498
+ });
499
+ // Regex search input handlers
500
+ regexInput.on('submit', async (value) => {
501
+ const query = value.startsWith(':') ? value.slice(1) : value;
502
+ exitSearch(state, ui);
503
+ await applyFilter(state, ui, query, 'regex', dbPath, dataDir, state.currentProject, sourceType);
504
+ });
505
+ regexInput.on('cancel', () => {
506
+ exitSearch(state, ui);
507
+ });
508
+ // Document search input handlers (fullview mode)
509
+ docSearchInput.on('submit', (value) => {
510
+ const pattern = value.startsWith('/') ? value.slice(1) : value;
511
+ exitDocSearch(state, ui, false);
512
+ if (!pattern && state.docSearchPattern) {
513
+ // Empty input with existing search = go to next match (like vim)
514
+ nextMatch(state, ui);
515
+ }
516
+ else {
517
+ applyDocSearch(state, ui, pattern);
518
+ }
519
+ });
520
+ docSearchInput.on('cancel', () => {
521
+ exitDocSearch(state, ui, false);
522
+ });
523
+ // Ask/Research input handlers (shared input component)
524
+ const { askInput } = ui;
525
+ askInput.on('submit', async (value) => {
526
+ // Skip submit if autocomplete just handled the Enter key
527
+ if (state.autocompleteJustSelected) {
528
+ state.autocompleteJustSelected = false;
529
+ // Re-focus and continue input
530
+ askInput.focus();
531
+ askInput.readInput();
532
+ return;
533
+ }
534
+ if (state.mode === 'research') {
535
+ await executeResearch(state, ui, dbPath, dataDir, value);
536
+ }
537
+ else if (state.mode === 'edit-info') {
538
+ // Save the edited title
539
+ state.editTitle = value;
540
+ await saveEditInfo(state, ui, dbPath, sourceType);
541
+ }
542
+ else {
543
+ await executeAsk(state, ui, dbPath, value);
544
+ }
545
+ });
546
+ askInput.on('cancel', () => {
547
+ hideAutocomplete(state, ui);
548
+ if (state.mode === 'research') {
549
+ exitResearchMode(state, ui);
550
+ }
551
+ else if (state.mode === 'edit-info') {
552
+ exitEditInfo(state, ui);
553
+ }
554
+ else {
555
+ exitAskMode(state, ui);
556
+ }
557
+ });
558
+ // Autocomplete keypress handler for askInput
559
+ askInput.on('keypress', async (_ch, key) => {
560
+ // Only active in ask or research modes (not edit-info)
561
+ if (state.mode !== 'ask' && state.mode !== 'research') {
562
+ return;
563
+ }
564
+ // Check if autocomplete handles this key
565
+ const acResult = handleAutocompleteKey(state, ui, key.name);
566
+ if (acResult.handled) {
567
+ if (acResult.result) {
568
+ // Set flag to prevent submit handler from firing
569
+ state.autocompleteJustSelected = true;
570
+ if (acResult.result.type === 'input') {
571
+ // Just a command prefix, set in textbox
572
+ askInput.setValue(acResult.result.value);
573
+ ui.screen.render();
574
+ // Trigger autocomplete update after setting new value
575
+ await updateAutocomplete(state, ui, dbPath, acResult.result.value);
576
+ }
577
+ else if (acResult.result.type === 'project') {
578
+ // Directly set project filter - bypass textbox to avoid truncation
579
+ state.currentProject = acResult.result.value;
580
+ askInput.setValue('');
581
+ renderReturnToAskOrResearch(state, ui, state.mode);
582
+ ui.screen.render();
583
+ }
584
+ else if (acResult.result.type === 'contentType') {
585
+ // Directly set content type filter - bypass textbox to avoid truncation
586
+ state.currentContentType = acResult.result.value;
587
+ askInput.setValue('');
588
+ renderReturnToAskOrResearch(state, ui, state.mode);
589
+ ui.screen.render();
590
+ }
591
+ }
592
+ return;
593
+ }
594
+ // After any other key, update autocomplete based on current value
595
+ // Use setImmediate to get the updated value after the keypress is processed
596
+ setImmediate(async () => {
597
+ const currentValue = askInput.getValue();
598
+ await updateAutocomplete(state, ui, dbPath, currentValue);
599
+ });
600
+ });
601
+ // Load data
602
+ try {
603
+ state.sources = await getAllSources(dbPath, {
604
+ project,
605
+ source_type: sourceType,
606
+ limit,
607
+ });
608
+ state.filtered = [...state.sources];
609
+ // Build grouped list items
610
+ if (state.groupByProject) {
611
+ state.listItems = buildListItems(state);
612
+ }
613
+ }
614
+ catch (error) {
615
+ ui.statusBar.setContent(` Error: ${error}`);
616
+ screen.render();
617
+ return;
618
+ }
619
+ // Initial render
620
+ updateStatus(ui, state, state.currentProject, sourceType);
621
+ renderList(ui, state);
622
+ renderPreview(ui, state);
623
+ listContent.focus();
624
+ screen.render();
625
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Terminal markdown renderer
3
+ *
4
+ * Converts markdown to styled terminal output using ANSI codes.
5
+ * Designed for blessed terminals.
6
+ */
7
+ /**
8
+ * Render markdown to terminal-friendly styled text
9
+ */
10
+ export declare function renderMarkdown(text: string, width?: number): string;
11
+ /**
12
+ * Strip markdown formatting to get plain text
13
+ */
14
+ export declare function stripMarkdown(text: string): string;
15
+ /**
16
+ * Truncate text to fit width, preserving ANSI codes
17
+ */
18
+ export declare function truncateWithAnsi(text: string, maxWidth: number): string;
19
+ /**
20
+ * Word wrap text while preserving ANSI codes
21
+ */
22
+ export declare function wrapText(text: string, width: number): string[];