@brainfile/cli 0.8.0 → 0.9.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 (112) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/commands/add.d.ts.map +1 -1
  3. package/dist/commands/add.js +8 -24
  4. package/dist/commands/add.js.map +1 -1
  5. package/dist/commands/archive.d.ts.map +1 -1
  6. package/dist/commands/archive.js +8 -23
  7. package/dist/commands/archive.js.map +1 -1
  8. package/dist/commands/delete.d.ts.map +1 -1
  9. package/dist/commands/delete.js +8 -23
  10. package/dist/commands/delete.js.map +1 -1
  11. package/dist/commands/list.d.ts.map +1 -1
  12. package/dist/commands/list.js +4 -12
  13. package/dist/commands/list.js.map +1 -1
  14. package/dist/commands/mcp.d.ts.map +1 -1
  15. package/dist/commands/mcp.js +29 -2
  16. package/dist/commands/mcp.js.map +1 -1
  17. package/dist/commands/move.d.ts.map +1 -1
  18. package/dist/commands/move.js +12 -35
  19. package/dist/commands/move.js.map +1 -1
  20. package/dist/commands/patch.d.ts.map +1 -1
  21. package/dist/commands/patch.js +9 -27
  22. package/dist/commands/patch.js.map +1 -1
  23. package/dist/commands/restore.d.ts.map +1 -1
  24. package/dist/commands/restore.js +10 -26
  25. package/dist/commands/restore.js.map +1 -1
  26. package/dist/commands/subtask.d.ts.map +1 -1
  27. package/dist/commands/subtask.js +16 -60
  28. package/dist/commands/subtask.js.map +1 -1
  29. package/dist/commands/tui.d.ts.map +1 -1
  30. package/dist/commands/tui.js +6 -701
  31. package/dist/commands/tui.js.map +1 -1
  32. package/dist/tui/BrainfileTUI.d.ts +4 -0
  33. package/dist/tui/BrainfileTUI.d.ts.map +1 -0
  34. package/dist/tui/BrainfileTUI.js +198 -0
  35. package/dist/tui/BrainfileTUI.js.map +1 -0
  36. package/dist/tui/actions.d.ts +49 -0
  37. package/dist/tui/actions.d.ts.map +1 -0
  38. package/dist/tui/actions.js +569 -0
  39. package/dist/tui/actions.js.map +1 -0
  40. package/dist/tui/components/ColumnTabs.d.ts +9 -0
  41. package/dist/tui/components/ColumnTabs.d.ts.map +1 -0
  42. package/dist/tui/components/ColumnTabs.js +30 -0
  43. package/dist/tui/components/ColumnTabs.js.map +1 -0
  44. package/dist/tui/components/Header.d.ts +12 -0
  45. package/dist/tui/components/Header.d.ts.map +1 -0
  46. package/dist/tui/components/Header.js +24 -0
  47. package/dist/tui/components/Header.js.map +1 -0
  48. package/dist/tui/components/HelpOverlay.d.ts +7 -0
  49. package/dist/tui/components/HelpOverlay.d.ts.map +1 -0
  50. package/dist/tui/components/HelpOverlay.js +56 -0
  51. package/dist/tui/components/HelpOverlay.js.map +1 -0
  52. package/dist/tui/components/Overlays.d.ts +41 -0
  53. package/dist/tui/components/Overlays.d.ts.map +1 -0
  54. package/dist/tui/components/Overlays.js +127 -0
  55. package/dist/tui/components/Overlays.js.map +1 -0
  56. package/dist/tui/components/ProgressBar.d.ts +8 -0
  57. package/dist/tui/components/ProgressBar.d.ts.map +1 -0
  58. package/dist/tui/components/ProgressBar.js +35 -0
  59. package/dist/tui/components/ProgressBar.js.map +1 -0
  60. package/dist/tui/components/SearchBar.d.ts +7 -0
  61. package/dist/tui/components/SearchBar.d.ts.map +1 -0
  62. package/dist/tui/components/SearchBar.js +22 -0
  63. package/dist/tui/components/SearchBar.js.map +1 -0
  64. package/dist/tui/components/StatusBar.d.ts +12 -0
  65. package/dist/tui/components/StatusBar.d.ts.map +1 -0
  66. package/dist/tui/components/StatusBar.js +37 -0
  67. package/dist/tui/components/StatusBar.js.map +1 -0
  68. package/dist/tui/components/TaskCard.d.ts +19 -0
  69. package/dist/tui/components/TaskCard.d.ts.map +1 -0
  70. package/dist/tui/components/TaskCard.js +105 -0
  71. package/dist/tui/components/TaskCard.js.map +1 -0
  72. package/dist/tui/components/TaskList.d.ts +11 -0
  73. package/dist/tui/components/TaskList.d.ts.map +1 -0
  74. package/dist/tui/components/TaskList.js +56 -0
  75. package/dist/tui/components/TaskList.js.map +1 -0
  76. package/dist/tui/components/index.d.ts +19 -0
  77. package/dist/tui/components/index.d.ts.map +1 -0
  78. package/dist/tui/components/index.js +26 -0
  79. package/dist/tui/components/index.js.map +1 -0
  80. package/dist/tui/hooks/index.d.ts +3 -0
  81. package/dist/tui/hooks/index.d.ts.map +1 -0
  82. package/dist/tui/hooks/index.js +8 -0
  83. package/dist/tui/hooks/index.js.map +1 -0
  84. package/dist/tui/hooks/useBrainfileLoader.d.ts +5 -0
  85. package/dist/tui/hooks/useBrainfileLoader.d.ts.map +1 -0
  86. package/dist/tui/hooks/useBrainfileLoader.js +171 -0
  87. package/dist/tui/hooks/useBrainfileLoader.js.map +1 -0
  88. package/dist/tui/hooks/useKeyboardNavigation.d.ts +16 -0
  89. package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +1 -0
  90. package/dist/tui/hooks/useKeyboardNavigation.js +434 -0
  91. package/dist/tui/hooks/useKeyboardNavigation.js.map +1 -0
  92. package/dist/tui/index.d.ts +9 -0
  93. package/dist/tui/index.d.ts.map +1 -0
  94. package/dist/tui/index.js +36 -0
  95. package/dist/tui/index.js.map +1 -0
  96. package/dist/tui/theme.d.ts +113 -0
  97. package/dist/tui/theme.d.ts.map +1 -0
  98. package/dist/tui/theme.js +132 -0
  99. package/dist/tui/theme.js.map +1 -0
  100. package/dist/tui/types.d.ts +30 -0
  101. package/dist/tui/types.d.ts.map +1 -0
  102. package/dist/tui/types.js +6 -0
  103. package/dist/tui/types.js.map +1 -0
  104. package/dist/tui/utils.d.ts +3 -0
  105. package/dist/tui/utils.d.ts.map +1 -0
  106. package/dist/tui/utils.js +31 -0
  107. package/dist/tui/utils.js.map +1 -0
  108. package/dist/utils/errorHandler.d.ts +106 -0
  109. package/dist/utils/errorHandler.d.ts.map +1 -0
  110. package/dist/utils/errorHandler.js +228 -0
  111. package/dist/utils/errorHandler.js.map +1 -0
  112. package/package.json +1 -1
