winter-super-cli 2026.6.6 → 2026.6.8
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/bin/winter.js +1 -0
- package/package.json +1 -1
- package/src/agent/runtime.js +10 -16
- package/src/ai/model-capabilities.js +15 -0
- package/src/ai/prompts/system-prompt.js +26 -4
- package/src/ai/providers.js +132 -55
- package/src/ai/small-model-amplifier.js +2 -2
- package/src/cli/commands.js +12 -0
- package/src/cli/context-loader.js +1 -1
- package/src/cli/diff-view.js +88 -32
- package/src/cli/input-controller.js +77 -44
- package/src/cli/markdown-format.js +12 -7
- package/src/cli/prompt-builder.js +20 -11
- package/src/cli/repl-commands.js +3 -0
- package/src/cli/repl.js +299 -358
- package/src/cli/slash-commands.js +1 -0
- package/src/cli/snowflake-logo.js +64 -86
- package/src/cli/terminal-manager.js +74 -0
- package/src/cli/terminal-ui.js +139 -86
- package/src/cli/tool-runtime.js +8 -3
- package/src/cli/tui.js +195 -0
- package/src/context/token-juice.js +37 -10
- package/src/tools/executor.js +78 -3
|
@@ -18,6 +18,7 @@ export const SLASH_COMMANDS = [
|
|
|
18
18
|
{ cmd: '/compress', desc: 'Compress old conversation context' },
|
|
19
19
|
{ cmd: '/context', desc: 'Inspect what Winter sends to the model', usage: '/context [task]' },
|
|
20
20
|
{ cmd: '/scorecard', desc: 'Score Winter against Codebuff/Codex/Claude capability gates' },
|
|
21
|
+
{ cmd: '/tui', desc: 'Show Winter TUI dashboard' },
|
|
21
22
|
{ cmd: '/plan', desc: 'Create/view plans' },
|
|
22
23
|
{ cmd: '/plans', desc: 'List active plans' },
|
|
23
24
|
{ cmd: '/tasks', desc: 'List tasks' },
|
|
@@ -6,6 +6,11 @@ export const colors = {
|
|
|
6
6
|
dim: '\x1b[2m',
|
|
7
7
|
italic: '\x1b[3m',
|
|
8
8
|
underline: '\x1b[4m',
|
|
9
|
+
bgBlack: '\x1b[40m',
|
|
10
|
+
bgBlue: '\x1b[44m',
|
|
11
|
+
bgCyan: '\x1b[46m',
|
|
12
|
+
bgWhite: '\x1b[47m',
|
|
13
|
+
bgBrightBlue: '\x1b[104m',
|
|
9
14
|
cyan: '\x1b[36m',
|
|
10
15
|
blue: '\x1b[34m',
|
|
11
16
|
magenta: '\x1b[35m',
|
|
@@ -13,112 +18,85 @@ export const colors = {
|
|
|
13
18
|
red: '\x1b[31m',
|
|
14
19
|
yellow: '\x1b[33m',
|
|
15
20
|
green: '\x1b[32m',
|
|
16
|
-
|
|
21
|
+
brightGreen: '\x1b[92m',
|
|
17
22
|
bgMagenta: '\x1b[45m',
|
|
18
23
|
};
|
|
19
24
|
|
|
20
|
-
const DARK_THEME = {
|
|
21
|
-
const LIGHT_THEME = {
|
|
25
|
+
const DARK_THEME = { ...colors };
|
|
26
|
+
const LIGHT_THEME = { ...colors };
|
|
22
27
|
|
|
23
28
|
export function applyColorTheme(theme = 'dark') {
|
|
24
|
-
|
|
25
|
-
Object.assign(colors, selected, { theme: String(theme || 'dark').toLowerCase() === 'light' ? 'light' : 'dark' });
|
|
29
|
+
colors.theme = theme;
|
|
26
30
|
return colors.theme;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
const
|
|
30
|
-
ii
|
|
31
|
-
.i;
|
|
32
|
-
, ;i ,
|
|
33
|
-
;i::;:i;
|
|
34
|
-
,i1:
|
|
35
|
-
:,, ,, ;:,::i,;:i: , i ii;
|
|
36
|
-
,ii;;: ii ,1 :;iiiii: ;1 .1, i;:,
|
|
37
|
-
:::ii,,;1. ;iii; :1:,:i1;,
|
|
38
|
-
,;i:,i:i1:i, ,;i; ;i;1i;; :;:
|
|
39
|
-
,. ,i1iii11, ,;, ,11ii;ii;
|
|
40
|
-
:;: ::,,:i111i: ,, ,:
|
|
41
|
-
:: ,,, :i111i:,,,: :;:
|
|
42
|
-
:ii;ii11: ,;, 11iii1i:
|
|
43
|
-
:i: ;;i1;;; ;i;, ,i:1i:i,:i;:
|
|
44
|
-
,;1i;,:1; ;iiii 1;,,i1::,
|
|
45
|
-
,,;i, 1, i; :iiiii;: 1: ii ,;;ii,
|
|
46
|
-
;ii i, , :i::,;:,::;. ,: ,,:
|
|
47
|
-
:ii:
|
|
48
|
-
;i:;;:i;
|
|
49
|
-
;i
|
|
50
|
-
;i,
|
|
51
|
-
;i.
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
export const miniLogo = `${colors.cyan}${supportsUnicodeUi() ? '❄' : '*'}${colors.reset}`;
|
|
33
|
+
export const miniLogo = `${colors.cyan}❄${colors.reset}`;
|
|
55
34
|
|
|
56
35
|
export function welcomeBanner(version, info = {}) {
|
|
57
|
-
const
|
|
58
|
-
const displayPath = pPath; // Hiển thị đầy đủ đường dẫn chính xác
|
|
59
|
-
const pId = info.session || 'New';
|
|
36
|
+
const displayPath = info.project || 'Unknown';
|
|
60
37
|
const provider = info.provider || 'default';
|
|
61
38
|
const model = info.model || 'unknown';
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
${
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
40
|
+
const W = Math.max(60, Math.min(process.stdout.columns || 80, 100));
|
|
41
|
+
const white = colors.white;
|
|
42
|
+
const dim = colors.dim;
|
|
43
|
+
const bright = colors.bright;
|
|
44
|
+
const reset = colors.reset;
|
|
45
|
+
const green = '\x1b[92m';
|
|
46
|
+
const cyan = '\x1b[36m';
|
|
47
|
+
const bgBrightBlue = '\x1b[104m';
|
|
48
|
+
const bgBlue = '\x1b[48;5;236m';
|
|
49
|
+
|
|
50
|
+
const logo = [
|
|
51
|
+
'╔══════════════════════════════════════════════════════════════════╗',
|
|
52
|
+
'║ ║',
|
|
53
|
+
'║ ██╗ ██╗██╗███╗ ██╗████████╗███████╗██████╗ ║',
|
|
54
|
+
'║ ██║ ██║██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗ ║',
|
|
55
|
+
'║ ██║ █╗ ██║██║██╔██╗██║ ██║ █████╗ ██████╔╝ ║',
|
|
56
|
+
'║ ██║███╗██║██║██║╚████║ ██║ ██╔══╝ ██╔══██╗ ║',
|
|
57
|
+
'║ ╚███╔███╔╝██║██║ ╚███║ ██║ ███████╗██║ ██║ ║',
|
|
58
|
+
'║ ╚══╝╚══╝ ╚═╝╚═╝ ╚══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ║',
|
|
59
|
+
'║ ║',
|
|
60
|
+
'║ A I C o d i n g A s s i s t a n t ║',
|
|
61
|
+
'║══════════════════════════════════════════════════════════════════║',
|
|
62
|
+
'║ fb.com/iam.anhtu ║ github.com/anhtu1707 ║',
|
|
63
|
+
'╚══════════════════════════════════════════════════════════════════╝',
|
|
64
|
+
];
|
|
65
|
+
const logoLines = logo.map(line => `${bright}${cyan}${line}${reset}`);
|
|
66
|
+
|
|
67
|
+
const leftStatus = ` ${provider} · ${model} `;
|
|
68
|
+
const rightStatus = ` ESC×2 exit · /help `;
|
|
69
|
+
const padding = Math.max(0, W - leftStatus.length - rightStatus.length);
|
|
70
|
+
const statusBar = `${bgBlue}${white}${leftStatus}${' '.repeat(padding)}${rightStatus}${reset}`;
|
|
71
|
+
|
|
72
|
+
const banner = [
|
|
73
|
+
...logoLines,
|
|
74
|
+
'',
|
|
75
|
+
`${white}Winter will run commands on your behalf to help you build.${reset}`,
|
|
76
|
+
'',
|
|
77
|
+
`${white}Directory${reset} ${dim}${displayPath}${reset}`,
|
|
78
|
+
'',
|
|
79
|
+
statusBar
|
|
80
|
+
].join('\n');
|
|
101
81
|
return banner;
|
|
102
82
|
}
|
|
103
83
|
|
|
104
84
|
export const statusIcons = {
|
|
105
|
-
online: `${colors.green}
|
|
106
|
-
offline: `${colors.dim}
|
|
107
|
-
warning: `${colors.yellow}
|
|
108
|
-
error: `${colors.red}
|
|
109
|
-
success: `${colors.green}
|
|
110
|
-
thinking: `${colors.cyan}
|
|
111
|
-
queue: `${colors.magenta}
|
|
85
|
+
online: `${colors.green}●${colors.reset}`,
|
|
86
|
+
offline: `${colors.dim}○${colors.reset}`,
|
|
87
|
+
warning: `${colors.yellow}◆${colors.reset}`,
|
|
88
|
+
error: `${colors.red}✖${colors.reset}`,
|
|
89
|
+
success: `${colors.green}✓${colors.reset}`,
|
|
90
|
+
thinking: `${colors.cyan}◉${colors.reset}`,
|
|
91
|
+
queue: `${colors.magenta}◎${colors.reset}`,
|
|
112
92
|
};
|
|
113
93
|
|
|
114
|
-
export function
|
|
115
|
-
|
|
116
|
-
return `${colors.dim}[${colors.cyan}session:${id}${colors.dim}]${colors.reset}`;
|
|
94
|
+
export function providerStatus(name, status) {
|
|
95
|
+
return `${statusIcons[status] || statusIcons.offline} ${name}`;
|
|
117
96
|
}
|
|
118
97
|
|
|
119
|
-
export function
|
|
120
|
-
|
|
121
|
-
return `${icon} ${name}`;
|
|
98
|
+
export function sessionIndicator(sessionId) {
|
|
99
|
+
return `session:${sessionId}`;
|
|
122
100
|
}
|
|
123
101
|
|
|
124
|
-
export const snowflake =
|
|
102
|
+
export const snowflake = '';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
|
|
3
|
+
class TerminalManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.isPromptVisible = false;
|
|
6
|
+
this.getLinesCountFn = null;
|
|
7
|
+
this.redrawFn = null;
|
|
8
|
+
this.onHideFn = null;
|
|
9
|
+
this._originalLog = console.log;
|
|
10
|
+
this._originalError = console.error;
|
|
11
|
+
this._isIntercepting = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
install() {
|
|
15
|
+
if (this._isIntercepting) return;
|
|
16
|
+
this._isIntercepting = true;
|
|
17
|
+
|
|
18
|
+
console.log = (...args) => this._interceptLog(this._originalLog, args);
|
|
19
|
+
console.error = (...args) => this._interceptLog(this._originalError, args);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
uninstall() {
|
|
23
|
+
if (!this._isIntercepting) return;
|
|
24
|
+
this._isIntercepting = false;
|
|
25
|
+
console.log = this._originalLog;
|
|
26
|
+
console.error = this._originalError;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setPromptState(isVisible, getLinesCountFn = null, redrawFn = null, onHideFn = null) {
|
|
30
|
+
this.isPromptVisible = isVisible;
|
|
31
|
+
this.getLinesCountFn = getLinesCountFn;
|
|
32
|
+
this.redrawFn = redrawFn;
|
|
33
|
+
this.onHideFn = onHideFn;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
hidePrompt() {
|
|
37
|
+
if (!this.isPromptVisible || !process.stdout.isTTY) return;
|
|
38
|
+
|
|
39
|
+
// Clear the current line first (the readline prompt itself)
|
|
40
|
+
readline.clearLine(process.stdout, 0);
|
|
41
|
+
readline.cursorTo(process.stdout, 0);
|
|
42
|
+
|
|
43
|
+
// If the prompt panel has multiple lines above it, move up and clear
|
|
44
|
+
const linesCount = this.getLinesCountFn ? this.getLinesCountFn() : 0;
|
|
45
|
+
if (linesCount > 1) {
|
|
46
|
+
readline.moveCursor(process.stdout, 0, -(linesCount - 1));
|
|
47
|
+
readline.clearScreenDown(process.stdout);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.onHideFn) {
|
|
51
|
+
this.onHideFn();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_interceptLog(originalFn, args) {
|
|
56
|
+
if (!this.isPromptVisible) {
|
|
57
|
+
originalFn.apply(console, args);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Hide prompt
|
|
62
|
+
this.hidePrompt();
|
|
63
|
+
|
|
64
|
+
// Print the actual log
|
|
65
|
+
originalFn.apply(console, args);
|
|
66
|
+
|
|
67
|
+
// Redraw prompt
|
|
68
|
+
if (this.redrawFn) {
|
|
69
|
+
this.redrawFn();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const terminalManager = new TerminalManager();
|
package/src/cli/terminal-ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
2
2
|
const ZERO_WIDTH_PATTERN = /[\u200B-\u200D\u2060\uFE0E\uFE0F]/u;
|
|
3
|
-
const WIDE_CODE_POINT_RANGES = [
|
|
3
|
+
const WIDE_CODE_POINT_RANGES = [
|
|
4
4
|
[0x1100, 0x115f],
|
|
5
5
|
[0x2329, 0x232a],
|
|
6
6
|
[0x2e80, 0x303e],
|
|
@@ -14,30 +14,30 @@ const WIDE_CODE_POINT_RANGES = [
|
|
|
14
14
|
[0x1f300, 0x1f64f],
|
|
15
15
|
[0x1f680, 0x1f6ff],
|
|
16
16
|
[0x1f900, 0x1f9ff],
|
|
17
|
-
[0x1fa70, 0x1faff],
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const UNICODE_BOX = {
|
|
21
|
-
topLeft: '╭',
|
|
22
|
-
topRight: '╮',
|
|
23
|
-
bottomLeft: '╰',
|
|
24
|
-
bottomRight: '╯',
|
|
25
|
-
horizontal: '─',
|
|
26
|
-
vertical: '│',
|
|
27
|
-
teeLeft: '├',
|
|
28
|
-
teeRight: '┤',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const ASCII_BOX = {
|
|
32
|
-
topLeft: '+',
|
|
33
|
-
topRight: '+',
|
|
34
|
-
bottomLeft: '+',
|
|
35
|
-
bottomRight: '+',
|
|
36
|
-
horizontal: '-',
|
|
37
|
-
vertical: '|',
|
|
38
|
-
teeLeft: '+',
|
|
39
|
-
teeRight: '+',
|
|
40
|
-
};
|
|
17
|
+
[0x1fa70, 0x1faff],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const UNICODE_BOX = {
|
|
21
|
+
topLeft: '╭',
|
|
22
|
+
topRight: '╮',
|
|
23
|
+
bottomLeft: '╰',
|
|
24
|
+
bottomRight: '╯',
|
|
25
|
+
horizontal: '─',
|
|
26
|
+
vertical: '│',
|
|
27
|
+
teeLeft: '├',
|
|
28
|
+
teeRight: '┤',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ASCII_BOX = {
|
|
32
|
+
topLeft: '+',
|
|
33
|
+
topRight: '+',
|
|
34
|
+
bottomLeft: '+',
|
|
35
|
+
bottomRight: '+',
|
|
36
|
+
horizontal: '-',
|
|
37
|
+
vertical: '|',
|
|
38
|
+
teeLeft: '+',
|
|
39
|
+
teeRight: '+',
|
|
40
|
+
};
|
|
41
41
|
|
|
42
42
|
export function stripAnsi(text) {
|
|
43
43
|
return String(text ?? '').replace(ANSI_PATTERN, '');
|
|
@@ -63,20 +63,19 @@ export function charDisplayWidth(char) {
|
|
|
63
63
|
return 1;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
export function terminalWidth(min = 72, max = 120, fallback = 88) {
|
|
67
|
-
const columns = process.stdout.columns || fallback;
|
|
68
|
-
return Math.max(min, Math.min(columns - 2, max));
|
|
69
|
-
}
|
|
70
|
-
|
|
66
|
+
export function terminalWidth(min = 72, max = 120, fallback = 88) {
|
|
67
|
+
const columns = process.stdout.columns || fallback;
|
|
68
|
+
return Math.max(min, Math.min(columns - 2, max));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
71
|
export function supportsUnicodeUi(env = process.env, platform = process.platform) {
|
|
72
72
|
if (env.WINTER_ASCII_UI === '1' || env.WINTER_ASCII_UI === 'true') return false;
|
|
73
|
-
|
|
74
|
-
if (platform !== 'win32') return true;
|
|
75
|
-
return Boolean(env.WT_SESSION || env.TERM_PROGRAM || env.TERM || env.ConEmuANSI === 'ON');
|
|
73
|
+
return true;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
export function getBoxChars() {
|
|
79
|
-
|
|
77
|
+
if (supportsUnicodeUi()) return UNICODE_BOX;
|
|
78
|
+
return ASCII_BOX;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
export function padVisible(text, width, fill = ' ') {
|
|
@@ -124,38 +123,38 @@ export function wrapText(text, width) {
|
|
|
124
123
|
return output;
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
export function chunkText(text, width) {
|
|
128
|
-
const chars = Array.from(stripAnsi(text));
|
|
129
|
-
const chunks = [];
|
|
130
|
-
let current = '';
|
|
131
|
-
let currentWidth = 0;
|
|
132
|
-
for (const char of chars) {
|
|
133
|
-
const charWidth = charDisplayWidth(char);
|
|
134
|
-
if (current && currentWidth + charWidth > width) {
|
|
135
|
-
chunks.push(current);
|
|
136
|
-
current = '';
|
|
137
|
-
currentWidth = 0;
|
|
138
|
-
}
|
|
139
|
-
current += char;
|
|
140
|
-
currentWidth += charWidth;
|
|
141
|
-
}
|
|
142
|
-
if (current) chunks.push(current);
|
|
143
|
-
return chunks.length > 0 ? chunks : [''];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function renderBox({
|
|
147
|
-
title = '',
|
|
148
|
-
body = [],
|
|
149
|
-
width,
|
|
150
|
-
borderColor = '\x1b[35m',
|
|
151
|
-
titleColor = '\x1b[36m',
|
|
152
|
-
reset = '\x1b[0m',
|
|
153
|
-
boxChars = getBoxChars(),
|
|
154
|
-
} = {}) {
|
|
155
|
-
const innerWidth = Math.max(28, (width || terminalWidth()) - 4);
|
|
156
|
-
const top = `${borderColor}${boxChars.topLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.topRight}${reset}`;
|
|
157
|
-
const bottom = `${borderColor}${boxChars.bottomLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.bottomRight}${reset}`;
|
|
158
|
-
const lines = [];
|
|
126
|
+
export function chunkText(text, width) {
|
|
127
|
+
const chars = Array.from(stripAnsi(text));
|
|
128
|
+
const chunks = [];
|
|
129
|
+
let current = '';
|
|
130
|
+
let currentWidth = 0;
|
|
131
|
+
for (const char of chars) {
|
|
132
|
+
const charWidth = charDisplayWidth(char);
|
|
133
|
+
if (current && currentWidth + charWidth > width) {
|
|
134
|
+
chunks.push(current);
|
|
135
|
+
current = '';
|
|
136
|
+
currentWidth = 0;
|
|
137
|
+
}
|
|
138
|
+
current += char;
|
|
139
|
+
currentWidth += charWidth;
|
|
140
|
+
}
|
|
141
|
+
if (current) chunks.push(current);
|
|
142
|
+
return chunks.length > 0 ? chunks : [''];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function renderBox({
|
|
146
|
+
title = '',
|
|
147
|
+
body = [],
|
|
148
|
+
width,
|
|
149
|
+
borderColor = '\x1b[35m',
|
|
150
|
+
titleColor = '\x1b[36m',
|
|
151
|
+
reset = '\x1b[0m',
|
|
152
|
+
boxChars = getBoxChars(),
|
|
153
|
+
} = {}) {
|
|
154
|
+
const innerWidth = Math.max(28, (width || terminalWidth()) - 4);
|
|
155
|
+
const top = `${borderColor}${boxChars.topLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.topRight}${reset}`;
|
|
156
|
+
const bottom = `${borderColor}${boxChars.bottomLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.bottomRight}${reset}`;
|
|
157
|
+
const lines = [];
|
|
159
158
|
const titleText = title ? ` ${title} ` : '';
|
|
160
159
|
|
|
161
160
|
if (titleText) {
|
|
@@ -166,45 +165,99 @@ export function renderBox({
|
|
|
166
165
|
const padding = Math.max(0, innerWidth - visible);
|
|
167
166
|
const left = index === 0 ? Math.floor(padding / 2) : 0;
|
|
168
167
|
const right = index === 0 ? padding - left : padding;
|
|
169
|
-
lines.push(`${borderColor}${boxChars.vertical}${reset}${' '.repeat(left)}${titleColor}${plainSegment}${reset}${' '.repeat(right)}${borderColor}${boxChars.vertical}${reset}`);
|
|
170
|
-
});
|
|
171
|
-
lines.push(`${borderColor}${boxChars.teeLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.teeRight}${reset}`);
|
|
172
|
-
}
|
|
168
|
+
lines.push(`${borderColor}${boxChars.vertical}${reset}${' '.repeat(left)}${titleColor}${plainSegment}${reset}${' '.repeat(right)}${borderColor}${boxChars.vertical}${reset}`);
|
|
169
|
+
});
|
|
170
|
+
lines.push(`${borderColor}${boxChars.teeLeft}${boxChars.horizontal.repeat(innerWidth)}${boxChars.teeRight}${reset}`);
|
|
171
|
+
}
|
|
173
172
|
|
|
174
173
|
for (const item of body) {
|
|
175
174
|
const rawText = String(item ?? '');
|
|
176
175
|
if (visibleWidth(rawText) <= innerWidth) {
|
|
177
176
|
const visible = visibleWidth(rawText);
|
|
178
177
|
const padding = Math.max(0, innerWidth - visible);
|
|
179
|
-
lines.push(`${borderColor}${boxChars.vertical}${reset} ${rawText}${' '.repeat(Math.max(0, padding - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
178
|
+
lines.push(`${borderColor}${boxChars.vertical}${reset} ${rawText}${' '.repeat(Math.max(0, padding - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
182
181
|
|
|
183
182
|
const wrapped = wrapText(rawText, innerWidth);
|
|
184
183
|
if (wrapped.length === 0) {
|
|
185
|
-
lines.push(`${borderColor}${boxChars.vertical}${reset} ${' '.repeat(Math.max(0, innerWidth - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
184
|
+
lines.push(`${borderColor}${boxChars.vertical}${reset} ${' '.repeat(Math.max(0, innerWidth - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
188
187
|
|
|
189
188
|
for (const segment of wrapped) {
|
|
190
189
|
const text = stripAnsi(segment);
|
|
191
190
|
const visible = visibleWidth(text);
|
|
192
191
|
const padding = Math.max(0, innerWidth - visible);
|
|
193
|
-
lines.push(`${borderColor}${boxChars.vertical}${reset} ${text}${' '.repeat(Math.max(0, padding - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
192
|
+
lines.push(`${borderColor}${boxChars.vertical}${reset} ${text}${' '.repeat(Math.max(0, padding - 1))}${borderColor}${boxChars.vertical}${reset}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
196
195
|
|
|
197
196
|
return [top, ...lines, bottom].join('\n');
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
export function renderKeyValueRows(rows, width, colors) {
|
|
201
|
-
const innerWidth = Math.max(28, (width || terminalWidth()) - 4);
|
|
202
|
-
const boxChars = getBoxChars();
|
|
203
|
-
return rows.map(([left, right]) => {
|
|
199
|
+
export function renderKeyValueRows(rows, width, colors) {
|
|
200
|
+
const innerWidth = Math.max(28, (width || terminalWidth()) - 4);
|
|
201
|
+
const boxChars = getBoxChars();
|
|
202
|
+
return rows.map(([left, right]) => {
|
|
204
203
|
const leftWidth = Math.floor(innerWidth * 0.5);
|
|
205
204
|
const rightWidth = innerWidth - leftWidth - 1;
|
|
206
205
|
const leftText = padVisible(left, leftWidth);
|
|
207
206
|
const rightText = padVisible(right, rightWidth);
|
|
208
|
-
return `${colors.border}${boxChars.vertical}${colors.reset} ${leftText}${colors.spacer}${rightText} ${colors.border}${boxChars.vertical}${colors.reset}`;
|
|
209
|
-
});
|
|
210
|
-
}
|
|
207
|
+
return `${colors.border}${boxChars.vertical}${colors.reset} ${leftText}${colors.spacer}${rightText} ${colors.border}${boxChars.vertical}${colors.reset}`;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
export const PANEL_HEIGHT = 5;
|
|
214
|
+
|
|
215
|
+
let _fixedEnabled = false;
|
|
216
|
+
|
|
217
|
+
export function enableFixedPanel() {
|
|
218
|
+
if (!process.stdout.isTTY) return false;
|
|
219
|
+
_fixedEnabled = true;
|
|
220
|
+
const rows = process.stdout.rows || 24;
|
|
221
|
+
const scrollBottom = Math.max(1, rows - PANEL_HEIGHT);
|
|
222
|
+
process.stdout.write("\x1b[1;" + scrollBottom + "r");
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function disableFixedPanel() {
|
|
227
|
+
_fixedEnabled = false;
|
|
228
|
+
process.stdout.write("\x1b[r");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function refreshFixedPanel() {
|
|
232
|
+
if (!_fixedEnabled || !process.stdout.isTTY) return;
|
|
233
|
+
const rows = process.stdout.rows || 24;
|
|
234
|
+
const scrollBottom = Math.max(1, rows - PANEL_HEIGHT);
|
|
235
|
+
process.stdout.write("\x1b[1;" + scrollBottom + "r");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function drawInFixedArea(content) {
|
|
239
|
+
if (!_fixedEnabled || !process.stdout.isTTY) return;
|
|
240
|
+
const rows = process.stdout.rows || 24;
|
|
241
|
+
const startRow = Math.max(1, rows - PANEL_HEIGHT + 1);
|
|
242
|
+
process.stdout.write("\x1b7");
|
|
243
|
+
process.stdout.write("\x1b[" + startRow + ";1H");
|
|
244
|
+
process.stdout.write("\x1b[J");
|
|
245
|
+
process.stdout.write(String(content ?? ""));
|
|
246
|
+
process.stdout.write("\x1b8");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function moveToScrollRegion() {
|
|
250
|
+
if (!process.stdout.isTTY) return;
|
|
251
|
+
const rows = process.stdout.rows || 24;
|
|
252
|
+
const scrollBottom = Math.max(1, rows - PANEL_HEIGHT);
|
|
253
|
+
process.stdout.write("\x1b[" + scrollBottom + ";1H");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function moveToPromptRow() {
|
|
257
|
+
if (!process.stdout.isTTY) return;
|
|
258
|
+
const rows = process.stdout.rows || 24;
|
|
259
|
+
// Position prompt at last scrollable row (just above the fixed panel)
|
|
260
|
+
const promptRow = Math.max(1, rows - PANEL_HEIGHT - 1);
|
|
261
|
+
process.stdout.write("\x1b[" + promptRow + ";1H");
|
|
262
|
+
}
|
|
263
|
+
|
package/src/cli/tool-runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TokenJuice } from '../context/token-juice.js';
|
|
2
|
+
import { getModelBudgetMultiplier } from '../ai/model-capabilities.js';
|
|
2
3
|
|
|
3
4
|
export function getMutatingToolNames() {
|
|
4
5
|
return new Set([
|
|
@@ -18,7 +19,8 @@ export function getMutatingToolNames() {
|
|
|
18
19
|
]);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function getToolResultPromptBudget(toolName, compact = false) {
|
|
22
|
+
export function getToolResultPromptBudget(toolName, compact = false, modelTier = '') {
|
|
23
|
+
const scale = getModelBudgetMultiplier(modelTier);
|
|
22
24
|
const budgets = {
|
|
23
25
|
Read: compact ? 2800 : 5000,
|
|
24
26
|
Grep: compact ? 2200 : 4200,
|
|
@@ -29,19 +31,20 @@ export function getToolResultPromptBudget(toolName, compact = false) {
|
|
|
29
31
|
BrowserDebug: compact ? 2200 : 4200,
|
|
30
32
|
NotebookRead: compact ? 2600 : 5000,
|
|
31
33
|
};
|
|
32
|
-
return budgets[toolName] || (compact ? 1800 : 3200);
|
|
34
|
+
return Math.max(400, Math.round((budgets[toolName] || (compact ? 1800 : 3200)) * scale));
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export function buildPromptToolResult({
|
|
36
38
|
toolName,
|
|
37
39
|
result,
|
|
38
40
|
compact = false,
|
|
41
|
+
modelTier = '',
|
|
39
42
|
compactText = (value, maxChars) => String(value || '').slice(0, maxChars),
|
|
40
43
|
summarizeToolResult = value => ({ ...value }),
|
|
41
44
|
} = {}) {
|
|
42
45
|
if (!result || typeof result !== 'object') return result;
|
|
43
46
|
|
|
44
|
-
const budget = getToolResultPromptBudget(toolName, compact);
|
|
47
|
+
const budget = getToolResultPromptBudget(toolName, compact, modelTier);
|
|
45
48
|
const copy = summarizeToolResult(result);
|
|
46
49
|
const preserveKeys = ['content', 'stdout', 'stderr', 'diff', 'matches', 'files', 'cells'];
|
|
47
50
|
let remaining = budget;
|
|
@@ -78,6 +81,7 @@ export async function buildPromptToolResultWithTokenJuice({
|
|
|
78
81
|
projectPath = process.cwd(),
|
|
79
82
|
tokenJuice,
|
|
80
83
|
compact = false,
|
|
84
|
+
modelTier = '',
|
|
81
85
|
compactText = (value, maxChars) => String(value || '').slice(0, maxChars),
|
|
82
86
|
summarizeToolResult = value => ({ ...value }),
|
|
83
87
|
} = {}) {
|
|
@@ -85,6 +89,7 @@ export async function buildPromptToolResultWithTokenJuice({
|
|
|
85
89
|
toolName,
|
|
86
90
|
result,
|
|
87
91
|
compact,
|
|
92
|
+
modelTier,
|
|
88
93
|
compactText,
|
|
89
94
|
summarizeToolResult,
|
|
90
95
|
});
|