@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.
- package/README.md +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- 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>;
|