@compilr-dev/cli 0.4.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 (152) hide show
  1. package/README.md +110 -0
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agent.js +317 -0
  4. package/dist/agents/registry.d.ts +66 -0
  5. package/dist/agents/registry.js +238 -0
  6. package/dist/agents/types.d.ts +40 -0
  7. package/dist/agents/types.js +94 -0
  8. package/dist/commands/custom-registry.d.ts +69 -0
  9. package/dist/commands/custom-registry.js +246 -0
  10. package/dist/commands/index.d.ts +7 -0
  11. package/dist/commands/index.js +7 -0
  12. package/dist/commands/types.d.ts +31 -0
  13. package/dist/commands/types.js +26 -0
  14. package/dist/commands.d.ts +63 -0
  15. package/dist/commands.js +324 -0
  16. package/dist/db/index.d.ts +42 -0
  17. package/dist/db/index.js +146 -0
  18. package/dist/db/repositories/document-repository.d.ts +63 -0
  19. package/dist/db/repositories/document-repository.js +184 -0
  20. package/dist/db/repositories/index.d.ts +9 -0
  21. package/dist/db/repositories/index.js +6 -0
  22. package/dist/db/repositories/project-repository.d.ts +132 -0
  23. package/dist/db/repositories/project-repository.js +337 -0
  24. package/dist/db/repositories/work-item-repository.d.ts +115 -0
  25. package/dist/db/repositories/work-item-repository.js +389 -0
  26. package/dist/db/schema.d.ts +83 -0
  27. package/dist/db/schema.js +143 -0
  28. package/dist/debug.d.ts +8 -0
  29. package/dist/debug.js +48 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +348 -0
  32. package/dist/index.old.d.ts +7 -0
  33. package/dist/index.old.js +1014 -0
  34. package/dist/repl.d.ts +121 -0
  35. package/dist/repl.js +1878 -0
  36. package/dist/settings/index.d.ts +80 -0
  37. package/dist/settings/index.js +195 -0
  38. package/dist/shared-handlers.d.ts +63 -0
  39. package/dist/shared-handlers.js +57 -0
  40. package/dist/slash-autocomplete.d.ts +41 -0
  41. package/dist/slash-autocomplete.js +638 -0
  42. package/dist/state.d.ts +75 -0
  43. package/dist/state.js +130 -0
  44. package/dist/tabbed-menu.d.ts +11 -0
  45. package/dist/tabbed-menu.js +328 -0
  46. package/dist/templates/backlog-md.d.ts +7 -0
  47. package/dist/templates/backlog-md.js +94 -0
  48. package/dist/templates/claude-md.d.ts +7 -0
  49. package/dist/templates/claude-md.js +189 -0
  50. package/dist/templates/coding-standards.d.ts +7 -0
  51. package/dist/templates/coding-standards.js +299 -0
  52. package/dist/templates/compilr-md.d.ts +7 -0
  53. package/dist/templates/compilr-md.js +189 -0
  54. package/dist/templates/config-json.d.ts +38 -0
  55. package/dist/templates/config-json.js +39 -0
  56. package/dist/templates/gitignore.d.ts +7 -0
  57. package/dist/templates/gitignore.js +85 -0
  58. package/dist/templates/index.d.ts +19 -0
  59. package/dist/templates/index.js +302 -0
  60. package/dist/templates/package-json.d.ts +7 -0
  61. package/dist/templates/package-json.js +111 -0
  62. package/dist/templates/readme-md.d.ts +7 -0
  63. package/dist/templates/readme-md.js +161 -0
  64. package/dist/templates/tsconfig.d.ts +7 -0
  65. package/dist/templates/tsconfig.js +61 -0
  66. package/dist/templates/types.d.ts +33 -0
  67. package/dist/templates/types.js +24 -0
  68. package/dist/test-autocomplete.d.ts +7 -0
  69. package/dist/test-autocomplete.js +85 -0
  70. package/dist/test-tabbed-menu.d.ts +7 -0
  71. package/dist/test-tabbed-menu.js +25 -0
  72. package/dist/themes/colors.d.ts +49 -0
  73. package/dist/themes/colors.js +135 -0
  74. package/dist/themes/index.d.ts +23 -0
  75. package/dist/themes/index.js +24 -0
  76. package/dist/themes/registry.d.ts +60 -0
  77. package/dist/themes/registry.js +195 -0
  78. package/dist/themes/types.d.ts +82 -0
  79. package/dist/themes/types.js +7 -0
  80. package/dist/tool-selector.d.ts +71 -0
  81. package/dist/tool-selector.js +184 -0
  82. package/dist/tools/ask-user-simple.d.ts +19 -0
  83. package/dist/tools/ask-user-simple.js +86 -0
  84. package/dist/tools/ask-user.d.ts +32 -0
  85. package/dist/tools/ask-user.js +113 -0
  86. package/dist/tools/backlog.d.ts +53 -0
  87. package/dist/tools/backlog.js +709 -0
  88. package/dist/tools.d.ts +15 -0
  89. package/dist/tools.js +121 -0
  90. package/dist/ui/agents-overlay.d.ts +12 -0
  91. package/dist/ui/agents-overlay.js +501 -0
  92. package/dist/ui/arch-type-overlay.d.ts +20 -0
  93. package/dist/ui/arch-type-overlay.js +229 -0
  94. package/dist/ui/ask-user-overlay.d.ts +26 -0
  95. package/dist/ui/ask-user-overlay.js +647 -0
  96. package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
  97. package/dist/ui/ask-user-simple-overlay.js +242 -0
  98. package/dist/ui/backlog-overlay.d.ts +17 -0
  99. package/dist/ui/backlog-overlay.js +786 -0
  100. package/dist/ui/commands-overlay.d.ts +11 -0
  101. package/dist/ui/commands-overlay.js +410 -0
  102. package/dist/ui/config-overlay.d.ts +34 -0
  103. package/dist/ui/config-overlay.js +977 -0
  104. package/dist/ui/conversation.d.ts +82 -0
  105. package/dist/ui/conversation.js +508 -0
  106. package/dist/ui/diff.d.ts +38 -0
  107. package/dist/ui/diff.js +182 -0
  108. package/dist/ui/ephemeral.d.ts +111 -0
  109. package/dist/ui/ephemeral.js +413 -0
  110. package/dist/ui/file-autocomplete.d.ts +45 -0
  111. package/dist/ui/file-autocomplete.js +237 -0
  112. package/dist/ui/footer.d.ts +153 -0
  113. package/dist/ui/footer.js +422 -0
  114. package/dist/ui/index.d.ts +12 -0
  115. package/dist/ui/index.js +15 -0
  116. package/dist/ui/init-overlay.d.ts +24 -0
  117. package/dist/ui/init-overlay.js +525 -0
  118. package/dist/ui/input-prompt-v2.d.ts +179 -0
  119. package/dist/ui/input-prompt-v2.js +991 -0
  120. package/dist/ui/input-prompt.d.ts +97 -0
  121. package/dist/ui/input-prompt.js +800 -0
  122. package/dist/ui/iteration-limit-overlay.d.ts +21 -0
  123. package/dist/ui/iteration-limit-overlay.js +150 -0
  124. package/dist/ui/keys-overlay.d.ts +14 -0
  125. package/dist/ui/keys-overlay.js +181 -0
  126. package/dist/ui/model-warning-overlay.d.ts +30 -0
  127. package/dist/ui/model-warning-overlay.js +171 -0
  128. package/dist/ui/overlay-controller.d.ts +25 -0
  129. package/dist/ui/overlay-controller.js +35 -0
  130. package/dist/ui/overlays.d.ts +47 -0
  131. package/dist/ui/overlays.js +627 -0
  132. package/dist/ui/permission-overlay.d.ts +16 -0
  133. package/dist/ui/permission-overlay.js +494 -0
  134. package/dist/ui/terminal.d.ts +117 -0
  135. package/dist/ui/terminal.js +237 -0
  136. package/dist/ui/todo-zone.d.ts +112 -0
  137. package/dist/ui/todo-zone.js +353 -0
  138. package/dist/ui/tools-overlay.d.ts +26 -0
  139. package/dist/ui/tools-overlay.js +278 -0
  140. package/dist/ui/tutorial-overlay.d.ts +10 -0
  141. package/dist/ui/tutorial-overlay.js +936 -0
  142. package/dist/ui/types.d.ts +103 -0
  143. package/dist/ui/types.js +33 -0
  144. package/dist/utils/credentials.d.ts +55 -0
  145. package/dist/utils/credentials.js +268 -0
  146. package/dist/utils/model-tiers.d.ts +37 -0
  147. package/dist/utils/model-tiers.js +118 -0
  148. package/dist/utils/project-memory.d.ts +47 -0
  149. package/dist/utils/project-memory.js +117 -0
  150. package/dist/utils/project-status.d.ts +56 -0
  151. package/dist/utils/project-status.js +237 -0
  152. package/package.json +66 -0
