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