@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,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Manages all persistent UI elements below the scrolling zone:
|
|
5
|
+
* - TodoZone (spinner + todo list)
|
|
6
|
+
* - Queued input display
|
|
7
|
+
* - InputPrompt
|
|
8
|
+
*
|
|
9
|
+
* Handles:
|
|
10
|
+
* - Coordinated rendering via single render loop
|
|
11
|
+
* - Event forwarding from InputPrompt
|
|
12
|
+
* - Clearing for scrolling output
|
|
13
|
+
* - State management for agent running/idle modes
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import { MODE_INFO } from './types.js';
|
|
17
|
+
import { getStyles } from '../themes/index.js';
|
|
18
|
+
import * as terminal from './terminal.js';
|
|
19
|
+
import { TodoZone } from './todo-zone.js';
|
|
20
|
+
import { InputPrompt, DEFAULT_COMMANDS } from './input-prompt-v2.js';
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Footer Class
|
|
23
|
+
// =============================================================================
|
|
24
|
+
export class Footer extends EventEmitter {
|
|
25
|
+
// Child components
|
|
26
|
+
todoZone;
|
|
27
|
+
inputPrompt;
|
|
28
|
+
// State
|
|
29
|
+
agentRunning = false;
|
|
30
|
+
mode;
|
|
31
|
+
lastRenderHeight = 0;
|
|
32
|
+
isRunning = false;
|
|
33
|
+
isPaused = false; // Prevents rendering even if callback is queued
|
|
34
|
+
cursorLineFromBottom = 0; // Track cursor position for clear()
|
|
35
|
+
// Render loop
|
|
36
|
+
renderInterval;
|
|
37
|
+
renderTimer = null;
|
|
38
|
+
needsRender = false;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
super();
|
|
41
|
+
this.renderInterval = options.renderInterval ?? 60; // 60ms = ~16fps
|
|
42
|
+
this.mode = options.initialMode ?? 'normal';
|
|
43
|
+
// Create child components
|
|
44
|
+
const s = getStyles();
|
|
45
|
+
this.todoZone = new TodoZone();
|
|
46
|
+
this.inputPrompt = new InputPrompt({
|
|
47
|
+
prompt: options.prompt ?? s.primaryBold('compilr>') + ' ',
|
|
48
|
+
showSeparators: options.showSeparators ?? true,
|
|
49
|
+
commands: DEFAULT_COMMANDS,
|
|
50
|
+
});
|
|
51
|
+
// Wire up TodoZone animation callback
|
|
52
|
+
this.todoZone.setAnimationCallback(() => {
|
|
53
|
+
this.needsRender = true;
|
|
54
|
+
});
|
|
55
|
+
// Wire up InputPrompt events
|
|
56
|
+
this.inputPrompt.on('submit', (input) => {
|
|
57
|
+
this.emit('submit', input);
|
|
58
|
+
});
|
|
59
|
+
this.inputPrompt.on('command', (command, args) => {
|
|
60
|
+
this.emit('command', command, args);
|
|
61
|
+
});
|
|
62
|
+
this.inputPrompt.on('cancel', () => {
|
|
63
|
+
this.emit('cancel');
|
|
64
|
+
});
|
|
65
|
+
this.inputPrompt.on('escape', () => {
|
|
66
|
+
this.emit('escape');
|
|
67
|
+
});
|
|
68
|
+
this.inputPrompt.on('modeChange', () => {
|
|
69
|
+
this.emit('modeChange');
|
|
70
|
+
});
|
|
71
|
+
this.inputPrompt.on('change', () => {
|
|
72
|
+
this.needsRender = true;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// ===========================================================================
|
|
76
|
+
// Public API
|
|
77
|
+
// ===========================================================================
|
|
78
|
+
/**
|
|
79
|
+
* Start the footer (begins render loop and input capture)
|
|
80
|
+
*/
|
|
81
|
+
start() {
|
|
82
|
+
if (this.isRunning)
|
|
83
|
+
return;
|
|
84
|
+
this.isRunning = true;
|
|
85
|
+
this.isPaused = false; // Ensure not paused when starting
|
|
86
|
+
// Start input capture
|
|
87
|
+
this.inputPrompt.start();
|
|
88
|
+
// Start render loop
|
|
89
|
+
this.render();
|
|
90
|
+
this.renderTimer = setInterval(() => {
|
|
91
|
+
if (this.needsRender && !this.isPaused) {
|
|
92
|
+
this.render();
|
|
93
|
+
this.needsRender = false;
|
|
94
|
+
}
|
|
95
|
+
}, this.renderInterval);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stop the footer
|
|
99
|
+
*/
|
|
100
|
+
stop() {
|
|
101
|
+
if (!this.isRunning)
|
|
102
|
+
return;
|
|
103
|
+
this.isRunning = false;
|
|
104
|
+
// Stop render loop
|
|
105
|
+
if (this.renderTimer) {
|
|
106
|
+
clearInterval(this.renderTimer);
|
|
107
|
+
this.renderTimer = null;
|
|
108
|
+
}
|
|
109
|
+
// Stop components
|
|
110
|
+
this.todoZone.dispose();
|
|
111
|
+
this.inputPrompt.stop();
|
|
112
|
+
// Clear rendered content
|
|
113
|
+
this.clear();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Set whether agent is running
|
|
117
|
+
* Enables/disables queue mode and spinner
|
|
118
|
+
*/
|
|
119
|
+
setAgentRunning(running) {
|
|
120
|
+
const wasRunning = this.agentRunning;
|
|
121
|
+
this.agentRunning = running;
|
|
122
|
+
this.todoZone.setAgentRunning(running);
|
|
123
|
+
this.inputPrompt.setQueueMode(running);
|
|
124
|
+
// When stopping, immediately clear and re-render to remove spinner
|
|
125
|
+
// This prevents ghost spinner from render interval races
|
|
126
|
+
if (wasRunning && !running) {
|
|
127
|
+
this.clear();
|
|
128
|
+
this.render();
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
this.needsRender = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if agent is running
|
|
136
|
+
*/
|
|
137
|
+
isAgentRunning() {
|
|
138
|
+
return this.agentRunning;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Set the current mode
|
|
142
|
+
*/
|
|
143
|
+
setMode(mode) {
|
|
144
|
+
this.mode = mode;
|
|
145
|
+
this.needsRender = true;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get the current mode
|
|
149
|
+
*/
|
|
150
|
+
getMode() {
|
|
151
|
+
return this.mode;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update the todo list
|
|
155
|
+
*/
|
|
156
|
+
setTodos(todos) {
|
|
157
|
+
this.todoZone.setTodos(todos);
|
|
158
|
+
this.needsRender = true;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Set the current tool being executed
|
|
162
|
+
*/
|
|
163
|
+
setCurrentTool(tool) {
|
|
164
|
+
this.todoZone.setCurrentTool(tool);
|
|
165
|
+
this.needsRender = true;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Set a suggestion for the next action (ghost text in input prompt)
|
|
169
|
+
*/
|
|
170
|
+
setSuggestion(action) {
|
|
171
|
+
this.inputPrompt.setSuggestion(action);
|
|
172
|
+
this.needsRender = true;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Set custom spinner text (overrides todo activeForm and random thinking word)
|
|
176
|
+
* Pass null to clear and use default behavior.
|
|
177
|
+
*/
|
|
178
|
+
setSpinnerText(text) {
|
|
179
|
+
this.todoZone.setSpinnerText(text);
|
|
180
|
+
this.needsRender = true;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Add tokens to the spinner counter
|
|
184
|
+
*/
|
|
185
|
+
addTokens(count) {
|
|
186
|
+
this.todoZone.addTokens(count);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get queued inputs
|
|
190
|
+
*/
|
|
191
|
+
getQueuedInputs() {
|
|
192
|
+
return this.inputPrompt.getQueuedInputs();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Pop the first queued input
|
|
196
|
+
*/
|
|
197
|
+
popQueuedInput() {
|
|
198
|
+
const input = this.inputPrompt.popQueuedInput();
|
|
199
|
+
if (input !== null) {
|
|
200
|
+
this.needsRender = true;
|
|
201
|
+
}
|
|
202
|
+
return input;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if there are queued inputs
|
|
206
|
+
*/
|
|
207
|
+
hasQueuedInput() {
|
|
208
|
+
return this.inputPrompt.hasQueuedInput();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clear the queue
|
|
212
|
+
*/
|
|
213
|
+
clearQueue() {
|
|
214
|
+
this.inputPrompt.clearQueue();
|
|
215
|
+
this.needsRender = true;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Clear footer before scrolling output
|
|
219
|
+
* Call this before writing to the scrolling zone
|
|
220
|
+
*/
|
|
221
|
+
clearForOutput() {
|
|
222
|
+
this.clear();
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Force an immediate render
|
|
226
|
+
*/
|
|
227
|
+
forceRender() {
|
|
228
|
+
this.render();
|
|
229
|
+
this.needsRender = false;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Pause footer completely (for overlays)
|
|
233
|
+
* Stops render loop, input capture, and animation
|
|
234
|
+
*/
|
|
235
|
+
pauseAnimation() {
|
|
236
|
+
// Set paused flag FIRST - this prevents any queued render callbacks from executing
|
|
237
|
+
this.isPaused = true;
|
|
238
|
+
// Stop render loop
|
|
239
|
+
if (this.renderTimer) {
|
|
240
|
+
clearInterval(this.renderTimer);
|
|
241
|
+
this.renderTimer = null;
|
|
242
|
+
}
|
|
243
|
+
// Stop input capture
|
|
244
|
+
this.inputPrompt.stop();
|
|
245
|
+
// Stop animation
|
|
246
|
+
this.todoZone.pauseAnimation();
|
|
247
|
+
// Clear footer from screen
|
|
248
|
+
this.clear();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resume footer after pause
|
|
252
|
+
* Restarts render loop, input capture, and animation
|
|
253
|
+
*/
|
|
254
|
+
resumeAnimation() {
|
|
255
|
+
// Clear paused flag FIRST
|
|
256
|
+
this.isPaused = false;
|
|
257
|
+
// Resume animation
|
|
258
|
+
this.todoZone.resumeAnimation();
|
|
259
|
+
// Restart input capture
|
|
260
|
+
this.inputPrompt.start();
|
|
261
|
+
// Restart render loop
|
|
262
|
+
this.render();
|
|
263
|
+
this.renderTimer = setInterval(() => {
|
|
264
|
+
if (this.needsRender && !this.isPaused) {
|
|
265
|
+
this.render();
|
|
266
|
+
this.needsRender = false;
|
|
267
|
+
}
|
|
268
|
+
}, this.renderInterval);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get the height of last render
|
|
272
|
+
*/
|
|
273
|
+
getLastRenderHeight() {
|
|
274
|
+
return this.lastRenderHeight;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Refresh the prompt with current theme colors
|
|
278
|
+
* Call after theme changes to apply new colors immediately
|
|
279
|
+
*/
|
|
280
|
+
refreshPrompt() {
|
|
281
|
+
const s = getStyles();
|
|
282
|
+
this.inputPrompt.setPrompt(s.primaryBold('compilr>') + ' ');
|
|
283
|
+
this.needsRender = true;
|
|
284
|
+
}
|
|
285
|
+
// ===========================================================================
|
|
286
|
+
// Private: Rendering
|
|
287
|
+
// ===========================================================================
|
|
288
|
+
/**
|
|
289
|
+
* Clear the footer area
|
|
290
|
+
*/
|
|
291
|
+
clear() {
|
|
292
|
+
if (this.lastRenderHeight > 0) {
|
|
293
|
+
// Move to start of current line
|
|
294
|
+
terminal.moveCursorToLineStart();
|
|
295
|
+
// First move DOWN to the last line of footer (cursor may be positioned in input area)
|
|
296
|
+
if (this.cursorLineFromBottom > 0) {
|
|
297
|
+
terminal.moveCursorDown(this.cursorLineFromBottom);
|
|
298
|
+
}
|
|
299
|
+
// Now move up to the first line of footer
|
|
300
|
+
terminal.moveCursorUp(this.lastRenderHeight - 1);
|
|
301
|
+
terminal.clearToEndOfScreen();
|
|
302
|
+
}
|
|
303
|
+
this.lastRenderHeight = 0;
|
|
304
|
+
this.cursorLineFromBottom = 0;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Render the entire footer
|
|
308
|
+
*/
|
|
309
|
+
render() {
|
|
310
|
+
// Clear and redraw footer
|
|
311
|
+
this.clear();
|
|
312
|
+
const allLines = [];
|
|
313
|
+
// 1. Todo zone (spinner + todos)
|
|
314
|
+
const todoLines = this.todoZone.render();
|
|
315
|
+
allLines.push(...todoLines);
|
|
316
|
+
// 2. Queued inputs
|
|
317
|
+
const s = getStyles();
|
|
318
|
+
const queuedInputs = this.inputPrompt.getQueuedInputs();
|
|
319
|
+
for (const queued of queuedInputs) {
|
|
320
|
+
allLines.push(s.muted(`queued: "${queued}"`));
|
|
321
|
+
}
|
|
322
|
+
// 3. Input prompt
|
|
323
|
+
const inputLines = this.inputPrompt.render();
|
|
324
|
+
allLines.push(...inputLines);
|
|
325
|
+
// 4. Mode indicator (below input, before autocomplete)
|
|
326
|
+
allLines.push(this.renderModeIndicator());
|
|
327
|
+
// 5. Autocomplete dropdown (if active)
|
|
328
|
+
const autocompleteLines = this.inputPrompt.getAutocompleteLines();
|
|
329
|
+
allLines.push(...autocompleteLines);
|
|
330
|
+
// Write all lines
|
|
331
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
332
|
+
terminal.write(allLines[i]);
|
|
333
|
+
if (i < allLines.length - 1) {
|
|
334
|
+
terminal.write('\n');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Track height - account for line wrapping in terminal
|
|
338
|
+
const termWidth = process.stdout.columns || 80;
|
|
339
|
+
let actualTerminalRows = 0;
|
|
340
|
+
for (const line of allLines) {
|
|
341
|
+
// Strip ANSI codes to get visible length
|
|
342
|
+
// eslint-disable-next-line no-control-regex
|
|
343
|
+
const visibleLength = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
344
|
+
// Each line takes at least 1 row, plus extra rows if it wraps
|
|
345
|
+
actualTerminalRows += Math.max(1, Math.ceil(visibleLength / termWidth));
|
|
346
|
+
}
|
|
347
|
+
this.lastRenderHeight = actualTerminalRows;
|
|
348
|
+
// Position cursor within input prompt
|
|
349
|
+
// linesBeforeInput = todoLines + queuedInputs (mode indicator is now AFTER input)
|
|
350
|
+
this.positionCursor(allLines, todoLines.length + queuedInputs.length);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Render mode indicator line
|
|
354
|
+
*/
|
|
355
|
+
renderModeIndicator() {
|
|
356
|
+
const s = getStyles();
|
|
357
|
+
const modeInfo = MODE_INFO[this.mode];
|
|
358
|
+
switch (this.mode) {
|
|
359
|
+
case 'normal':
|
|
360
|
+
// Subtle indicator in normal mode
|
|
361
|
+
return s.muted(`mode: ${modeInfo.label} (Shift+Tab to change)`);
|
|
362
|
+
case 'auto-accept':
|
|
363
|
+
// Warning color for auto-accept
|
|
364
|
+
return s.warning(`⚡ mode: ${modeInfo.label}`) + s.muted(' (Shift+Tab to change)');
|
|
365
|
+
case 'plan':
|
|
366
|
+
// Different color for plan mode
|
|
367
|
+
return s.info(`📋 mode: ${modeInfo.label}`) + s.muted(' (Shift+Tab to change)');
|
|
368
|
+
default:
|
|
369
|
+
return s.muted(`mode: ${modeInfo.label} (Shift+Tab to change)`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Calculate how many terminal rows a line takes (accounting for wrapping)
|
|
374
|
+
*/
|
|
375
|
+
getTerminalRowsForLine(line) {
|
|
376
|
+
const termWidth = process.stdout.columns || 80;
|
|
377
|
+
// Strip ANSI codes to get visible length
|
|
378
|
+
// eslint-disable-next-line no-control-regex
|
|
379
|
+
const visibleLength = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
380
|
+
// Each line takes at least 1 row, plus extra rows if it wraps
|
|
381
|
+
return Math.max(1, Math.ceil(visibleLength / termWidth));
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Position cursor correctly within the input prompt
|
|
385
|
+
*/
|
|
386
|
+
positionCursor(allLines, linesBeforeInput) {
|
|
387
|
+
const cursorInfo = this.inputPrompt.getCursorInfo();
|
|
388
|
+
// cursorInfo.row already accounts for:
|
|
389
|
+
// - Top separator (if enabled)
|
|
390
|
+
// - Wrapped lines within input
|
|
391
|
+
// cursorInfo.col is the column position within that terminal row
|
|
392
|
+
// Calculate terminal rows for lines BEFORE input (todo zone, queued inputs)
|
|
393
|
+
let rowsBeforeInput = 0;
|
|
394
|
+
for (let i = 0; i < linesBeforeInput; i++) {
|
|
395
|
+
rowsBeforeInput += this.getTerminalRowsForLine(allLines[i]);
|
|
396
|
+
}
|
|
397
|
+
// Calculate terminal rows for input section (including separators and wrapped lines)
|
|
398
|
+
const inputLines = this.inputPrompt.render();
|
|
399
|
+
let inputTotalRows = 0;
|
|
400
|
+
for (const line of inputLines) {
|
|
401
|
+
inputTotalRows += this.getTerminalRowsForLine(line);
|
|
402
|
+
}
|
|
403
|
+
// Calculate terminal rows for lines AFTER input (mode indicator, autocomplete)
|
|
404
|
+
let rowsAfterInput = 0;
|
|
405
|
+
const inputEndIndex = linesBeforeInput + inputLines.length;
|
|
406
|
+
for (let i = inputEndIndex; i < allLines.length; i++) {
|
|
407
|
+
rowsAfterInput += this.getTerminalRowsForLine(allLines[i]);
|
|
408
|
+
}
|
|
409
|
+
// Cursor target row from start of footer = rows before input + cursor row within input
|
|
410
|
+
const cursorRowFromStart = rowsBeforeInput + cursorInfo.row;
|
|
411
|
+
// Total rows to move up from end = total rows - cursor row - 1
|
|
412
|
+
const totalRows = rowsBeforeInput + inputTotalRows + rowsAfterInput;
|
|
413
|
+
const rowsToMoveUp = totalRows - cursorRowFromStart - 1;
|
|
414
|
+
if (rowsToMoveUp > 0) {
|
|
415
|
+
terminal.moveCursorUp(rowsToMoveUp);
|
|
416
|
+
}
|
|
417
|
+
// Track how far from bottom cursor is (for clear())
|
|
418
|
+
this.cursorLineFromBottom = rowsToMoveUp;
|
|
419
|
+
// Position cursor column (add 1 because terminal columns are 1-indexed)
|
|
420
|
+
terminal.moveCursorToColumn(cursorInfo.col + 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all UI components for easy importing.
|
|
5
|
+
*/
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export * from './terminal.js';
|
|
8
|
+
export * from './conversation.js';
|
|
9
|
+
export * from './overlays.js';
|
|
10
|
+
export * from './todo-zone.js';
|
|
11
|
+
export * from './input-prompt-v2.js';
|
|
12
|
+
export * from './footer.js';
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all UI components for easy importing.
|
|
5
|
+
*/
|
|
6
|
+
export * from './types.js';
|
|
7
|
+
export * from './terminal.js';
|
|
8
|
+
export * from './conversation.js';
|
|
9
|
+
export * from './overlays.js';
|
|
10
|
+
// Refactored components (event-driven, persistent footer)
|
|
11
|
+
export * from './todo-zone.js';
|
|
12
|
+
export * from './input-prompt-v2.js';
|
|
13
|
+
export * from './footer.js';
|
|
14
|
+
// Note: ephemeral.js and input-prompt.js are legacy and not re-exported
|
|
15
|
+
// to avoid naming conflicts. Import directly if needed.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay for the /init wizard - creates new projects with structured workflow.
|
|
5
|
+
* Steps:
|
|
6
|
+
* 0. Project type (new vs existing)
|
|
7
|
+
* 1. Project name
|
|
8
|
+
* 2. Description
|
|
9
|
+
* 3. Repo pattern (single vs two-repo)
|
|
10
|
+
* 4. Tech stack
|
|
11
|
+
* 5. Coding standards
|
|
12
|
+
* 6. Initialize git?
|
|
13
|
+
* 7. Confirmation
|
|
14
|
+
*/
|
|
15
|
+
export interface InitResult {
|
|
16
|
+
created: boolean;
|
|
17
|
+
projectPath?: string;
|
|
18
|
+
docsPath?: string;
|
|
19
|
+
filesCreated?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Show the init wizard overlay
|
|
23
|
+
*/
|
|
24
|
+
export declare function showInitOverlay(): Promise<InitResult>;
|