@@ -0,0 +1,627 @@
1
+ /**
2
+ * Overlays
3
+ *
4
+ * Modal overlays that temporarily take over the screen.
5
+ * - Help menu
6
+ * - Confirmation dialogs
7
+ * - Permission prompts
8
+ * - Context stats
9
+ */
10
+ import chalk from 'chalk';
11
+ import * as terminal from './terminal.js';
12
+ import { getHelpCommands, getCommandByIndex } from '../commands.js';
13
+ import { getCustomCommandRegistry } from '../commands/index.js';
14
+ import { getStyles } from '../themes/index.js';
15
+ // =============================================================================
16
+ // Version
17
+ // =============================================================================
18
+ const VERSION = 'compilr.dev/agent';
19
+ const TABS = [
20
+ { id: 'general', label: 'general' },
21
+ { id: 'commands', label: 'commands' },
22
+ { id: 'custom-commands', label: 'custom-commands' },
23
+ ];
24
+ // Use central command registry
25
+ const COMMANDS = getHelpCommands();
26
+ const MAX_VISIBLE_COMMANDS = 10; // Maximum commands to show at once
27
+ function renderHelpHeader(state) {
28
+ const s = getStyles();
29
+ const lines = [];
30
+ const cols = terminal.getTerminalWidth();
31
+ lines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
32
+ let tabLine = ' ' + s.primaryBold(VERSION) + ' ';
33
+ for (let i = 0; i < TABS.length; i++) {
34
+ const tab = TABS[i];
35
+ if (i === state.currentTab) {
36
+ tabLine += s.selected(` ${tab.label} `) + ' ';
37
+ }
38
+ else {
39
+ tabLine += s.muted(` ${tab.label} `) + ' ';
40
+ }
41
+ }
42
+ tabLine += s.muted('(tab to cycle)');
43
+ lines.push(tabLine);
44
+ lines.push('');
45
+ return lines;
46
+ }
47
+ function renderGeneralTab() {
48
+ const s = getStyles();
49
+ const lines = [];
50
+ lines.push(' An AI assistant that understands your codebase, makes edits with your permission, and executes commands — right from your terminal.');
51
+ lines.push('');
52
+ lines.push(chalk.bold(' Shortcuts'));
53
+ const shortcuts = [
54
+ ['/ for commands', 'Enter to submit'],
55
+ ['↑↓ for history', 'Esc to cancel'],
56
+ ['Tab to autocomplete', 'Ctrl+C to exit'],
57
+ ];
58
+ for (const [left, right] of shortcuts) {
59
+ lines.push(' ' + s.primary(left.padEnd(25)) + s.primary(right));
60
+ }
61
+ lines.push('');
62
+ lines.push(chalk.bold(' Features'));
63
+ lines.push(' ' + s.muted('• Multi-LLM support (Claude, OpenAI, Gemini, Ollama)'));
64
+ lines.push(' ' + s.muted('• Tool execution with permission prompts'));
65
+ lines.push(' ' + s.muted('• Context management and compaction'));
66
+ lines.push(' ' + s.muted('• Slash command autocomplete'));
67
+ lines.push(''); // Padding to match commands tab height
68
+ lines.push('');
69
+ return lines;
70
+ }
71
+ function renderCommandsTab(state) {
72
+ const s = getStyles();
73
+ const lines = [];
74
+ const totalCommands = COMMANDS.length;
75
+ const visibleCount = Math.min(MAX_VISIBLE_COMMANDS, totalCommands);
76
+ lines.push(chalk.bold(' Browse available commands:') + s.muted(` (${String(totalCommands)} total)`));
77
+ lines.push('');
78
+ // Show up arrow if can scroll up
79
+ if (state.scrollOffset > 0) {
80
+ lines.push(s.muted(' ↑ more above'));
81
+ }
82
+ else {
83
+ lines.push('');
84
+ }
85
+ const visibleCommands = COMMANDS.slice(state.scrollOffset, state.scrollOffset + visibleCount);
86
+ for (let i = 0; i < visibleCommands.length; i++) {
87
+ const cmd = visibleCommands[i];
88
+ const globalIndex = state.scrollOffset + i;
89
+ const isSelected = globalIndex === state.selectedCommand;
90
+ const prefix = isSelected ? s.primary(' ❯ ') : ' ';
91
+ const name = isSelected ? s.primary(cmd.name.padEnd(20)) : s.muted(cmd.name.padEnd(20));
92
+ const desc = s.muted(cmd.description);
93
+ lines.push(prefix + name + desc);
94
+ }
95
+ // Pad to fixed height when fewer commands than max
96
+ for (let i = visibleCommands.length; i < visibleCount; i++) {
97
+ lines.push('');
98
+ }
99
+ // Show down arrow if can scroll down
100
+ if (state.scrollOffset + visibleCount < totalCommands) {
101
+ lines.push(s.muted(' ↓ more below'));
102
+ }
103
+ else {
104
+ lines.push('');
105
+ }
106
+ return lines;
107
+ }
108
+ function renderCustomCommandsTab(state) {
109
+ const s = getStyles();
110
+ const lines = [];
111
+ const registry = getCustomCommandRegistry();
112
+ const commands = registry.getAll();
113
+ const totalCommands = commands.length;
114
+ const visibleCount = Math.min(MAX_VISIBLE_COMMANDS, totalCommands);
115
+ lines.push(chalk.bold(' Custom commands:') + (totalCommands > 0 ? s.muted(` (${String(totalCommands)} total)`) : ''));
116
+ lines.push('');
117
+ if (commands.length === 0) {
118
+ lines.push(s.muted(' No custom commands found'));
119
+ lines.push('');
120
+ lines.push(s.muted(' Create commands with /commands'));
121
+ lines.push(s.muted(' or add .md files to:'));
122
+ lines.push(s.muted(` ${registry.getProjectDir()}/`));
123
+ lines.push(s.muted(` ${registry.getUserDir()}/`));
124
+ // Pad to match expected height
125
+ for (let i = 0; i < 6; i++) {
126
+ lines.push('');
127
+ }
128
+ }
129
+ else {
130
+ // Show up arrow if can scroll up
131
+ if (state.customScrollOffset > 0) {
132
+ lines.push(s.muted(' ↑ more above'));
133
+ }
134
+ else {
135
+ lines.push('');
136
+ }
137
+ const visibleCommands = commands.slice(state.customScrollOffset, state.customScrollOffset + visibleCount);
138
+ for (let i = 0; i < visibleCommands.length; i++) {
139
+ const cmd = visibleCommands[i];
140
+ const globalIndex = state.customScrollOffset + i;
141
+ const isSelected = globalIndex === state.selectedCustomCommand;
142
+ const prefix = isSelected ? s.primary(' ❯ ') : ' ';
143
+ const name = `/${cmd.name}`.padEnd(18);
144
+ const desc = cmd.description.slice(0, 35) + (cmd.description.length > 35 ? '...' : '');
145
+ const location = ` (${cmd.location})`;
146
+ if (isSelected) {
147
+ lines.push(prefix + s.primary(name) + s.muted(desc) + s.muted(location));
148
+ }
149
+ else {
150
+ lines.push(prefix + s.muted(name + desc + location));
151
+ }
152
+ }
153
+ // Pad to fixed height when fewer commands than max
154
+ for (let i = visibleCommands.length; i < visibleCount; i++) {
155
+ lines.push('');
156
+ }
157
+ // Show down arrow if can scroll down
158
+ if (state.customScrollOffset + visibleCount < totalCommands) {
159
+ lines.push(s.muted(' ↓ more below'));
160
+ }
161
+ else {
162
+ lines.push('');
163
+ }
164
+ lines.push('');
165
+ lines.push(s.muted(' Use /commands to manage custom commands'));
166
+ }
167
+ return lines;
168
+ }
169
+ function renderCommandDetail(commandIndex) {
170
+ const s = getStyles();
171
+ const lines = [];
172
+ const cols = terminal.getTerminalWidth();
173
+ const maxLineWidth = cols - 2; // Leave margin for safety
174
+ const cmd = getCommandByIndex(commandIndex);
175
+ // Helper to truncate a line to prevent wrapping
176
+ const truncate = (line, max = maxLineWidth) => {
177
+ // Strip ANSI codes for length calculation
178
+ // eslint-disable-next-line no-control-regex
179
+ const stripped = line.replace(/\x1b\[[0-9;]*m/g, '');
180
+ if (stripped.length <= max)
181
+ return line;
182
+ // Find where to cut (accounting for ANSI codes)
183
+ let visibleLen = 0;
184
+ let cutIndex = 0;
185
+ for (let i = 0; i < line.length; i++) {
186
+ if (line[i] === '\x1b') {
187
+ // Skip ANSI sequence
188
+ const end = line.indexOf('m', i);
189
+ if (end !== -1) {
190
+ i = end;
191
+ continue;
192
+ }
193
+ }
194
+ visibleLen++;
195
+ if (visibleLen >= max - 3) {
196
+ cutIndex = i + 1;
197
+ break;
198
+ }
199
+ }
200
+ return line.slice(0, cutIndex) + '...';
201
+ };
202
+ if (!cmd) {
203
+ lines.push(s.error(' Command not found'));
204
+ return lines;
205
+ }
206
+ // Command name header
207
+ lines.push(chalk.bold(` /${cmd.name}`));
208
+ lines.push('');
209
+ // Description
210
+ lines.push(' ' + s.primary('DESCRIPTION'));
211
+ lines.push(truncate(' ' + cmd.description));
212
+ lines.push('');
213
+ // Details (word-wrapped)
214
+ if (cmd.details) {
215
+ lines.push(' ' + s.primary('ABOUT'));
216
+ const maxWidth = Math.min(cols - 6, 70); // Cap at 70 chars for readability
217
+ const words = cmd.details.split(' ');
218
+ let currentLine = ' ';
219
+ for (const word of words) {
220
+ if (currentLine.length + word.length + 1 > maxWidth) {
221
+ lines.push(truncate(currentLine));
222
+ currentLine = ' ' + word;
223
+ }
224
+ else {
225
+ currentLine += (currentLine.length > 3 ? ' ' : '') + word;
226
+ }
227
+ }
228
+ if (currentLine.length > 3) {
229
+ lines.push(truncate(currentLine));
230
+ }
231
+ lines.push('');
232
+ }
233
+ // Aliases
234
+ if (cmd.aliases && cmd.aliases.length > 0) {
235
+ lines.push(' ' + s.primary('ALIASES'));
236
+ lines.push(truncate(' ' + cmd.aliases.map(a => `/${a}`).join(', ')));
237
+ lines.push('');
238
+ }
239
+ // Examples (structured format)
240
+ if (cmd.examples && cmd.examples.length > 0) {
241
+ lines.push(' ' + s.primary('EXAMPLES'));
242
+ for (const example of cmd.examples) {
243
+ if (example.description) {
244
+ // Format: /command · description
245
+ // Pad the raw string before applying styles
246
+ const paddedCode = example.code.padEnd(22);
247
+ const exLine = ` ${s.muted(paddedCode)}${s.muted('·')} ${example.description}`;
248
+ lines.push(truncate(exLine));
249
+ }
250
+ else {
251
+ lines.push(truncate(' ' + s.muted(example.code)));
252
+ }
253
+ }
254
+ lines.push('');
255
+ }
256
+ // Interactions (new section)
257
+ if (cmd.interactions && cmd.interactions.length > 0) {
258
+ lines.push(' ' + s.primary('INTERACTIONS'));
259
+ for (const hint of cmd.interactions) {
260
+ lines.push(truncate(' ' + s.muted('• ' + hint)));
261
+ }
262
+ lines.push('');
263
+ }
264
+ // Pad to consistent height (at least 12 lines for content)
265
+ while (lines.length < 12) {
266
+ lines.push('');
267
+ }
268
+ return lines;
269
+ }
270
+ function renderHelpFooter(showingDetail) {
271
+ const s = getStyles();
272
+ const lines = [];
273
+ lines.push('');
274
+ if (showingDetail) {
275
+ lines.push(s.muted(' Esc to go back'));
276
+ }
277
+ else {
278
+ lines.push(s.muted(' For more help: https://github.com/scozzola/compilr-dev-cli'));
279
+ lines.push('');
280
+ lines.push(s.muted(' Enter for details · Esc to exit'));
281
+ }
282
+ return lines;
283
+ }
284
+ function buildHelpLines(state) {
285
+ const allLines = [];
286
+ const s = getStyles();
287
+ const cols = terminal.getTerminalWidth();
288
+ // If showing command detail, render that instead
289
+ if (state.showingDetail) {
290
+ allLines.push(s.muted('─'.repeat(Math.max(1, cols - 1))));
291
+ allLines.push(' ' + s.primaryBold('Command Reference'));
292
+ allLines.push('');
293
+ allLines.push(...renderCommandDetail(state.detailCommandIndex));
294
+ allLines.push(...renderHelpFooter(true));
295
+ return allLines;
296
+ }
297
+ allLines.push(...renderHelpHeader(state));
298
+ switch (TABS[state.currentTab].id) {
299
+ case 'general':
300
+ allLines.push(...renderGeneralTab());
301
+ break;
302
+ case 'commands':
303
+ allLines.push(...renderCommandsTab(state));
304
+ break;
305
+ case 'custom-commands':
306
+ allLines.push(...renderCustomCommandsTab(state));
307
+ break;
308
+ }
309
+ allLines.push(...renderHelpFooter(false));
310
+ return allLines;
311
+ }
312
+ function renderHelpMenu(state, prevLineCount) {
313
+ const lines = buildHelpLines(state);
314
+ if (prevLineCount > 0) {
315
+ terminal.moveCursorToLineStart();
316
+ if (prevLineCount > 1) {
317
+ terminal.moveCursorUp(prevLineCount - 1);
318
+ }
319
+ terminal.clearToEndOfScreen();
320
+ }
321
+ terminal.write(lines.join('\n'));
322
+ return lines.length;
323
+ }
324
+ /**
325
+ * Show the tabbed help menu
326
+ */
327
+ export function showHelp() {
328
+ return new Promise((resolve) => {
329
+ const state = {
330
+ currentTab: 0,
331
+ selectedCommand: 0,
332
+ scrollOffset: 0,
333
+ selectedCustomCommand: 0,
334
+ customScrollOffset: 0,
335
+ showingDetail: false,
336
+ detailCommandIndex: 0,
337
+ };
338
+ let lineCount = 0;
339
+ terminal.writeLine('');
340
+ terminal.hideCursor();
341
+ const wasRawMode = process.stdin.isRaw;
342
+ terminal.enableRawMode();
343
+ lineCount = renderHelpMenu(state, 0);
344
+ const cleanup = () => {
345
+ if (lineCount > 0) {
346
+ terminal.moveCursorUp(lineCount);
347
+ terminal.clearToEndOfScreen();
348
+ }
349
+ terminal.writeLine('');
350
+ terminal.showCursor();
351
+ if (!wasRawMode) {
352
+ terminal.disableRawMode();
353
+ }
354
+ process.stdin.removeListener('data', onData);
355
+ process.stdout.removeListener('resize', onResize);
356
+ };
357
+ const onData = (data) => {
358
+ const isEscape = data.length === 1 && data[0] === 0x1b;
359
+ const isTab = data.length === 1 && data[0] === 0x09;
360
+ const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
361
+ const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
362
+ const isCtrlC = data.length === 1 && data[0] === 0x03;
363
+ const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
364
+ // Ctrl+C always exits
365
+ if (isCtrlC) {
366
+ cleanup();
367
+ resolve();
368
+ return;
369
+ }
370
+ // If showing command detail view
371
+ if (state.showingDetail) {
372
+ if (isEscape) {
373
+ // Go back to commands list
374
+ state.showingDetail = false;
375
+ lineCount = renderHelpMenu(state, lineCount);
376
+ return;
377
+ }
378
+ // Ignore other keys in detail view
379
+ return;
380
+ }
381
+ // Escape exits the help menu (when not in detail view)
382
+ if (isEscape) {
383
+ cleanup();
384
+ resolve();
385
+ return;
386
+ }
387
+ // Enter shows command detail (for commands tab only)
388
+ if (isEnter && TABS[state.currentTab].id === 'commands') {
389
+ state.showingDetail = true;
390
+ state.detailCommandIndex = state.selectedCommand;
391
+ lineCount = renderHelpMenu(state, lineCount);
392
+ return;
393
+ }
394
+ if (isTab) {
395
+ state.currentTab = (state.currentTab + 1) % TABS.length;
396
+ // Reset scroll when switching tabs
397
+ if (TABS[state.currentTab].id === 'commands') {
398
+ state.selectedCommand = 0;
399
+ state.scrollOffset = 0;
400
+ }
401
+ else if (TABS[state.currentTab].id === 'custom-commands') {
402
+ state.selectedCustomCommand = 0;
403
+ state.customScrollOffset = 0;
404
+ }
405
+ lineCount = renderHelpMenu(state, lineCount);
406
+ return;
407
+ }
408
+ // Handle arrow navigation for commands tab
409
+ if (TABS[state.currentTab].id === 'commands') {
410
+ const visibleCount = Math.min(MAX_VISIBLE_COMMANDS, COMMANDS.length);
411
+ if (isUpArrow && state.selectedCommand > 0) {
412
+ state.selectedCommand--;
413
+ if (state.selectedCommand < state.scrollOffset) {
414
+ state.scrollOffset = state.selectedCommand;
415
+ }
416
+ lineCount = renderHelpMenu(state, lineCount);
417
+ }
418
+ if (isDownArrow && state.selectedCommand < COMMANDS.length - 1) {
419
+ state.selectedCommand++;
420
+ if (state.selectedCommand >= state.scrollOffset + visibleCount) {
421
+ state.scrollOffset = state.selectedCommand - visibleCount + 1;
422
+ }
423
+ lineCount = renderHelpMenu(state, lineCount);
424
+ }
425
+ }
426
+ // Handle arrow navigation for custom commands tab
427
+ if (TABS[state.currentTab].id === 'custom-commands') {
428
+ const registry = getCustomCommandRegistry();
429
+ const customCommands = registry.getAll();
430
+ const totalCustom = customCommands.length;
431
+ if (totalCustom > 0) {
432
+ const visibleCount = Math.min(MAX_VISIBLE_COMMANDS, totalCustom);
433
+ if (isUpArrow && state.selectedCustomCommand > 0) {
434
+ state.selectedCustomCommand--;
435
+ if (state.selectedCustomCommand < state.customScrollOffset) {
436
+ state.customScrollOffset = state.selectedCustomCommand;
437
+ }
438
+ lineCount = renderHelpMenu(state, lineCount);
439
+ }
440
+ if (isDownArrow && state.selectedCustomCommand < totalCustom - 1) {
441
+ state.selectedCustomCommand++;
442
+ if (state.selectedCustomCommand >= state.customScrollOffset + visibleCount) {
443
+ state.customScrollOffset = state.selectedCustomCommand - visibleCount + 1;
444
+ }
445
+ lineCount = renderHelpMenu(state, lineCount);
446
+ }
447
+ }
448
+ }
449
+ };
450
+ const onResize = () => {
451
+ lineCount = renderHelpMenu(state, lineCount);
452
+ };
453
+ process.stdin.on('data', onData);
454
+ process.stdout.on('resize', onResize);
455
+ });
456
+ }
457
+ /**
458
+ * Show a confirmation dialog
459
+ */
460
+ export function showConfirmation(message, options = {}) {
461
+ const s = getStyles();
462
+ const confirmText = options.confirmText ?? 'Yes';
463
+ const cancelText = options.cancelText ?? 'No';
464
+ return new Promise((resolve) => {
465
+ terminal.writeLine('');
466
+ terminal.write(s.warning('? ') + message + ' ');
467
+ terminal.write(s.muted(`[${confirmText}/${cancelText}] `));
468
+ terminal.hideCursor();
469
+ const wasRawMode = process.stdin.isRaw;
470
+ terminal.enableRawMode();
471
+ const cleanup = (result) => {
472
+ terminal.writeLine(result ? s.success(confirmText) : s.error(cancelText));
473
+ terminal.showCursor();
474
+ if (!wasRawMode) {
475
+ terminal.disableRawMode();
476
+ }
477
+ process.stdin.removeListener('data', onData);
478
+ resolve(result);
479
+ };
480
+ const onData = (data) => {
481
+ const key = data.toString().toLowerCase();
482
+ if (key === 'y' || key === '\r' || key === '\n') {
483
+ cleanup(true);
484
+ }
485
+ else if (key === 'n' || key === '\x1b' || key === '\x03') {
486
+ cleanup(false);
487
+ }
488
+ };
489
+ process.stdin.on('data', onData);
490
+ });
491
+ }
492
+ // =============================================================================
493
+ // Permission Prompt
494
+ // =============================================================================
495
+ /**
496
+ * Show permission prompt for tool execution
497
+ */
498
+ export function showPermissionPrompt(tool, args) {
499
+ const s = getStyles();
500
+ return new Promise((resolve) => {
501
+ terminal.writeLine('');
502
+ terminal.writeLine(s.warning('⚠ ') + chalk.bold('Permission Required'));
503
+ terminal.writeLine('');
504
+ terminal.writeLine(' Tool: ' + s.primary(tool));
505
+ if (args) {
506
+ terminal.writeLine(' Args: ' + s.muted(args.slice(0, 60) + (args.length > 60 ? '...' : '')));
507
+ }
508
+ terminal.writeLine('');
509
+ terminal.write(s.muted(' [') +
510
+ s.success('y') +
511
+ s.muted(']es [') +
512
+ s.error('n') +
513
+ s.muted(']o [') +
514
+ s.primary('a') +
515
+ s.muted(']lways '));
516
+ terminal.hideCursor();
517
+ const wasRawMode = process.stdin.isRaw;
518
+ terminal.enableRawMode();
519
+ const renderedLines = 6;
520
+ const cleanup = (result) => {
521
+ // Clear the prompt
522
+ terminal.moveCursorToLineStart();
523
+ terminal.moveCursorUp(renderedLines - 1);
524
+ terminal.clearToEndOfScreen();
525
+ // Show result
526
+ const resultText = result === 'allow'
527
+ ? s.success('Allowed')
528
+ : result === 'allow-always'
529
+ ? s.primary('Always allowed')
530
+ : s.error('Denied');
531
+ terminal.writeLine(s.muted(`Permission: ${resultText}`));
532
+ terminal.writeLine(''); // Add blank line after permission result for separation
533
+ terminal.showCursor();
534
+ if (!wasRawMode) {
535
+ terminal.disableRawMode();
536
+ }
537
+ process.stdin.removeListener('data', onData);
538
+ resolve(result);
539
+ };
540
+ const onData = (data) => {
541
+ const key = data.toString().toLowerCase();
542
+ if (key === 'y' || key === '\r' || key === '\n') {
543
+ cleanup('allow');
544
+ }
545
+ else if (key === 'n' || key === '\x1b') {
546
+ cleanup('deny');
547
+ }
548
+ else if (key === 'a') {
549
+ cleanup('allow-always');
550
+ }
551
+ else if (key === '\x03') {
552
+ // Ctrl+C
553
+ cleanup('deny');
554
+ }
555
+ };
556
+ process.stdin.on('data', onData);
557
+ });
558
+ }
559
+ // =============================================================================
560
+ // Context Stats Display
561
+ // =============================================================================
562
+ /**
563
+ * Show context window usage stats
564
+ */
565
+ export function showContextStats(stats) {
566
+ const pct = (stats.utilization * 100).toFixed(1);
567
+ const bar = renderProgressBar(stats.utilization, 30);
568
+ terminal.writeLine('');
569
+ terminal.writeLine(chalk.bold('Context Usage'));
570
+ terminal.writeLine('');
571
+ terminal.writeLine(` Tokens: ${stats.tokens.toLocaleString()} / ${stats.maxTokens.toLocaleString()}`);
572
+ terminal.writeLine(` Usage: ${bar} ${pct}%`);
573
+ terminal.writeLine(` Messages: ${String(stats.messages)}`);
574
+ terminal.writeLine(` Turns: ${String(stats.turns)}`);
575
+ terminal.writeLine('');
576
+ }
577
+ /**
578
+ * Render a progress bar
579
+ */
580
+ function renderProgressBar(value, width) {
581
+ const s = getStyles();
582
+ const filled = Math.round(value * width);
583
+ const empty = width - filled;
584
+ let color = s.success;
585
+ if (value > 0.8)
586
+ color = s.error;
587
+ else if (value > 0.6)
588
+ color = s.warning;
589
+ return color('█'.repeat(filled)) + s.muted('░'.repeat(empty));
590
+ }
591
+ /**
592
+ * Show session token usage
593
+ */
594
+ export function showTokenUsage(usage) {
595
+ terminal.writeLine('');
596
+ terminal.writeLine(chalk.bold('Token Usage'));
597
+ terminal.writeLine('');
598
+ terminal.writeLine(` Input: ${usage.inputTokens.toLocaleString()} tokens`);
599
+ terminal.writeLine(` Output: ${usage.outputTokens.toLocaleString()} tokens`);
600
+ terminal.writeLine(` Total: ${usage.totalTokens.toLocaleString()} tokens`);
601
+ if (usage.estimatedCost !== undefined) {
602
+ terminal.writeLine(` Cost: $${usage.estimatedCost.toFixed(4)}`);
603
+ }
604
+ terminal.writeLine('');
605
+ }
606
+ // =============================================================================
607
+ // Tool List Display
608
+ // =============================================================================
609
+ /**
610
+ * Show available tools in compact column format
611
+ */
612
+ export function showTools(tools) {
613
+ const s = getStyles();
614
+ terminal.writeLine('');
615
+ terminal.writeLine(chalk.bold('Available Tools') + s.muted(` (${String(tools.length)})`));
616
+ terminal.writeLine('');
617
+ // Display in 3 columns
618
+ const colWidth = 20;
619
+ const cols = 3;
620
+ const names = tools.map((t) => t.name);
621
+ for (let i = 0; i < names.length; i += cols) {
622
+ const row = names.slice(i, i + cols);
623
+ const formatted = row.map((name) => s.primary(name.padEnd(colWidth))).join('');
624
+ terminal.writeLine(' ' + formatted);
625
+ }
626
+ terminal.writeLine('');
627
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Permission Overlay
3
+ *
4
+ * Modal overlay for tool permission requests.
5
+ * Uses the same pattern as ask-user-simple-overlay for consistent behavior.
6
+ */
7
+ export type PermissionResult = 'allow' | 'deny' | 'allow-always';
8
+ export interface PermissionOverlayOptions {
9
+ toolName: string;
10
+ args: Record<string, unknown>;
11
+ description?: string;
12
+ }
13
+ /**
14
+ * Show the permission overlay
15
+ */
16
+ export declare function showPermissionOverlay(options: PermissionOverlayOptions): Promise<PermissionResult>;