@@ -32,711 +32,16 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.tuiCommand = tuiCommand;
37
- const react_1 = __importStar(require("react"));
40
+ const react_1 = __importDefault(require("react"));
38
41
  const ink_1 = require("ink");
39
- const core_1 = require("@brainfile/core");
40
42
  const fs = __importStar(require("fs"));
41
43
  const path = __importStar(require("path"));
42
- const chokidar = __importStar(require("chokidar"));
43
- // Unified palette matching VSCode extension priority colors
44
- // VSCode uses: critical=#d64933, high=#867530, medium=#37505C, low=#bac1b8
45
- const PALETTE = {
46
- bg: '#000000',
47
- panel: '#1e1e1e',
48
- text: 'white',
49
- textMuted: 'gray',
50
- border: 'gray',
51
- // Priority colors (terminal equivalents)
52
- critical: 'red', // #d64933
53
- giga: 'magenta', // Custom high priority
54
- high: 'yellow', // #867530
55
- medium: 'cyan', // #37505C
56
- low: 'gray', // #bac1b8
57
- // UI accents
58
- progress: 'blue',
59
- success: 'green',
60
- selected: 'white',
61
- };
62
- // Box drawing characters
63
- const BOX = {
64
- topLeft: '┌',
65
- topRight: '┐',
66
- bottomLeft: '└',
67
- bottomRight: '┘',
68
- horizontal: '─',
69
- vertical: '│',
70
- teeRight: '├',
71
- teeLeft: '┤',
72
- };
73
- const HEADER_ROWS = 7; // title(1) + progress(3: padTop+content+padBottom) + tabs(2: marginTop+content) + separator(1)
74
- const FOOTER_ROWS = 3; // separator + status bar (2 rows: content + bottom padding)
75
- function BrainfileTUI({ filePath }) {
76
- const { exit } = (0, ink_1.useApp)();
77
- const { stdout } = (0, ink_1.useStdout)();
78
- const termWidth = stdout?.columns ?? process.stdout.columns ?? 80;
79
- const termHeight = stdout?.rows ?? process.stdout.rows ?? 24;
80
- const [state, setState] = (0, react_1.useState)({
81
- board: null,
82
- error: null,
83
- lastUpdated: new Date(),
84
- activeColumnIndex: 0,
85
- selectedTaskIndex: 0,
86
- mode: 'browse',
87
- searchQuery: '',
88
- expandedTaskIds: new Set(),
89
- reloadFlash: false,
90
- lastContentHash: null,
91
- });
92
- const viewportHeight = Math.max(termHeight - HEADER_ROWS - FOOTER_ROWS, 5);
93
- const loadBrainfile = (0, react_1.useCallback)((forceRefresh = false) => {
94
- try {
95
- const content = fs.readFileSync(filePath, 'utf-8');
96
- // Skip redundant refreshes using content hash (like VSCode extension)
97
- const contentHash = (0, core_1.hashBoardContent)(content);
98
- if (!forceRefresh && state.lastContentHash === contentHash) {
99
- return; // Content unchanged, skip re-render
100
- }
101
- const result = core_1.Brainfile.parseWithErrors(content);
102
- if (result.board) {
103
- setState(prev => {
104
- // Preserve selection by task ID if possible
105
- const prevTaskId = prev.board?.columns[prev.activeColumnIndex]?.tasks[prev.selectedTaskIndex]?.id;
106
- let newSelectedIndex = 0;
107
- if (prevTaskId && result.board) {
108
- const col = result.board.columns[prev.activeColumnIndex];
109
- if (col) {
110
- const idx = col.tasks.findIndex(t => t.id === prevTaskId);
111
- if (idx >= 0)
112
- newSelectedIndex = idx;
113
- }
114
- }
115
- return {
116
- ...prev,
117
- board: result.board,
118
- error: null,
119
- lastUpdated: new Date(),
120
- selectedTaskIndex: newSelectedIndex,
121
- reloadFlash: true,
122
- lastContentHash: contentHash,
123
- };
124
- });
125
- // Clear reload flash after 1 second
126
- setTimeout(() => {
127
- setState(prev => ({ ...prev, reloadFlash: false }));
128
- }, 1000);
129
- }
130
- else {
131
- // Distinguish between different error cases
132
- let errorMessage;
133
- if (result.data && result.type) {
134
- // Valid brainfile but not a board type
135
- errorMessage = `This is a '${result.type}' brainfile. The TUI currently only supports 'board' type files.`;
136
- }
137
- else if (result.error) {
138
- // Parse error with specific message
139
- errorMessage = result.error;
140
- }
141
- else {
142
- // Unknown failure
143
- errorMessage = 'Not a valid brainfile';
144
- }
145
- setState(prev => ({
146
- ...prev,
147
- error: errorMessage,
148
- lastContentHash: contentHash,
149
- }));
150
- }
151
- }
152
- catch (err) {
153
- setState(prev => ({
154
- ...prev,
155
- error: err instanceof Error ? err.message : String(err),
156
- }));
157
- }
158
- }, [filePath, state.lastContentHash]);
159
- (0, react_1.useEffect)(() => {
160
- loadBrainfile();
161
- }, [loadBrainfile]);
162
- (0, react_1.useEffect)(() => {
163
- const watcher = chokidar.watch(filePath, {
164
- persistent: true,
165
- ignoreInitial: true,
166
- usePolling: true,
167
- interval: 750,
168
- awaitWriteFinish: {
169
- stabilityThreshold: 250,
170
- pollInterval: 100,
171
- },
172
- depth: 0,
173
- });
174
- watcher.on('change', () => loadBrainfile());
175
- watcher.on('error', (err) => {
176
- const message = err instanceof Error ? err.message : String(err);
177
- setState(prev => ({
178
- ...prev,
179
- error: `File watcher error: ${message}`,
180
- }));
181
- });
182
- return () => {
183
- watcher.close();
184
- };
185
- }, [filePath, loadBrainfile]);
186
- // Sort columns by order property (like VSCode extension)
187
- const orderedColumns = (0, react_1.useMemo)(() => {
188
- if (!state.board)
189
- return [];
190
- return [...state.board.columns].sort((a, b) => {
191
- const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
192
- const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
193
- return orderA - orderB;
194
- });
195
- }, [state.board]);
196
- // Filtered columns based on search
197
- const filteredColumns = (0, react_1.useMemo)(() => {
198
- if (!orderedColumns.length)
199
- return [];
200
- if (!state.searchQuery.trim())
201
- return orderedColumns;
202
- const query = state.searchQuery.toLowerCase();
203
- return orderedColumns.map(col => ({
204
- ...col,
205
- tasks: col.tasks.filter(task => task.title.toLowerCase().includes(query) ||
206
- task.id.toLowerCase().includes(query) ||
207
- task.tags?.some(t => t.toLowerCase().includes(query)) ||
208
- task.priority?.toLowerCase().includes(query)),
209
- })).filter(col => col.tasks.length > 0);
210
- }, [orderedColumns, state.searchQuery]);
211
- // Current column and its tasks
212
- const currentColumn = filteredColumns[state.activeColumnIndex];
213
- const currentTasks = currentColumn?.tasks || [];
214
- const maxTaskIndex = Math.max(0, currentTasks.length - 1);
215
- // Keep selection in bounds
216
- (0, react_1.useEffect)(() => {
217
- setState(prev => ({
218
- ...prev,
219
- activeColumnIndex: Math.min(prev.activeColumnIndex, Math.max(0, filteredColumns.length - 1)),
220
- selectedTaskIndex: Math.min(prev.selectedTaskIndex, maxTaskIndex),
221
- }));
222
- }, [filteredColumns.length, maxTaskIndex]);
223
- // Calculate stats
224
- const stats = (0, react_1.useMemo)(() => {
225
- if (!state.board)
226
- return { total: 0, done: 0, percentage: 0 };
227
- const total = state.board.columns.reduce((sum, col) => sum + col.tasks.length, 0);
228
- const doneCol = state.board.columns.find(col => col.id === 'done' || col.title.toLowerCase() === 'done');
229
- const done = doneCol?.tasks.length || 0;
230
- const percentage = total > 0 ? Math.round((done / total) * 100) : 0;
231
- return { total, done, percentage };
232
- }, [state.board]);
233
- (0, ink_1.useInput)((input, key) => {
234
- // Help mode - any key closes
235
- if (state.mode === 'help') {
236
- setState(prev => ({ ...prev, mode: 'browse' }));
237
- return;
238
- }
239
- // Search mode handling
240
- if (state.mode === 'search') {
241
- if (key.escape) {
242
- setState(prev => ({ ...prev, mode: 'browse', searchQuery: '' }));
243
- return;
244
- }
245
- if (key.return) {
246
- setState(prev => ({ ...prev, mode: 'browse' }));
247
- return;
248
- }
249
- if (key.backspace || key.delete) {
250
- setState(prev => ({ ...prev, searchQuery: prev.searchQuery.slice(0, -1) }));
251
- return;
252
- }
253
- if (input && !key.ctrl && !key.meta) {
254
- setState(prev => ({ ...prev, searchQuery: prev.searchQuery + input }));
255
- return;
256
- }
257
- return;
258
- }
259
- // Browse mode
260
- if (input === 'q' || (key.ctrl && input === 'c')) {
261
- exit();
262
- return;
263
- }
264
- if (input === '?') {
265
- setState(prev => ({ ...prev, mode: 'help' }));
266
- return;
267
- }
268
- if (input === '/') {
269
- setState(prev => ({ ...prev, mode: 'search', searchQuery: '' }));
270
- return;
271
- }
272
- // Refresh (force refresh bypasses hash check)
273
- if (input === 'r') {
274
- loadBrainfile(true);
275
- return;
276
- }
277
- // Navigation: up/down through tasks
278
- if (key.downArrow || input === 'j') {
279
- setState(prev => ({
280
- ...prev,
281
- selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxTaskIndex),
282
- }));
283
- return;
284
- }
285
- if (key.upArrow || input === 'k') {
286
- setState(prev => ({
287
- ...prev,
288
- selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0),
289
- }));
290
- return;
291
- }
292
- // Page scrolling
293
- if (key.ctrl && input === 'd') {
294
- setState(prev => ({
295
- ...prev,
296
- selectedTaskIndex: Math.min(prev.selectedTaskIndex + Math.floor(viewportHeight / 2), maxTaskIndex),
297
- }));
298
- return;
299
- }
300
- if (key.ctrl && input === 'u') {
301
- setState(prev => ({
302
- ...prev,
303
- selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
304
- }));
305
- return;
306
- }
307
- // Column switching: TAB / left/right
308
- if (key.tab || key.rightArrow || input === 'l') {
309
- setState(prev => ({
310
- ...prev,
311
- activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumns.length,
312
- selectedTaskIndex: 0,
313
- }));
314
- return;
315
- }
316
- if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
317
- setState(prev => ({
318
- ...prev,
319
- activeColumnIndex: prev.activeColumnIndex === 0
320
- ? filteredColumns.length - 1
321
- : prev.activeColumnIndex - 1,
322
- selectedTaskIndex: 0,
323
- }));
324
- return;
325
- }
326
- // Jump to column headers with { and }
327
- if (input === '{') {
328
- setState(prev => ({
329
- ...prev,
330
- activeColumnIndex: Math.max(0, prev.activeColumnIndex - 1),
331
- selectedTaskIndex: 0,
332
- }));
333
- return;
334
- }
335
- if (input === '}') {
336
- setState(prev => ({
337
- ...prev,
338
- activeColumnIndex: Math.min(filteredColumns.length - 1, prev.activeColumnIndex + 1),
339
- selectedTaskIndex: 0,
340
- }));
341
- return;
342
- }
343
- // Home/End
344
- if (input === 'g') {
345
- setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
346
- return;
347
- }
348
- if (input === 'G') {
349
- setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
350
- return;
351
- }
352
- // Expand/collapse task
353
- if (key.return) {
354
- const task = currentTasks[state.selectedTaskIndex];
355
- if (task) {
356
- setState(prev => {
357
- const newExpanded = new Set(prev.expandedTaskIds);
358
- if (newExpanded.has(task.id)) {
359
- newExpanded.delete(task.id);
360
- }
361
- else {
362
- newExpanded.add(task.id);
363
- }
364
- return { ...prev, expandedTaskIds: newExpanded };
365
- });
366
- }
367
- return;
368
- }
369
- // Escape: collapse all or clear search
370
- if (key.escape) {
371
- setState(prev => ({
372
- ...prev,
373
- expandedTaskIds: new Set(),
374
- searchQuery: '',
375
- }));
376
- return;
377
- }
378
- });
379
- // Error state
380
- if (state.error) {
381
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
382
- react_1.default.createElement(ink_1.Text, { color: PALETTE.high, bold: true }, "Error"),
383
- react_1.default.createElement(ink_1.Box, { marginTop: 1 },
384
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, state.error)),
385
- react_1.default.createElement(ink_1.Box, { marginTop: 1 },
386
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "Press q to quit, r to retry"))));
387
- }
388
- // Loading state
389
- if (!state.board) {
390
- return (react_1.default.createElement(ink_1.Box, { padding: 1 },
391
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, "Loading...")));
392
- }
393
- // Help overlay
394
- if (state.mode === 'help') {
395
- return react_1.default.createElement(HelpOverlay, { termWidth: termWidth });
396
- }
397
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", width: termWidth, height: termHeight },
398
- react_1.default.createElement(Header, { title: state.board.title || 'Brainfile', stats: stats, reloadFlash: state.reloadFlash }),
399
- react_1.default.createElement(ProgressBar, { done: stats.done, total: stats.total, width: termWidth - 4 }),
400
- state.mode === 'search' && (react_1.default.createElement(SearchBar, { query: state.searchQuery, width: termWidth - 2 })),
401
- react_1.default.createElement(ColumnTabs, { columns: filteredColumns, activeIndex: state.activeColumnIndex, termWidth: termWidth }),
402
- react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
403
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, BOX.horizontal.repeat(termWidth - 2))),
404
- react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column" },
405
- react_1.default.createElement(TaskList, { tasks: currentTasks, selectedIndex: state.selectedTaskIndex, expandedIds: state.expandedTaskIds, viewportHeight: viewportHeight - (state.mode === 'search' ? 1 : 0), termWidth: termWidth })),
406
- react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
407
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, BOX.horizontal.repeat(termWidth - 2))),
408
- react_1.default.createElement(StatusBar, { mode: state.mode, columnName: currentColumn?.title || '', taskIndex: state.selectedTaskIndex + 1, taskCount: currentTasks.length, termWidth: termWidth })));
409
- }
410
- function Header({ title, reloadFlash }) {
411
- return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, paddingRight: 1 },
412
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
413
- BOX.topLeft,
414
- BOX.horizontal,
415
- " ",
416
- title,
417
- " "),
418
- reloadFlash ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.success },
419
- " ",
420
- String.fromCharCode(0x21BB),
421
- " reloaded")) : null));
422
- }
423
- function ProgressBar({ done, total, width }) {
424
- const percentage = total > 0 ? Math.round((done / total) * 100) : 0;
425
- // Reserve space for: padding + "XX% " + " X of Y complete"
426
- const textWidth = 20 + String(done).length + String(total).length;
427
- const barWidth = Math.max(width - textWidth, 20);
428
- const filled = Math.round((percentage / 100) * barWidth);
429
- const empty = barWidth - filled;
430
- return (react_1.default.createElement(ink_1.Box, { paddingLeft: 2, paddingTop: 1, paddingBottom: 1 },
431
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
432
- percentage,
433
- "% "),
434
- react_1.default.createElement(ink_1.Text, { color: PALETTE.progress }, '█'.repeat(filled)),
435
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, '░'.repeat(empty)),
436
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
437
- " ",
438
- done,
439
- " of ",
440
- total,
441
- " complete")));
442
- }
443
- function SearchBar({ query, width }) {
444
- const inputWidth = Math.min(width - 6, 60);
445
- const displayQuery = query.padEnd(inputWidth, ' ').slice(0, inputWidth);
446
- return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, marginTop: 0 },
447
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
448
- String.fromCharCode(0x1F50D),
449
- " "),
450
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, inverse: true },
451
- "[",
452
- displayQuery,
453
- "]")));
454
- }
455
- function ColumnTabs({ columns, activeIndex, termWidth }) {
456
- const maxTabWidth = Math.floor((termWidth - 4) / Math.max(columns.length, 1)) - 2;
457
- return (react_1.default.createElement(ink_1.Box, { paddingLeft: 1, paddingRight: 1, marginTop: 1 }, columns.map((col, idx) => {
458
- const isActive = idx === activeIndex;
459
- const label = truncate(col.title.toUpperCase(), maxTabWidth - 4);
460
- const count = col.tasks.length;
461
- return (react_1.default.createElement(ink_1.Box, { key: col.id, marginRight: 1 }, isActive ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true, inverse: true },
462
- " ",
463
- label,
464
- " ",
465
- count,
466
- " ")) : (react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
467
- " ",
468
- label,
469
- " ",
470
- count,
471
- " "))));
472
- })));
473
- }
474
- function TaskList({ tasks, selectedIndex, expandedIds, viewportHeight, termWidth }) {
475
- // Calculate scroll offset to keep selection visible
476
- const scrollOffset = Math.max(0, Math.min(selectedIndex - Math.floor(viewportHeight / 3), Math.max(0, tasks.length - Math.floor(viewportHeight / 2))));
477
- const cardWidth = termWidth - 4;
478
- // Build visible items with expansion
479
- const visibleItems = [];
480
- let linesUsed = 0;
481
- for (let i = scrollOffset; i < tasks.length && linesUsed < viewportHeight; i++) {
482
- const task = tasks[i];
483
- const isSelected = i === selectedIndex;
484
- const isExpanded = expandedIds.has(task.id);
485
- // Calculate how many lines this task will take (2 lines base + expanded content)
486
- const subtaskLines = isExpanded ? Math.min((task.subtasks?.length || 0), 4) : 0;
487
- const expandedLines = isExpanded ? 2 + subtaskLines + (task.relatedFiles?.length ? 1 : 0) : 0;
488
- const taskLines = 3 + expandedLines; // title + metadata + margin + expanded
489
- if (linesUsed + taskLines <= viewportHeight) {
490
- visibleItems.push({ task, isSelected, isExpanded });
491
- linesUsed += taskLines;
492
- }
493
- else if (visibleItems.length === 0) {
494
- // Always show at least one item
495
- visibleItems.push({ task, isSelected, isExpanded });
496
- linesUsed += taskLines;
497
- break;
498
- }
499
- else {
500
- break;
501
- }
502
- }
503
- // Calculate remaining empty lines to fill viewport
504
- const emptyLines = Math.max(0, viewportHeight - linesUsed);
505
- if (tasks.length === 0) {
506
- return (react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column", paddingLeft: 2 },
507
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "No tasks in this column"),
508
- Array.from({ length: viewportHeight - 1 }).map((_, i) => (react_1.default.createElement(ink_1.Box, { key: `empty-${i}` },
509
- react_1.default.createElement(ink_1.Text, null, ' '))))));
510
- }
511
- return (react_1.default.createElement(ink_1.Box, { flexGrow: 1, flexDirection: "column", paddingLeft: 1 },
512
- visibleItems.map(({ task, isSelected, isExpanded }) => (react_1.default.createElement(TaskCard, { key: task.id, task: task, isSelected: isSelected, isExpanded: isExpanded, width: cardWidth }))),
513
- Array.from({ length: emptyLines }).map((_, i) => (react_1.default.createElement(ink_1.Box, { key: `empty-${i}` },
514
- react_1.default.createElement(ink_1.Text, null, ' '))))));
515
- }
516
- function TaskCard({ task, isSelected, isExpanded, width }) {
517
- const priorityColor = getPriorityColor(task.priority);
518
- const priorityLabel = task.priority?.toUpperCase() || '';
519
- const tags = task.tags || [];
520
- const subtasks = task.subtasks || [];
521
- const completedSubtasks = subtasks.filter(s => s.completed).length;
522
- // Arrow indicator: colored when selected
523
- const arrow = isExpanded ? '▼' : '▶';
524
- const arrowColor = isSelected ? PALETTE.progress : PALETTE.textMuted;
525
- const titleWidth = width - task.id.length - 10;
526
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
527
- react_1.default.createElement(ink_1.Box, null,
528
- react_1.default.createElement(ink_1.Text, { color: arrowColor, bold: isSelected }, arrow),
529
- react_1.default.createElement(ink_1.Text, null, " "),
530
- react_1.default.createElement(ink_1.Text, { color: isSelected ? PALETTE.text : PALETTE.textMuted, inverse: isSelected, bold: isSelected },
531
- ' ',
532
- truncate(task.title, titleWidth),
533
- ' '),
534
- react_1.default.createElement(ink_1.Text, null, " "),
535
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, task.id)),
536
- react_1.default.createElement(ink_1.Box, { paddingLeft: 3 },
537
- react_1.default.createElement(ink_1.Text, { color: priorityColor }, priorityLabel || ' '),
538
- tags.length > 0 ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true },
539
- ' ',
540
- String.fromCharCode(0xB7),
541
- " ",
542
- tags.slice(0, 4).join(` ${String.fromCharCode(0xB7)} `))) : null),
543
- isExpanded && (react_1.default.createElement(ExpandedTaskContent, { task: task, priorityColor: priorityColor, completedSubtasks: completedSubtasks, totalSubtasks: subtasks.length, width: width }))));
544
- }
545
- function ExpandedTaskContent({ task, priorityColor, completedSubtasks, totalSubtasks, width }) {
546
- const subtasks = task.subtasks || [];
547
- const innerWidth = width - 6;
548
- // Build content lines - all with consistent left border
549
- const lines = [];
550
- // Empty line for spacing
551
- lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, ' '));
552
- if (task.description) {
553
- lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, truncate(task.description.split('\n')[0], innerWidth)));
554
- }
555
- if (totalSubtasks > 0) {
556
- // Empty line before subtasks if there was a description
557
- if (task.description) {
558
- lines.push(react_1.default.createElement(ink_1.Text, null, ' '));
559
- }
560
- lines.push(react_1.default.createElement(react_1.default.Fragment, null,
561
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, "Subtasks: "),
562
- react_1.default.createElement(ink_1.Text, { color: PALETTE.success }, '●'.repeat(Math.min(completedSubtasks, 10))),
563
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border }, '○'.repeat(Math.min(totalSubtasks - completedSubtasks, 10))),
564
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted },
565
- " ",
566
- completedSubtasks,
567
- "/",
568
- totalSubtasks)));
569
- subtasks.slice(0, 4).forEach((st) => {
570
- lines.push(react_1.default.createElement(ink_1.Text, { color: st.completed ? PALETTE.success : PALETTE.textMuted },
571
- st.completed ? '✓' : '○',
572
- " ",
573
- truncate(st.title, innerWidth - 2)));
574
- });
575
- }
576
- if (task.relatedFiles && task.relatedFiles.length > 0) {
577
- // Empty line before files
578
- lines.push(react_1.default.createElement(ink_1.Text, null, ' '));
579
- lines.push(react_1.default.createElement(ink_1.Text, { color: PALETTE.medium },
580
- "Files: ",
581
- truncate(task.relatedFiles.slice(0, 2).join(', '), innerWidth - 7)));
582
- }
583
- if (lines.length === 0)
584
- return null;
585
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" }, lines.map((content, idx) => (react_1.default.createElement(ink_1.Box, { key: idx, paddingLeft: 3 },
586
- react_1.default.createElement(ink_1.Text, { color: priorityColor }, BOX.vertical),
587
- react_1.default.createElement(ink_1.Text, null, " "),
588
- content)))));
589
- }
590
- function StatusBar({ mode, columnName, taskIndex, taskCount, termWidth, isWatching = true }) {
591
- // Left section: essential commands
592
- const leftSection = mode === 'search' ? 'ESC:cancel' : '?:help TAB:column q:quit';
593
- // Middle section: column and position
594
- const position = taskCount > 0 ? `${taskIndex}/${taskCount}` : '';
595
- const middleSection = columnName ? `${columnName.toUpperCase()} ${position}` : '';
596
- // Right section: status
597
- const rightSection = isWatching ? '● live' : '';
598
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", width: termWidth },
599
- react_1.default.createElement(ink_1.Box, { width: termWidth - 2, paddingLeft: 1, paddingRight: 1 },
600
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, leftSection),
601
- react_1.default.createElement(ink_1.Box, { flexGrow: 1 }),
602
- middleSection ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.progress }, middleSection)) : null,
603
- react_1.default.createElement(ink_1.Box, { flexGrow: 1 }),
604
- rightSection ? (react_1.default.createElement(ink_1.Text, { color: PALETTE.success, dimColor: true }, rightSection)) : null),
605
- react_1.default.createElement(ink_1.Box, { paddingLeft: 1 },
606
- react_1.default.createElement(ink_1.Text, null, ' '))));
607
- }
608
- function HelpOverlay({ termWidth }) {
609
- const boxWidth = Math.min(termWidth - 4, 56);
610
- const hr = BOX.horizontal.repeat(boxWidth - 2);
611
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 2 },
612
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
613
- BOX.topLeft,
614
- BOX.horizontal,
615
- " KEYBOARD SHORTCUTS ",
616
- BOX.horizontal.repeat(boxWidth - 22),
617
- BOX.topRight),
618
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
619
- BOX.vertical,
620
- ' '.repeat(boxWidth - 2),
621
- BOX.vertical),
622
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
623
- BOX.vertical,
624
- " Navigation",
625
- ' '.repeat(boxWidth - 14),
626
- BOX.vertical),
627
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
628
- BOX.vertical,
629
- " ",
630
- BOX.horizontal.repeat(boxWidth - 6),
631
- " ",
632
- BOX.vertical),
633
- react_1.default.createElement(HelpRow, { label: "j/k or \u2193/\u2191", desc: "Move up/down", width: boxWidth }),
634
- react_1.default.createElement(HelpRow, { label: "h/l or \u2190/\u2192", desc: "Switch columns", width: boxWidth }),
635
- react_1.default.createElement(HelpRow, { label: "TAB", desc: "Next column", width: boxWidth }),
636
- react_1.default.createElement(HelpRow, { label: "g / G", desc: "Top / Bottom", width: boxWidth }),
637
- react_1.default.createElement(HelpRow, { label: "Ctrl+d/u", desc: "Page down/up", width: boxWidth }),
638
- react_1.default.createElement(HelpRow, { label: "{ / }", desc: "Prev/next column", width: boxWidth }),
639
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
640
- BOX.vertical,
641
- ' '.repeat(boxWidth - 2),
642
- BOX.vertical),
643
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
644
- BOX.vertical,
645
- " Actions",
646
- ' '.repeat(boxWidth - 11),
647
- BOX.vertical),
648
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
649
- BOX.vertical,
650
- " ",
651
- BOX.horizontal.repeat(boxWidth - 6),
652
- " ",
653
- BOX.vertical),
654
- react_1.default.createElement(HelpRow, { label: "ENTER", desc: "Expand/collapse task", width: boxWidth }),
655
- react_1.default.createElement(HelpRow, { label: "/", desc: "Search/filter", width: boxWidth }),
656
- react_1.default.createElement(HelpRow, { label: "r", desc: "Refresh from file", width: boxWidth }),
657
- react_1.default.createElement(HelpRow, { label: "ESC", desc: "Clear/collapse", width: boxWidth }),
658
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
659
- BOX.vertical,
660
- ' '.repeat(boxWidth - 2),
661
- BOX.vertical),
662
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
663
- BOX.vertical,
664
- " Indicators",
665
- ' '.repeat(boxWidth - 14),
666
- BOX.vertical),
667
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
668
- BOX.vertical,
669
- " ",
670
- BOX.horizontal.repeat(boxWidth - 6),
671
- " ",
672
- BOX.vertical),
673
- react_1.default.createElement(HelpRow, { label: "\u25B6", desc: "Collapsed task", width: boxWidth }),
674
- react_1.default.createElement(HelpRow, { label: "\u25BC", desc: "Expanded task", width: boxWidth }),
675
- react_1.default.createElement(HelpRow, { label: "[inverse]", desc: "Selected task", width: boxWidth }),
676
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
677
- BOX.vertical,
678
- ' '.repeat(boxWidth - 2),
679
- BOX.vertical),
680
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
681
- BOX.vertical,
682
- " Quit",
683
- ' '.repeat(boxWidth - 7),
684
- BOX.vertical),
685
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
686
- BOX.vertical,
687
- " ",
688
- BOX.horizontal.repeat(boxWidth - 6),
689
- " ",
690
- BOX.vertical),
691
- react_1.default.createElement(HelpRow, { label: "q / Ctrl+C", desc: "Exit", width: boxWidth }),
692
- react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
693
- BOX.vertical,
694
- ' '.repeat(boxWidth - 2),
695
- BOX.vertical),
696
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text, bold: true },
697
- BOX.bottomLeft,
698
- hr,
699
- BOX.bottomRight),
700
- react_1.default.createElement(ink_1.Box, { marginTop: 1 },
701
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted, dimColor: true }, "Press any key to close"))));
702
- }
703
- function HelpRow({ label, desc, width }) {
704
- const padding = width - 6 - label.length - desc.length;
705
- return (react_1.default.createElement(ink_1.Text, { color: PALETTE.border },
706
- BOX.vertical,
707
- " ",
708
- react_1.default.createElement(ink_1.Text, { color: PALETTE.text }, label),
709
- ' '.repeat(Math.max(padding, 2)),
710
- react_1.default.createElement(ink_1.Text, { color: PALETTE.textMuted }, desc),
711
- " ",
712
- BOX.vertical));
713
- }
714
- // ---------------------------------------------------------------------------
715
- // Helper functions
716
- // ---------------------------------------------------------------------------
717
- function truncate(value, maxLength) {
718
- if (!value)
719
- return '';
720
- if (value.length <= maxLength)
721
- return value;
722
- return `${value.slice(0, maxLength - 1)}…`;
723
- }
724
- function getPriorityColor(priority) {
725
- switch (priority?.toLowerCase()) {
726
- case 'critical':
727
- return PALETTE.critical;
728
- case 'giga':
729
- return PALETTE.giga;
730
- case 'high':
731
- return PALETTE.high;
732
- case 'medium':
733
- return PALETTE.medium;
734
- case 'low':
735
- return PALETTE.low;
736
- default:
737
- return PALETTE.border;
738
- }
739
- }
44
+ const index_js_1 = require("../tui/index.js");
740
45
  function tuiCommand(options) {
741
46
  const filePath = path.resolve(options.file);
742
47
  if (!fs.existsSync(filePath)) {
@@ -753,6 +58,6 @@ function tuiCommand(options) {
753
58
  console.log('Please run this command in a standard terminal (not piped or in a non-TTY context).');
754
59
  process.exit(1);
755
60
  }
756
- (0, ink_1.render)(react_1.default.createElement(BrainfileTUI, { filePath: filePath }));
61
+ (0, ink_1.render)(react_1.default.createElement(index_js_1.BrainfileTUI, { filePath: filePath }));
757
62
  }
758
63
  //# sourceMappingURL=tui.js.map