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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import readline from 'readline';
|
|
2
2
|
import { colors } from './snowflake-logo.js';
|
|
3
|
-
import { padVisible, renderBox, terminalWidth
|
|
3
|
+
import { padVisible, renderBox, terminalWidth } from './terminal-ui.js';
|
|
4
|
+
import { buildTuiSnapshot, renderInputPanel } from './tui.js';
|
|
5
|
+
import { terminalManager } from './terminal-manager.js';
|
|
4
6
|
|
|
5
7
|
export class WinterInputController {
|
|
6
8
|
constructor(repl) {
|
|
@@ -11,52 +13,66 @@ export class WinterInputController {
|
|
|
11
13
|
const repl = this.repl;
|
|
12
14
|
if (!repl.running || repl.readlineClosed) return;
|
|
13
15
|
const panel = this.buildInputPanel();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
repl.
|
|
16
|
+
|
|
17
|
+
// Queue indicator
|
|
18
|
+
const queueCount = repl.taskQueue?.length || 0;
|
|
19
|
+
const queueTag = queueCount > 0
|
|
20
|
+
? ` ${colors.yellow}⧗ ${queueCount} pending${colors.reset}`
|
|
21
|
+
: '';
|
|
22
|
+
|
|
23
|
+
const lines = [panel.top + queueTag, panel.status, panel.hint].filter(l => l && l.trim() !== '');
|
|
24
|
+
|
|
25
|
+
const redrawFn = () => {
|
|
26
|
+
// Don't redraw if it shouldn't be visible anymore
|
|
27
|
+
if (!terminalManager.isPromptVisible) return;
|
|
28
|
+
process.stdout.write('\n' + lines.join('\n') + '\n');
|
|
29
|
+
if (typeof repl.rl?.setPrompt === 'function') {
|
|
30
|
+
repl.rl.setPrompt(panel.prompt);
|
|
31
|
+
}
|
|
32
|
+
if (repl.slashMenu?.open) {
|
|
33
|
+
this.renderSlashMenu();
|
|
34
|
+
} else {
|
|
35
|
+
if (repl.running && !repl.readlineClosed) {
|
|
36
|
+
repl.rl?.prompt?.(true);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getLinesCountFn = () => {
|
|
42
|
+
let count = lines.length + 2; // empty line + lines + prompt line
|
|
43
|
+
if (repl.slashMenu?.open && repl.slashMenu?.printedLines) {
|
|
44
|
+
count += repl.slashMenu.printedLines;
|
|
45
|
+
}
|
|
46
|
+
return count;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const onHideFn = () => {
|
|
50
|
+
if (repl.slashMenu) {
|
|
51
|
+
repl.slashMenu.printedLines = 0;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
terminalManager.setPromptState(true, getLinesCountFn, redrawFn, onHideFn);
|
|
56
|
+
redrawFn();
|
|
17
57
|
}
|
|
18
58
|
|
|
19
59
|
closeInputBox() {
|
|
20
60
|
const repl = this.repl;
|
|
21
61
|
if (!repl.running || repl.readlineClosed) return;
|
|
62
|
+
|
|
63
|
+
terminalManager.hidePrompt();
|
|
64
|
+
terminalManager.setPromptState(false);
|
|
65
|
+
|
|
22
66
|
const panel = this.buildInputPanel();
|
|
23
67
|
process.stdout.write(`${panel.bottom}\n`);
|
|
24
68
|
}
|
|
25
69
|
|
|
26
70
|
buildInputPanel() {
|
|
27
71
|
const repl = this.repl;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const provider = repl.ai?.getActiveProvider?.() || 'provider';
|
|
33
|
-
const model = repl.ai?.providers?.[provider]?.model || 'model';
|
|
34
|
-
const sessionId = repl.session?.getSessionId?.()?.slice(0, 8) || 'session';
|
|
35
|
-
const projectName = repl.projectPath ? repl.projectPath.split(/[\\/]/).filter(Boolean).pop() : 'project';
|
|
36
|
-
const queueText = repl.taskQueue?.length > 0 ? `queue:${repl.taskQueue.length}` : 'ready';
|
|
37
|
-
const title = ' Winter CLI ';
|
|
38
|
-
const titleWidth = visibleWidth(title);
|
|
39
|
-
const topFill = Math.max(0, width - titleWidth);
|
|
40
|
-
const leftFill = Math.floor(topFill / 2);
|
|
41
|
-
const rightFill = topFill - leftFill;
|
|
42
|
-
const statusText = [
|
|
43
|
-
`model ${provider}/${model}`,
|
|
44
|
-
`project ${projectName}`,
|
|
45
|
-
`session ${sessionId}`,
|
|
46
|
-
queueText,
|
|
47
|
-
].join(' ');
|
|
48
|
-
const hintText = '@file @Agent task !cmd Ctrl+V image /context /doctor full';
|
|
49
|
-
const hintInnerWidth = Math.max(20, width - 2);
|
|
50
|
-
const status = `${colors.magenta}${box.vertical}${colors.reset} ${colors.dim}${padVisible(statusText, hintInnerWidth)}${colors.reset} ${colors.magenta}${box.vertical}${colors.reset}`;
|
|
51
|
-
const hint = `${colors.magenta}${box.vertical}${colors.reset} ${colors.dim}${padVisible(hintText, hintInnerWidth)}${colors.reset} ${colors.magenta}${box.vertical}${colors.reset}`;
|
|
52
|
-
const prompt = `${colors.magenta}${box.vertical}${colors.reset} ${colors.bright}${colors.cyan}winter${colors.reset}${colors.dim} > ${colors.reset}`;
|
|
53
|
-
return {
|
|
54
|
-
top: `${colors.magenta}${box.topLeft}${box.horizontal.repeat(leftFill)}${title}${box.horizontal.repeat(rightFill)}${box.topRight}${colors.reset}`,
|
|
55
|
-
status,
|
|
56
|
-
hint,
|
|
57
|
-
prompt,
|
|
58
|
-
bottom: `${colors.magenta}${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}${colors.reset}`,
|
|
59
|
-
};
|
|
72
|
+
return renderInputPanel(buildTuiSnapshot(repl), {
|
|
73
|
+
colors,
|
|
74
|
+
width: terminalWidth(66, 124),
|
|
75
|
+
});
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
installSlashSuggestions() {
|
|
@@ -80,10 +96,24 @@ export class WinterInputController {
|
|
|
80
96
|
return;
|
|
81
97
|
}
|
|
82
98
|
|
|
83
|
-
if (key.name === 'escape'
|
|
84
|
-
repl.
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
if (key.name === 'escape') {
|
|
100
|
+
if (repl.isProcessing) {
|
|
101
|
+
// Cancel current AI turn
|
|
102
|
+
repl.isCancelled = true;
|
|
103
|
+
if (repl.spinner) repl.spinner.stop();
|
|
104
|
+
console.log(`\n${colors.red}[ Đã nhận lệnh HỦY... AI sẽ kết thúc ở thao tác tiếp theo ]${colors.reset}`);
|
|
105
|
+
} else {
|
|
106
|
+
// Double-ESC to end session
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
if (this._lastEscTime && (now - this._lastEscTime) < 500) {
|
|
109
|
+
console.log(`\n\n${colors.cyan}Cảm ơn đã sử dụng Winter!${colors.reset}`);
|
|
110
|
+
console.log(`${colors.yellow}Tiếp tục phiên làm việc:${colors.reset}`);
|
|
111
|
+
console.log(`${colors.bright}${colors.green}winter --session ${repl.session?.getSessionId?.() || ''}${colors.reset}\n`);
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
this._lastEscTime = now;
|
|
115
|
+
console.log(`${colors.dim}Press ESC again to end session${colors.reset}`);
|
|
116
|
+
}
|
|
87
117
|
return;
|
|
88
118
|
}
|
|
89
119
|
|
|
@@ -116,13 +146,13 @@ export class WinterInputController {
|
|
|
116
146
|
|
|
117
147
|
repl.inputQueue = repl.inputQueue
|
|
118
148
|
.then(async () => {
|
|
119
|
-
|
|
149
|
+
repl.closeInputBox?.();
|
|
120
150
|
await this.processPastedImageTask(prompt, image);
|
|
121
151
|
})
|
|
122
152
|
.catch((error) => {
|
|
123
|
-
|
|
153
|
+
repl.closeInputBox?.();
|
|
124
154
|
console.log(`\n${colors.red}✖ Paste image error: ${error.message}${colors.reset}\n`);
|
|
125
|
-
if (repl.running && !repl.readlineClosed)
|
|
155
|
+
if (repl.running && !repl.readlineClosed) repl.showInputPrompt?.();
|
|
126
156
|
});
|
|
127
157
|
return true;
|
|
128
158
|
} finally {
|
|
@@ -134,15 +164,17 @@ export class WinterInputController {
|
|
|
134
164
|
const repl = this.repl;
|
|
135
165
|
repl.isProcessing = true;
|
|
136
166
|
repl.isCancelled = false;
|
|
167
|
+
repl.currentAbortController = new AbortController();
|
|
137
168
|
try {
|
|
138
169
|
await repl.chat(prompt, [image]);
|
|
139
170
|
} finally {
|
|
140
171
|
repl.isProcessing = false;
|
|
172
|
+
repl.currentAbortController = null;
|
|
141
173
|
if (repl.taskQueue.length > 0) {
|
|
142
174
|
const nextTask = repl.taskQueue.shift();
|
|
143
175
|
setTimeout(() => repl.processInputTask(nextTask), 0);
|
|
144
176
|
} else if (!repl.readlineClosed) {
|
|
145
|
-
|
|
177
|
+
repl.showInputPrompt?.();
|
|
146
178
|
}
|
|
147
179
|
}
|
|
148
180
|
}
|
|
@@ -174,6 +206,7 @@ export class WinterInputController {
|
|
|
174
206
|
|
|
175
207
|
readline.moveCursor(process.stdout, 0, -printedLines);
|
|
176
208
|
readline.clearScreenDown(process.stdout);
|
|
209
|
+
repl.slashMenu.printedLines = 0;
|
|
177
210
|
}
|
|
178
211
|
|
|
179
212
|
handleSlashMenuKey(key = {}) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { highlight } from 'cli-highlight';
|
|
2
2
|
|
|
3
3
|
import { colors } from './snowflake-logo.js';
|
|
4
|
-
import { renderBox, terminalWidth, visibleWidth, wrapText, padVisible } from './terminal-ui.js';
|
|
4
|
+
import { renderBox, terminalWidth, visibleWidth, wrapText, padVisible, getBoxChars } from './terminal-ui.js';
|
|
5
5
|
|
|
6
6
|
export function formatMarkdown(text) {
|
|
7
7
|
if (!text) return '';
|
|
@@ -98,19 +98,24 @@ function renderMarkdownTableBlock(tableLines) {
|
|
|
98
98
|
const columnCount = Math.max(...rows.map(row => row.length), 0);
|
|
99
99
|
if (columnCount === 0) return tableLines.join('\n');
|
|
100
100
|
|
|
101
|
-
const boxWidth = Math.max(60, Math.min(terminalWidth(60, 100, 84), 100));
|
|
102
|
-
const innerWidth = boxWidth - 4;
|
|
103
|
-
const separatorWidth = (columnCount - 1) * 3;
|
|
104
|
-
const availableWidth = Math.max(columnCount * 8, innerWidth - separatorWidth);
|
|
105
|
-
|
|
106
101
|
const widestCells = Array.from({ length: columnCount }, (_, columnIndex) => {
|
|
107
102
|
return Math.max(8, ...rows.map(row => visibleWidth(row[columnIndex] || '')));
|
|
108
103
|
});
|
|
109
104
|
|
|
105
|
+
const separatorWidth = (columnCount - 1) * 3;
|
|
110
106
|
const widestTotal = widestCells.reduce((sum, width) => sum + width, 0);
|
|
107
|
+
const requiredInnerWidth = widestTotal + separatorWidth;
|
|
108
|
+
|
|
109
|
+
const maxAllowedWidth = terminalWidth(60, 160, 120);
|
|
110
|
+
const innerWidth = Math.min(requiredInnerWidth, maxAllowedWidth - 4);
|
|
111
|
+
const availableWidth = Math.max(columnCount * 8, innerWidth - separatorWidth);
|
|
112
|
+
const boxWidth = innerWidth + 4;
|
|
113
|
+
|
|
111
114
|
const scale = widestTotal > availableWidth ? availableWidth / widestTotal : 1;
|
|
112
115
|
const columnWidths = widestCells.map(width => Math.max(8, Math.floor(width * scale)));
|
|
113
116
|
|
|
117
|
+
const verticalChar = getBoxChars().vertical;
|
|
118
|
+
|
|
114
119
|
const renderRow = (cells) => {
|
|
115
120
|
const wrappedCells = cells.map((cell, index) => wrapText(cell || '', columnWidths[index]));
|
|
116
121
|
const lineCount = Math.max(...wrappedCells.map(lines => lines.length), 1);
|
|
@@ -122,7 +127,7 @@ function renderMarkdownTableBlock(tableLines) {
|
|
|
122
127
|
const cellLine = wrappedCells[columnIndex][lineIndex] || '';
|
|
123
128
|
parts.push(padVisible(cellLine, columnWidths[columnIndex]));
|
|
124
129
|
}
|
|
125
|
-
rendered.push(parts.join(
|
|
130
|
+
rendered.push(parts.join(` ${colors.dim}${verticalChar}${colors.reset} `));
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
return rendered;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from './runtime-env.js';
|
|
2
|
+
import { getModelBudgetMultiplier } from '../ai/model-capabilities.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* PromptBuilder — Builds system prompts for Winter CLI agents.
|
|
@@ -49,26 +50,32 @@ export class PromptBuilder {
|
|
|
49
50
|
const sessionContext = this.session?.getContext?.() || {};
|
|
50
51
|
const environmentSummary = this.tools?.getRuntimeEnvironmentSummary?.() || this._defaultEnvironmentSummary();
|
|
51
52
|
const requiredLocalResources = this.getRequiredLocalResources();
|
|
52
|
-
const
|
|
53
|
-
const
|
|
53
|
+
const scale = getModelBudgetMultiplier(options.modelTier);
|
|
54
|
+
const projectContextBudget = options.projectContextBudget || Math.round(3200 * scale);
|
|
55
|
+
const compactSystemPrompt = options.compactSystemPrompt ?? (scale <= 0.75);
|
|
56
|
+
const memoryBudget = Math.round(1200 * scale);
|
|
57
|
+
const planBudget = Math.round(1200 * scale);
|
|
58
|
+
const requiredResourcesBudget = Math.round((compactSystemPrompt ? 1200 : 1600) * scale);
|
|
59
|
+
const workflowBudget = Math.round(900 * scale);
|
|
60
|
+
const blueprintBudget = Math.round(700 * scale);
|
|
54
61
|
|
|
55
62
|
const memoryStr = memories.length > 0
|
|
56
|
-
? this._formatMemories(memories)
|
|
63
|
+
? this._formatMemories(memories, { maxTotalChars: memoryBudget })
|
|
57
64
|
: '';
|
|
58
65
|
const requiredResourcesStr = requiredLocalResources
|
|
59
|
-
? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources,
|
|
66
|
+
? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources, requiredResourcesBudget, 'required local resources')}`
|
|
60
67
|
: '';
|
|
61
68
|
const plansStr = plans.length > 0
|
|
62
|
-
? this._formatPlans(plans)
|
|
69
|
+
? this._formatPlans(plans, { maxTotalChars: planBudget })
|
|
63
70
|
: '';
|
|
64
71
|
const skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
|
|
65
72
|
? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
|
|
66
73
|
: '';
|
|
67
74
|
const workflowStr = sessionContext.workflowHints
|
|
68
|
-
? `\n## Workflow Auto-Selection\n${this._compactText(sessionContext.workflowHints,
|
|
75
|
+
? `\n## Workflow Auto-Selection\n${this._compactText(sessionContext.workflowHints, workflowBudget, 'workflow hints')}`
|
|
69
76
|
: '';
|
|
70
77
|
const blueprintStr = sessionContext.workflowBlueprint
|
|
71
|
-
? `\n## Profile Blueprint\n${this._compactText(sessionContext.workflowBlueprint,
|
|
78
|
+
? `\n## Profile Blueprint\n${this._compactText(sessionContext.workflowBlueprint, blueprintBudget, 'workflow blueprint')}`
|
|
72
79
|
: '';
|
|
73
80
|
const startupPlanStr = sessionContext.bootstrapPlan?.title
|
|
74
81
|
? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
|
|
@@ -164,12 +171,14 @@ export class PromptBuilder {
|
|
|
164
171
|
const plans = this.session?.getPlans?.() || [];
|
|
165
172
|
const sessionContext = this.session?.getContext?.() || {};
|
|
166
173
|
const requiredLocalResources = this.getRequiredLocalResources();
|
|
174
|
+
const scale = getModelBudgetMultiplier(this.ai?._modelTier || '');
|
|
175
|
+
const projectContextBudget = Math.round(3200 * (scale || 1));
|
|
167
176
|
|
|
168
|
-
const memoryStr = memories.length > 0 ? this._formatMemories(memories, { maxTotalChars: 900 }) : '';
|
|
177
|
+
const memoryStr = memories.length > 0 ? this._formatMemories(memories, { maxTotalChars: Math.round(900 * (scale || 1)) }) : '';
|
|
169
178
|
const requiredResourcesStr = requiredLocalResources
|
|
170
|
-
? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources, 1600, 'required local resources')}`
|
|
179
|
+
? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources, Math.round(1600 * (scale || 1)), 'required local resources')}`
|
|
171
180
|
: '';
|
|
172
|
-
const plansStr = plans.length > 0 ? this._formatPlans(plans, { maxTotalChars: 900 }) : '';
|
|
181
|
+
const plansStr = plans.length > 0 ? this._formatPlans(plans, { maxTotalChars: Math.round(900 * (scale || 1)) }) : '';
|
|
173
182
|
const skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
|
|
174
183
|
? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
|
|
175
184
|
: '';
|
|
@@ -217,7 +226,7 @@ export class PromptBuilder {
|
|
|
217
226
|
`Working directory: ${this.projectPath}`,
|
|
218
227
|
`Current session: ${this.session?.getSessionId?.()?.substring(0, 8) || 'unknown'}`,
|
|
219
228
|
`${requiredResourcesStr}${memoryStr}${plansStr}${skillsStr}${startupPlanStr}`,
|
|
220
|
-
context ? `\n## Project Context\n${this._compactText(context,
|
|
229
|
+
context ? `\n## Project Context\n${this._compactText(context, projectContextBudget, 'project context')}` : '',
|
|
221
230
|
].join('\n');
|
|
222
231
|
}
|
|
223
232
|
|
package/src/cli/repl-commands.js
CHANGED