@compilr-dev/cli 0.6.2 → 0.6.3
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/dist/commands-v2/handlers/core.js +2 -2
- package/dist/commands-v2/handlers/index.d.ts +1 -0
- package/dist/commands-v2/handlers/index.js +3 -0
- package/dist/commands-v2/handlers/perf.d.ts +9 -0
- package/dist/commands-v2/handlers/perf.js +66 -0
- package/dist/commands-v2/handlers/project.js +2 -3
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +9 -6
- package/dist/repl-v2.js +5 -1
- package/dist/session/project-session-manager.js +1 -1
- package/dist/slash-autocomplete.js +18 -16
- package/dist/tabbed-menu.js +8 -7
- package/dist/ui/base/overlay-base.js +2 -1
- package/dist/ui/overlay/impl/artifact-detail-overlay-v2.js +12 -7
- package/dist/ui/overlay/impl/config-overlay-v2.js +2 -2
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +12 -8
- package/dist/ui/overlay/impl/pending-overlay-v2.js +4 -1
- package/dist/ui/overlay-manager.js +7 -6
- package/dist/ui/terminal-render-item.js +2 -1
- package/dist/ui/terminal-renderer.js +1 -2
- package/dist/ui/terminal-ui.js +6 -4
- package/dist/ui/terminal.d.ts +9 -0
- package/dist/ui/terminal.js +28 -15
- package/package.json +1 -1
|
@@ -171,8 +171,8 @@ export const menuCommand = {
|
|
|
171
171
|
console.log(s.muted(' Run: npm update -g @compilr-dev/cli'));
|
|
172
172
|
console.log('');
|
|
173
173
|
}
|
|
174
|
-
// Dashboard loop
|
|
175
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
174
|
+
// Dashboard loop — returns to dashboard after sub-commands until user exits or continues
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop; exit happens via internal break/return
|
|
176
176
|
while (true) {
|
|
177
177
|
const dashboard = new DashboardOverlayV2();
|
|
178
178
|
const result = await ctx.ui.showOverlay(dashboard);
|
|
@@ -21,6 +21,7 @@ import { terminalsCommands } from './terminals.js';
|
|
|
21
21
|
import { notificationsCommands } from './notifications.js';
|
|
22
22
|
import { mcpCommands } from './mcp.js';
|
|
23
23
|
import { delegationsCommands } from './delegations.js';
|
|
24
|
+
import { perfCommands } from './perf.js';
|
|
24
25
|
// Re-export individual modules
|
|
25
26
|
export * from './core.js';
|
|
26
27
|
export * from './settings.js';
|
|
@@ -40,6 +41,7 @@ export * from './terminals.js';
|
|
|
40
41
|
export * from './notifications.js';
|
|
41
42
|
export * from './mcp.js';
|
|
42
43
|
export * from './delegations.js';
|
|
44
|
+
export * from './perf.js';
|
|
43
45
|
// All commands combined
|
|
44
46
|
export const allCommands = [
|
|
45
47
|
...coreCommands,
|
|
@@ -60,4 +62,5 @@ export const allCommands = [
|
|
|
60
62
|
...notificationsCommands,
|
|
61
63
|
...mcpCommands,
|
|
62
64
|
...delegationsCommands,
|
|
65
|
+
...perfCommands,
|
|
63
66
|
];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /perf Command
|
|
3
|
+
*
|
|
4
|
+
* Display the most recent CLI startup-performance profile.
|
|
5
|
+
* The profile is captured automatically and written to
|
|
6
|
+
* ~/.compilr-dev/startup-perf.log on every launch.
|
|
7
|
+
*/
|
|
8
|
+
import type { CommandHandlerV2 } from '../types.js';
|
|
9
|
+
export declare const perfCommands: CommandHandlerV2[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /perf Command
|
|
3
|
+
*
|
|
4
|
+
* Display the most recent CLI startup-performance profile.
|
|
5
|
+
* The profile is captured automatically and written to
|
|
6
|
+
* ~/.compilr-dev/startup-perf.log on every launch.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import pc from 'picocolors';
|
|
11
|
+
import { getDataPath } from '../../settings/paths.js';
|
|
12
|
+
const perfCommand = {
|
|
13
|
+
name: 'perf',
|
|
14
|
+
description: 'Show the latest startup-performance profile',
|
|
15
|
+
details: 'Display the timing breakdown captured during this CLI launch. ' +
|
|
16
|
+
'Useful for diagnosing slow startups (cold cache, heavy MCP servers, ' +
|
|
17
|
+
'large project DBs). The profile is overwritten on every launch.',
|
|
18
|
+
examples: [{ code: '/perf', description: 'Show the most recent startup profile' }],
|
|
19
|
+
execute(_args, ctx) {
|
|
20
|
+
const logPath = path.join(getDataPath(), 'startup-perf.log');
|
|
21
|
+
if (!fs.existsSync(logPath)) {
|
|
22
|
+
ctx.ui.print({
|
|
23
|
+
type: 'info',
|
|
24
|
+
message: 'No startup profile found. (Profile is written on the next launch.)',
|
|
25
|
+
});
|
|
26
|
+
return Promise.resolve(true);
|
|
27
|
+
}
|
|
28
|
+
let content;
|
|
29
|
+
try {
|
|
30
|
+
content = fs.readFileSync(logPath, 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
ctx.ui.print({
|
|
34
|
+
type: 'warning',
|
|
35
|
+
message: `Could not read startup profile: ${String(err)}`,
|
|
36
|
+
});
|
|
37
|
+
return Promise.resolve(true);
|
|
38
|
+
}
|
|
39
|
+
if (!content.trim()) {
|
|
40
|
+
ctx.ui.print({ type: 'info', message: 'Startup profile is empty.' });
|
|
41
|
+
return Promise.resolve(true);
|
|
42
|
+
}
|
|
43
|
+
// Print the profile, dimming the path footer.
|
|
44
|
+
const lines = content.split('\n');
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
if (line.startsWith('===')) {
|
|
48
|
+
out.push(pc.bold(line));
|
|
49
|
+
}
|
|
50
|
+
else if (line.startsWith('Slow steps')) {
|
|
51
|
+
out.push(pc.yellow(line));
|
|
52
|
+
}
|
|
53
|
+
else if (line.startsWith('Total:')) {
|
|
54
|
+
out.push(pc.cyan(line));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
out.push(line);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
out.push('');
|
|
61
|
+
out.push(pc.dim(` Profile: ${logPath}`));
|
|
62
|
+
ctx.ui.print({ type: 'raw-lines', lines: out });
|
|
63
|
+
return Promise.resolve(true);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
export const perfCommands = [perfCommand];
|
|
@@ -178,9 +178,8 @@ export const initCommand = {
|
|
|
178
178
|
'Type to enter values in input steps',
|
|
179
179
|
],
|
|
180
180
|
async execute(_args, ctx) {
|
|
181
|
-
// Show onboarding wizard (same as first-run experience)
|
|
182
|
-
//
|
|
183
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
181
|
+
// Show onboarding wizard (same as first-run experience).
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop; exit happens via internal break/return
|
|
184
183
|
while (true) {
|
|
185
184
|
const wizard = new OnboardingWizardOverlayV2();
|
|
186
185
|
const result = await ctx.ui.showOverlay(wizard);
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -88,7 +88,7 @@ function showHelp() {
|
|
|
88
88
|
console.log(`
|
|
89
89
|
@compilr-dev/cli v${VERSION}
|
|
90
90
|
|
|
91
|
-
Usage:
|
|
91
|
+
Usage: compilr [options]
|
|
92
92
|
|
|
93
93
|
Options:
|
|
94
94
|
--provider, -p <provider> LLM provider (claude, openai, gemini, ollama)
|
|
@@ -103,13 +103,16 @@ Environment Variables:
|
|
|
103
103
|
ANTHROPIC_API_KEY Required for Claude provider
|
|
104
104
|
OPENAI_API_KEY Required for OpenAI provider
|
|
105
105
|
GOOGLE_AI_API_KEY Required for Gemini provider
|
|
106
|
+
NO_COLOR Disable ANSI colors
|
|
107
|
+
COMPILR_NO_UPDATE_CHECK Skip the npm registry update check at startup
|
|
108
|
+
CI Auto-skips the update check
|
|
106
109
|
|
|
107
110
|
Examples:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
compilr
|
|
112
|
+
compilr --model claude-sonnet-4-6
|
|
113
|
+
compilr --provider gemini --model gemini-2.0-flash
|
|
114
|
+
compilr --minimal --show-filtering
|
|
115
|
+
compilr --update
|
|
113
116
|
`);
|
|
114
117
|
}
|
|
115
118
|
// =============================================================================
|
package/dist/repl-v2.js
CHANGED
|
@@ -3358,8 +3358,12 @@ export class ReplV2 {
|
|
|
3358
3358
|
// Only run main() when executed directly (not when imported)
|
|
3359
3359
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
3360
3360
|
const repl = new ReplV2();
|
|
3361
|
-
// Handle SIGINT
|
|
3361
|
+
// Handle SIGINT — restore raw mode before exit so the parent shell
|
|
3362
|
+
// doesn't inherit a broken terminal state.
|
|
3362
3363
|
process.on('SIGINT', () => {
|
|
3364
|
+
if (process.stdin.isTTY) {
|
|
3365
|
+
process.stdin.setRawMode(false);
|
|
3366
|
+
}
|
|
3363
3367
|
console.log('\n\nInterrupted\n');
|
|
3364
3368
|
process.exit(0);
|
|
3365
3369
|
});
|
|
@@ -387,7 +387,7 @@ export class ProjectSessionManager {
|
|
|
387
387
|
else if (Array.isArray(lastUserMsg.content)) {
|
|
388
388
|
const textBlock = lastUserMsg.content.find((b) => b.type === 'text');
|
|
389
389
|
if (textBlock?.type === 'text') {
|
|
390
|
-
preview = textBlock.text;
|
|
390
|
+
preview = (textBlock).text;
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Proper wrapping for long input
|
|
10
10
|
*/
|
|
11
11
|
import pc from 'picocolors';
|
|
12
|
+
import { ttyWrite } from './ui/terminal.js';
|
|
12
13
|
// ANSI escape codes for terminal control
|
|
13
14
|
const ANSI = {
|
|
14
15
|
HIDE_CURSOR: '\x1B[?25l',
|
|
@@ -83,7 +84,7 @@ function renderDropdown(state, promptLen, currentLine, totalLines, hasSeparators
|
|
|
83
84
|
if (hasSeparators)
|
|
84
85
|
linesToMoveDown += 1;
|
|
85
86
|
if (linesToMoveDown > 0) {
|
|
86
|
-
|
|
87
|
+
ttyWrite(ANSI.MOVE_DOWN(linesToMoveDown));
|
|
87
88
|
}
|
|
88
89
|
process.stdout.write('\n');
|
|
89
90
|
const visible = state.matches.slice(0, MAX_VISIBLE);
|
|
@@ -98,8 +99,8 @@ function renderDropdown(state, promptLen, currentLine, totalLines, hasSeparators
|
|
|
98
99
|
// Move back up to cursor position
|
|
99
100
|
const linesRendered = visible.length;
|
|
100
101
|
const linesToMoveUp = linesRendered + linesToMoveDown + 1;
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
ttyWrite(ANSI.MOVE_UP(linesToMoveUp));
|
|
103
|
+
ttyWrite(ANSI.MOVE_TO_COLUMN(promptLen + state.cursorPos + 1));
|
|
103
104
|
return linesRendered;
|
|
104
105
|
}
|
|
105
106
|
/**
|
|
@@ -113,16 +114,17 @@ function clearDropdown(dropdownLines, currentLine, totalLines, hasSeparators) {
|
|
|
113
114
|
if (hasSeparators)
|
|
114
115
|
linesToMoveDown += 1;
|
|
115
116
|
if (linesToMoveDown > 0) {
|
|
116
|
-
|
|
117
|
+
ttyWrite(ANSI.MOVE_DOWN(linesToMoveDown));
|
|
117
118
|
}
|
|
118
119
|
process.stdout.write('\n');
|
|
119
120
|
// Clear dropdown lines
|
|
120
121
|
for (let i = 0; i < dropdownLines; i++) {
|
|
121
|
-
|
|
122
|
+
ttyWrite(ANSI.CLEAR_LINE);
|
|
123
|
+
process.stdout.write('\n');
|
|
122
124
|
}
|
|
123
125
|
// Move back up
|
|
124
126
|
const linesToMoveUp = dropdownLines + linesToMoveDown + 1;
|
|
125
|
-
|
|
127
|
+
ttyWrite(ANSI.MOVE_UP(linesToMoveUp));
|
|
126
128
|
}
|
|
127
129
|
/**
|
|
128
130
|
* Create interactive input with autocomplete
|
|
@@ -198,13 +200,13 @@ export function createInteractiveInput(prompt, onSubmit, showSeparator = true, g
|
|
|
198
200
|
const termWidth = getTerminalWidth();
|
|
199
201
|
// Clear previous render
|
|
200
202
|
if (linesAboveCursor > 0) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
ttyWrite('\r');
|
|
204
|
+
ttyWrite(ANSI.MOVE_UP(linesAboveCursor));
|
|
203
205
|
}
|
|
204
206
|
else {
|
|
205
|
-
|
|
207
|
+
ttyWrite('\r');
|
|
206
208
|
}
|
|
207
|
-
|
|
209
|
+
ttyWrite(ANSI.CLEAR_TO_END_OF_SCREEN);
|
|
208
210
|
// Render todos
|
|
209
211
|
renderedTodoLines = renderTodoSection();
|
|
210
212
|
// Render top separator
|
|
@@ -246,10 +248,10 @@ export function createInteractiveInput(prompt, onSubmit, showSeparator = true, g
|
|
|
246
248
|
}
|
|
247
249
|
linesToMoveUp += (currentLinePhysical - 1 - cursorPhysicalRow);
|
|
248
250
|
if (linesToMoveUp > 0) {
|
|
249
|
-
|
|
251
|
+
ttyWrite(ANSI.MOVE_UP(linesToMoveUp));
|
|
250
252
|
}
|
|
251
253
|
const cursorCol = (cursorAbsPos % termWidth) + 1;
|
|
252
|
-
|
|
254
|
+
ttyWrite(ANSI.MOVE_TO_COLUMN(cursorCol));
|
|
253
255
|
// Track cursor position for next render
|
|
254
256
|
linesAboveCursor = physicalLinesRendered + physicalLinesBeforeCursor + cursorPhysicalRow;
|
|
255
257
|
}
|
|
@@ -339,9 +341,9 @@ export function createInteractiveInput(prompt, onSubmit, showSeparator = true, g
|
|
|
339
341
|
dropdownLines = 0;
|
|
340
342
|
// Clear display
|
|
341
343
|
if (linesAboveCursor > 0) {
|
|
342
|
-
|
|
344
|
+
ttyWrite('\r' + ANSI.MOVE_UP(linesAboveCursor));
|
|
343
345
|
}
|
|
344
|
-
|
|
346
|
+
ttyWrite('\r' + ANSI.CLEAR_TO_END_OF_SCREEN);
|
|
345
347
|
// Print clean input
|
|
346
348
|
for (let i = 0; i < state.lines.length; i++) {
|
|
347
349
|
const linePrompt = i === 0 ? prompt : pc.dim(' \\ ');
|
|
@@ -494,7 +496,7 @@ export function createInteractiveInput(prompt, onSubmit, showSeparator = true, g
|
|
|
494
496
|
if (isLeftArrow) {
|
|
495
497
|
if (state.cursorPos > 0) {
|
|
496
498
|
state.cursorPos--;
|
|
497
|
-
|
|
499
|
+
ttyWrite('\x1B[D');
|
|
498
500
|
}
|
|
499
501
|
else if (state.currentLine > 0) {
|
|
500
502
|
state.currentLine--;
|
|
@@ -507,7 +509,7 @@ export function createInteractiveInput(prompt, onSubmit, showSeparator = true, g
|
|
|
507
509
|
if (isRightArrow) {
|
|
508
510
|
if (state.cursorPos < state.lines[state.currentLine].length) {
|
|
509
511
|
state.cursorPos++;
|
|
510
|
-
|
|
512
|
+
ttyWrite('\x1B[C');
|
|
511
513
|
}
|
|
512
514
|
else if (state.currentLine < state.lines.length - 1) {
|
|
513
515
|
state.currentLine++;
|
package/dist/tabbed-menu.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import pc from 'picocolors';
|
|
8
8
|
import { getCustomCommandRegistry } from './commands/index.js';
|
|
9
9
|
import { truncate } from './ui/base/index.js';
|
|
10
|
+
import { ttyWrite } from './ui/terminal.js';
|
|
10
11
|
// ANSI escape codes
|
|
11
12
|
const ANSI = {
|
|
12
13
|
HIDE_CURSOR: '\x1B[?25l',
|
|
@@ -201,11 +202,11 @@ function render(state, prevLineCount) {
|
|
|
201
202
|
// Clear previous render
|
|
202
203
|
if (prevLineCount > 0) {
|
|
203
204
|
// Move to column 1, move up to first line, clear everything below
|
|
204
|
-
|
|
205
|
+
ttyWrite('\r');
|
|
205
206
|
if (prevLineCount > 1) {
|
|
206
|
-
|
|
207
|
+
ttyWrite(ANSI.MOVE_UP(prevLineCount - 1));
|
|
207
208
|
}
|
|
208
|
-
|
|
209
|
+
ttyWrite(ANSI.CLEAR_TO_END);
|
|
209
210
|
}
|
|
210
211
|
// Render new content
|
|
211
212
|
process.stdout.write(lines.join('\n'));
|
|
@@ -283,7 +284,7 @@ export function showHelpMenu() {
|
|
|
283
284
|
// Start on a new line (menu appears below the prompt)
|
|
284
285
|
process.stdout.write('\n');
|
|
285
286
|
// Hide cursor during menu display
|
|
286
|
-
|
|
287
|
+
ttyWrite(ANSI.HIDE_CURSOR);
|
|
287
288
|
// Store original raw mode state
|
|
288
289
|
const wasRawMode = process.stdin.isRaw;
|
|
289
290
|
// Enable raw mode
|
|
@@ -298,13 +299,13 @@ export function showHelpMenu() {
|
|
|
298
299
|
// lineCount lines with lineCount-1 newlines between them, plus the initial \n we added
|
|
299
300
|
// So cursor is (lineCount-1)+1 = lineCount lines below where "You: /help" ended
|
|
300
301
|
if (lineCount > 0) {
|
|
301
|
-
|
|
302
|
-
|
|
302
|
+
ttyWrite(ANSI.MOVE_UP(lineCount));
|
|
303
|
+
ttyWrite(ANSI.CLEAR_TO_END);
|
|
303
304
|
}
|
|
304
305
|
// Print newline so next prompt appears on fresh line
|
|
305
306
|
process.stdout.write('\n');
|
|
306
307
|
// Show cursor
|
|
307
|
-
|
|
308
|
+
ttyWrite(ANSI.SHOW_CURSOR);
|
|
308
309
|
// Restore raw mode to original state
|
|
309
310
|
if (process.stdin.isTTY && !wasRawMode) {
|
|
310
311
|
process.stdin.setRawMode(false);
|
|
@@ -42,6 +42,7 @@ import * as terminal from '../terminal.js';
|
|
|
42
42
|
import { getStyles } from '../../themes/index.js';
|
|
43
43
|
import { OverlayLifecycle } from './overlay-lifecycle.js';
|
|
44
44
|
import { debugLog } from '../../utils/debug-log.js';
|
|
45
|
+
import { ttyWrite } from '../terminal.js';
|
|
45
46
|
// =============================================================================
|
|
46
47
|
// BaseOverlay Abstract Class
|
|
47
48
|
// =============================================================================
|
|
@@ -150,7 +151,7 @@ export class BaseOverlay {
|
|
|
150
151
|
this.state.maxLineCount = 0;
|
|
151
152
|
// Explicitly move cursor to home and save position
|
|
152
153
|
// This ensures we're at (0,0) regardless of what clearScreen() did
|
|
153
|
-
|
|
154
|
+
ttyWrite('\x1b[H');
|
|
154
155
|
terminal.saveCursor();
|
|
155
156
|
}
|
|
156
157
|
// ===========================================================================
|
|
@@ -14,6 +14,7 @@ import { markedTerminal } from 'marked-terminal';
|
|
|
14
14
|
import { BaseOverlayV2 } from '../../base/overlay-base-v2.js';
|
|
15
15
|
import { getCurrentTheme } from '../../../themes/index.js';
|
|
16
16
|
import * as terminal from '../../terminal.js';
|
|
17
|
+
import { ttyWrite } from '../../terminal.js';
|
|
17
18
|
import { ARTIFACT_TYPE_LABELS } from '../../constants/labels.js';
|
|
18
19
|
// =============================================================================
|
|
19
20
|
// Alternate Screen Buffer Management
|
|
@@ -22,14 +23,14 @@ let inAlternateScreen = false;
|
|
|
22
23
|
function enterAlternateScreen() {
|
|
23
24
|
if (inAlternateScreen)
|
|
24
25
|
return;
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
ttyWrite('\x1b[?1049h'); // Switch to alternate screen
|
|
27
|
+
ttyWrite('\x1b[H'); // Move cursor to home
|
|
27
28
|
inAlternateScreen = true;
|
|
28
29
|
}
|
|
29
30
|
function exitAlternateScreen() {
|
|
30
31
|
if (!inAlternateScreen)
|
|
31
32
|
return;
|
|
32
|
-
|
|
33
|
+
ttyWrite('\x1b[?1049l'); // Switch back to main screen
|
|
33
34
|
inAlternateScreen = false;
|
|
34
35
|
}
|
|
35
36
|
// Ensure we exit alternate screen on process termination
|
|
@@ -42,10 +43,14 @@ function setupAlternateScreenCleanup() {
|
|
|
42
43
|
cleanup();
|
|
43
44
|
process.exit(130);
|
|
44
45
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Windows doesn't deliver SIGTERM — Node silently ignores the listener
|
|
47
|
+
// there, but we add the platform guard for clarity.
|
|
48
|
+
if (process.platform !== 'win32') {
|
|
49
|
+
process.on('SIGTERM', () => {
|
|
50
|
+
cleanup();
|
|
51
|
+
process.exit(143);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
setupAlternateScreenCleanup();
|
|
51
56
|
// =============================================================================
|
|
@@ -611,9 +611,9 @@ export class ConfigOverlayV2 extends BaseOverlayV2 {
|
|
|
611
611
|
*/
|
|
612
612
|
toggleOrCycleItem() {
|
|
613
613
|
const item = this.state.configItems[this.state.selectedItem];
|
|
614
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
614
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- selectedItem can briefly point past array end during async re-render
|
|
615
615
|
if (!item)
|
|
616
|
-
return null;
|
|
616
|
+
return null;
|
|
617
617
|
if (item.type === 'boolean') {
|
|
618
618
|
const newValue = !item.value;
|
|
619
619
|
item.value = newValue;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import chalk from 'chalk';
|
|
15
15
|
import hljs from 'highlight.js';
|
|
16
|
+
import { ttyWrite } from '../../terminal.js';
|
|
16
17
|
// =============================================================================
|
|
17
18
|
// Alternate Screen Buffer Management
|
|
18
19
|
// =============================================================================
|
|
@@ -20,14 +21,14 @@ let inAlternateScreen = false;
|
|
|
20
21
|
function enterAlternateScreen() {
|
|
21
22
|
if (inAlternateScreen)
|
|
22
23
|
return;
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
ttyWrite('\x1b[?1049h'); // Switch to alternate screen
|
|
25
|
+
ttyWrite('\x1b[H'); // Move cursor to home
|
|
25
26
|
inAlternateScreen = true;
|
|
26
27
|
}
|
|
27
28
|
function exitAlternateScreen() {
|
|
28
29
|
if (!inAlternateScreen)
|
|
29
30
|
return;
|
|
30
|
-
|
|
31
|
+
ttyWrite('\x1b[?1049l'); // Switch back to main screen
|
|
31
32
|
inAlternateScreen = false;
|
|
32
33
|
}
|
|
33
34
|
// Ensure we exit alternate screen on process termination
|
|
@@ -42,11 +43,14 @@ function setupAlternateScreenCleanup() {
|
|
|
42
43
|
cleanup();
|
|
43
44
|
process.exit(130);
|
|
44
45
|
});
|
|
45
|
-
// Handle termination
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
process.
|
|
49
|
-
|
|
46
|
+
// Handle termination (Windows doesn't deliver SIGTERM — Node silently
|
|
47
|
+
// ignores the listener there, but we add the platform guard for clarity)
|
|
48
|
+
if (process.platform !== 'win32') {
|
|
49
|
+
process.on('SIGTERM', () => {
|
|
50
|
+
cleanup();
|
|
51
|
+
process.exit(143);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
50
54
|
// Handle uncaught exceptions
|
|
51
55
|
process.on('uncaughtException', (err) => {
|
|
52
56
|
cleanup();
|
|
@@ -203,7 +203,10 @@ export class PendingOverlayV2 extends BaseOverlayV2 {
|
|
|
203
203
|
const agentLabel = `$${req.agentId}`;
|
|
204
204
|
const ctx = req.context;
|
|
205
205
|
const filePath = ctx?.input
|
|
206
|
-
? (
|
|
206
|
+
? (() => {
|
|
207
|
+
const input = ctx.input;
|
|
208
|
+
return (input.filePath ?? input.file_path ?? input.path ?? '');
|
|
209
|
+
})()
|
|
207
210
|
: '';
|
|
208
211
|
const fileName = filePath.split('/').pop() || filePath;
|
|
209
212
|
// Header
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* clear/render and console output.
|
|
7
7
|
*/
|
|
8
8
|
import * as terminal from './terminal.js';
|
|
9
|
+
import { ttyWrite } from './terminal.js';
|
|
9
10
|
import { getVisibleLength } from './line-utils.js';
|
|
10
11
|
import { getStyles } from '../themes/index.js';
|
|
11
12
|
export class OverlayManager {
|
|
@@ -89,7 +90,7 @@ export class OverlayManager {
|
|
|
89
90
|
// ===========================================================================
|
|
90
91
|
enterFullscreenOverlayMode() {
|
|
91
92
|
this.host.clearFooter();
|
|
92
|
-
|
|
93
|
+
ttyWrite('\x1b[2J\x1b[H');
|
|
93
94
|
this.renderState = { lineCount: 0, maxLineCount: 0 };
|
|
94
95
|
}
|
|
95
96
|
enterInlineOverlayMode() {
|
|
@@ -127,9 +128,9 @@ export class OverlayManager {
|
|
|
127
128
|
clearOverlayRender() {
|
|
128
129
|
const linesToClear = this.renderState.maxLineCount;
|
|
129
130
|
if (linesToClear > 0) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
ttyWrite(`\x1b[${String(linesToClear)}A`);
|
|
132
|
+
ttyWrite('\r');
|
|
133
|
+
ttyWrite('\x1b[J');
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
renderOverlay() {
|
|
@@ -179,9 +180,9 @@ export class OverlayManager {
|
|
|
179
180
|
const { line, column } = content.cursorPosition;
|
|
180
181
|
const linesFromEnd = paddedLines.length - 1 - line;
|
|
181
182
|
if (linesFromEnd > 0) {
|
|
182
|
-
|
|
183
|
+
ttyWrite(`\x1b[${String(linesFromEnd)}A`);
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
+
ttyWrite(`\x1b[${String(column)}G`);
|
|
185
186
|
}
|
|
186
187
|
terminal.showCursor();
|
|
187
188
|
}
|
|
@@ -229,7 +229,8 @@ export function renderItem(item, config) {
|
|
|
229
229
|
// If we add items that don't add trailing blanks, or if the rendering
|
|
230
230
|
// order changes, this could cause visual artifacts (overwriting content).
|
|
231
231
|
// If issues arise, consider tracking the last printed item type instead.
|
|
232
|
-
process.stdout.
|
|
232
|
+
if (process.stdout.isTTY)
|
|
233
|
+
process.stdout.write('\x1b[1A'); // Move up one line
|
|
233
234
|
}
|
|
234
235
|
// Show interrupted line with mascot
|
|
235
236
|
const suggestion = item.suggestion ?? 'What should I do instead?';
|
|
@@ -407,8 +407,7 @@ export class TerminalRenderer extends EventEmitter {
|
|
|
407
407
|
finally {
|
|
408
408
|
this.isRendering = false;
|
|
409
409
|
// Check if another render was requested during this one
|
|
410
|
-
//
|
|
411
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- renderPending can be flipped by callbacks during render
|
|
412
411
|
if (this.renderPending) {
|
|
413
412
|
this.requestRender();
|
|
414
413
|
}
|
package/dist/ui/terminal-ui.js
CHANGED
|
@@ -542,8 +542,9 @@ export class TerminalUI extends EventEmitter {
|
|
|
542
542
|
*/
|
|
543
543
|
reRenderConversationWithVerbosity(verbosity) {
|
|
544
544
|
const s = getStyles();
|
|
545
|
-
// Clear screen and scrollback buffer
|
|
546
|
-
process.stdout.
|
|
545
|
+
// Clear screen and scrollback buffer (TTY-only — keep piped output clean)
|
|
546
|
+
if (process.stdout.isTTY)
|
|
547
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
547
548
|
// Reset footer state
|
|
548
549
|
this.footer.resetRenderState();
|
|
549
550
|
// Show mode indicator at top for temp modes
|
|
@@ -641,8 +642,9 @@ export class TerminalUI extends EventEmitter {
|
|
|
641
642
|
* Called when verbose mode changes.
|
|
642
643
|
*/
|
|
643
644
|
reRenderConversation() {
|
|
644
|
-
// Clear screen AND scrollback buffer, then move to home
|
|
645
|
-
process.stdout.
|
|
645
|
+
// Clear screen AND scrollback buffer, then move to home (TTY-only — keep piped output clean)
|
|
646
|
+
if (process.stdout.isTTY)
|
|
647
|
+
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
646
648
|
// Reset footer state since we cleared everything
|
|
647
649
|
this.footer.resetRenderState();
|
|
648
650
|
// Show filter indicator if active
|
package/dist/ui/terminal.d.ts
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Low-level terminal operations using ANSI escape codes.
|
|
5
5
|
* Pure functions with no state.
|
|
6
|
+
*
|
|
7
|
+
* All output is gated through `ttyWrite()` so that pipes and redirected
|
|
8
|
+
* streams don't get raw escape sequences. `compilr | tee output.log`
|
|
9
|
+
* should produce a clean log, not a binary mess.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Write a control sequence only when stdout is an interactive TTY.
|
|
13
|
+
* No-op when output is piped or redirected — keeps log files clean.
|
|
6
14
|
*/
|
|
15
|
+
export declare function ttyWrite(seq: string): void;
|
|
7
16
|
/**
|
|
8
17
|
* Set terminal window title
|
|
9
18
|
*/
|
package/dist/ui/terminal.js
CHANGED
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Low-level terminal operations using ANSI escape codes.
|
|
5
5
|
* Pure functions with no state.
|
|
6
|
+
*
|
|
7
|
+
* All output is gated through `ttyWrite()` so that pipes and redirected
|
|
8
|
+
* streams don't get raw escape sequences. `compilr | tee output.log`
|
|
9
|
+
* should produce a clean log, not a binary mess.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Write a control sequence only when stdout is an interactive TTY.
|
|
13
|
+
* No-op when output is piped or redirected — keeps log files clean.
|
|
6
14
|
*/
|
|
15
|
+
export function ttyWrite(seq) {
|
|
16
|
+
if (process.stdout.isTTY) {
|
|
17
|
+
process.stdout.write(seq);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
// =============================================================================
|
|
8
21
|
// Terminal Title
|
|
9
22
|
// =============================================================================
|
|
@@ -11,7 +24,7 @@
|
|
|
11
24
|
* Set terminal window title
|
|
12
25
|
*/
|
|
13
26
|
export function setTitle(title) {
|
|
14
|
-
|
|
27
|
+
ttyWrite(`\x1b]0;${title}\x07`);
|
|
15
28
|
}
|
|
16
29
|
// =============================================================================
|
|
17
30
|
// Terminal Dimensions
|
|
@@ -36,7 +49,7 @@ export function getTerminalHeight() {
|
|
|
36
49
|
*/
|
|
37
50
|
export function moveCursorUp(n) {
|
|
38
51
|
if (n > 0) {
|
|
39
|
-
|
|
52
|
+
ttyWrite(`\x1b[${String(n)}A`);
|
|
40
53
|
}
|
|
41
54
|
}
|
|
42
55
|
/**
|
|
@@ -44,32 +57,32 @@ export function moveCursorUp(n) {
|
|
|
44
57
|
*/
|
|
45
58
|
export function moveCursorDown(n) {
|
|
46
59
|
if (n > 0) {
|
|
47
|
-
|
|
60
|
+
ttyWrite(`\x1b[${String(n)}B`);
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
/**
|
|
51
64
|
* Move cursor to column (1-indexed)
|
|
52
65
|
*/
|
|
53
66
|
export function moveCursorToColumn(col) {
|
|
54
|
-
|
|
67
|
+
ttyWrite(`\x1b[${String(col)}G`);
|
|
55
68
|
}
|
|
56
69
|
/**
|
|
57
70
|
* Move cursor to beginning of line
|
|
58
71
|
*/
|
|
59
72
|
export function moveCursorToLineStart() {
|
|
60
|
-
|
|
73
|
+
ttyWrite('\r');
|
|
61
74
|
}
|
|
62
75
|
/**
|
|
63
76
|
* Save cursor position
|
|
64
77
|
*/
|
|
65
78
|
export function saveCursor() {
|
|
66
|
-
|
|
79
|
+
ttyWrite('\x1b[s');
|
|
67
80
|
}
|
|
68
81
|
/**
|
|
69
82
|
* Restore cursor position
|
|
70
83
|
*/
|
|
71
84
|
export function restoreCursor() {
|
|
72
|
-
|
|
85
|
+
ttyWrite('\x1b[u');
|
|
73
86
|
}
|
|
74
87
|
// =============================================================================
|
|
75
88
|
// Cursor Visibility
|
|
@@ -78,13 +91,13 @@ export function restoreCursor() {
|
|
|
78
91
|
* Hide cursor
|
|
79
92
|
*/
|
|
80
93
|
export function hideCursor() {
|
|
81
|
-
|
|
94
|
+
ttyWrite('\x1b[?25l');
|
|
82
95
|
}
|
|
83
96
|
/**
|
|
84
97
|
* Show cursor
|
|
85
98
|
*/
|
|
86
99
|
export function showCursor() {
|
|
87
|
-
|
|
100
|
+
ttyWrite('\x1b[?25h');
|
|
88
101
|
}
|
|
89
102
|
// =============================================================================
|
|
90
103
|
// Clearing
|
|
@@ -93,13 +106,13 @@ export function showCursor() {
|
|
|
93
106
|
* Clear current line
|
|
94
107
|
*/
|
|
95
108
|
export function clearLine() {
|
|
96
|
-
|
|
109
|
+
ttyWrite('\r\x1b[K');
|
|
97
110
|
}
|
|
98
111
|
/**
|
|
99
112
|
* Clear from cursor to end of screen
|
|
100
113
|
*/
|
|
101
114
|
export function clearToEndOfScreen() {
|
|
102
|
-
|
|
115
|
+
ttyWrite('\x1b[J');
|
|
103
116
|
}
|
|
104
117
|
/**
|
|
105
118
|
* Clear N lines above cursor (including current line)
|
|
@@ -118,7 +131,7 @@ export function clearLinesAbove(count) {
|
|
|
118
131
|
* Clear entire screen
|
|
119
132
|
*/
|
|
120
133
|
export function clearScreen() {
|
|
121
|
-
|
|
134
|
+
ttyWrite('\x1b[2J\x1b[H');
|
|
122
135
|
}
|
|
123
136
|
/**
|
|
124
137
|
* Enter alternate screen buffer.
|
|
@@ -131,14 +144,14 @@ export function enterAlternateScreen() {
|
|
|
131
144
|
// \x1b[?1000l - disable mouse click tracking
|
|
132
145
|
// \x1b[?1002l - disable mouse drag tracking
|
|
133
146
|
// \x1b[?1003l - disable all mouse tracking
|
|
134
|
-
|
|
147
|
+
ttyWrite('\x1b[?1049h\x1b[?1000l\x1b[?1002l\x1b[?1003l');
|
|
135
148
|
}
|
|
136
149
|
/**
|
|
137
150
|
* Leave alternate screen buffer.
|
|
138
151
|
* Returns to main screen (previous content restored).
|
|
139
152
|
*/
|
|
140
153
|
export function leaveAlternateScreen() {
|
|
141
|
-
|
|
154
|
+
ttyWrite('\x1b[?1049l');
|
|
142
155
|
}
|
|
143
156
|
// =============================================================================
|
|
144
157
|
// Line Calculations
|
|
@@ -305,5 +318,5 @@ export function writeLine(text = '') {
|
|
|
305
318
|
* Ring terminal bell
|
|
306
319
|
*/
|
|
307
320
|
export function bell() {
|
|
308
|
-
|
|
321
|
+
ttyWrite('\x07');
|
|
309
322
|
}
|