winter-super-cli 2026.6.7 → 2026.6.9
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/package.json +1 -1
- package/src/agent/runtime.js +4 -2
- package/src/ai/benchmark.js +5 -5
- package/src/ai/model-capabilities.js +1 -1
- package/src/ai/orchestrator.js +6 -6
- package/src/ai/providers.js +20 -8
- package/src/cache/system.js +1 -1
- package/src/cli/at-context.js +1 -1
- package/src/cli/commands.js +1 -1
- package/src/cli/composer.js +2 -2
- package/src/cli/config.js +1 -1
- package/src/cli/diff-view.js +89 -33
- package/src/cli/ecc.js +2 -2
- package/src/cli/input-controller.js +45 -17
- package/src/cli/markdown-format.js +12 -7
- package/src/cli/repl-commands.js +44 -4
- package/src/cli/repl.js +127 -35
- package/src/cli/slash-commands.js +1 -0
- package/src/cli/spinner.js +7 -1
- package/src/cli/terminal-manager.js +74 -0
- package/src/cli/terminal-ui.js +2 -3
- package/src/cli/tui.js +18 -2
- package/src/codebase-index/indexer.js +1 -1
- package/src/codebase-index/search.js +1 -1
- package/src/codebase-index/watcher.js +1 -1
- package/src/design/commands.js +2 -2
- package/src/mcp/inline-complete.js +6 -6
- package/src/plugins/manager.js +5 -5
- package/src/session/manager.js +1 -1
- package/src/skills/manager.js +8 -8
- package/src/tools/agent.js +1 -1
- package/src/tools/executor.js +1 -1
- package/src/tools/insert-text.js +1 -1
- package/src/tools/interactive.js +1 -1
- package/src/tools/notebook.js +1 -1
- package/src/tools/scheduler.js +1 -1
- package/src/tools/str-replace-all.js +1 -1
- package/src/tools/todo.js +1 -1
- package/src/tools/web-archive.js +1 -1
package/package.json
CHANGED
package/src/agent/runtime.js
CHANGED
|
@@ -140,7 +140,7 @@ export class AgentRuntime {
|
|
|
140
140
|
const enrichedArgs = argParseError && !canUseRecoveredArgs ? {} : repl.enrichToolArgs(canonicalToolName, normalizedArgs, messages);
|
|
141
141
|
|
|
142
142
|
const icon = repl.useUnicodeUi
|
|
143
|
-
? (canonicalToolName === 'Bash' ? '
|
|
143
|
+
? (canonicalToolName === 'Bash' ? '$' : canonicalToolName === 'Read' ? '≡' : canonicalToolName === 'Write' ? '±' : canonicalToolName === 'Edit' ? '$' : canonicalToolName === 'Grep' ? '⌕' : canonicalToolName === 'Glob' ? '►' : '▶')
|
|
144
144
|
: `[${canonicalToolName}]`;
|
|
145
145
|
|
|
146
146
|
let proceed = true;
|
|
@@ -200,7 +200,9 @@ export class AgentRuntime {
|
|
|
200
200
|
}));
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
if (toolSummaries.length > 0) {
|
|
204
|
+
console.log(`\n${colors.dim}💡 Tip: Gõ /tool để xem lại toàn bộ dữ liệu (data) của các tool calls vừa chạy.${colors.reset}\n`);
|
|
205
|
+
}
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
if (usedTools && !finalContent) {
|
package/src/ai/benchmark.js
CHANGED
|
@@ -275,17 +275,17 @@ export class BenchmarkRunner {
|
|
|
275
275
|
|
|
276
276
|
const lines = [];
|
|
277
277
|
lines.push(`\n${colors.cyan}${'═'.repeat(60)}${colors.reset}`);
|
|
278
|
-
lines.push(`${colors.bright}${colors.cyan}
|
|
278
|
+
lines.push(`${colors.bright}${colors.cyan} ◆ WINTER MODEL BENCHMARK${colors.reset}`);
|
|
279
279
|
lines.push(`${colors.cyan}${'═'.repeat(60)}${colors.reset}`);
|
|
280
280
|
lines.push(` ${colors.dim}${benchmarkResult.timestamp}${colors.reset}`);
|
|
281
281
|
lines.push(` ${colors.dim}Total time: ${(benchmarkResult.totalElapsed / 1000).toFixed(1)}s${colors.reset}`);
|
|
282
282
|
lines.push('');
|
|
283
283
|
|
|
284
284
|
// Ranking
|
|
285
|
-
lines.push(`${colors.bright}
|
|
285
|
+
lines.push(`${colors.bright}★ RANKING${colors.reset}`);
|
|
286
286
|
lines.push(`${'─'.repeat(40)}`);
|
|
287
287
|
benchmarkResult.ranking.forEach((r, i) => {
|
|
288
|
-
const medal = i === 0 ? '
|
|
288
|
+
const medal = i === 0 ? '1.' : i === 1 ? '2.' : i === 2 ? '3.' : ` ${i + 1}.`;
|
|
289
289
|
const bar = this._scoreBar(r.score, 20);
|
|
290
290
|
lines.push(` ${medal} ${colors.bright}${r.name}${colors.reset} ${bar} ${r.score}%`);
|
|
291
291
|
lines.push(` ${colors.dim}Model: ${r.model} | Time: ${(r.elapsed / 1000).toFixed(1)}s${colors.reset}`);
|
|
@@ -295,7 +295,7 @@ export class BenchmarkRunner {
|
|
|
295
295
|
// Detail per provider
|
|
296
296
|
for (const [name, data] of Object.entries(benchmarkResult.providers)) {
|
|
297
297
|
lines.push(`${colors.bright}${'─'.repeat(50)}${colors.reset}`);
|
|
298
|
-
lines.push(`${colors.bright}
|
|
298
|
+
lines.push(`${colors.bright}≡ ${name}${colors.reset} ${colors.dim}(${data.model})${colors.reset}`);
|
|
299
299
|
lines.push(`${'─'.repeat(50)}`);
|
|
300
300
|
|
|
301
301
|
const categories = {};
|
|
@@ -316,7 +316,7 @@ export class BenchmarkRunner {
|
|
|
316
316
|
|
|
317
317
|
// Per-item breakdown
|
|
318
318
|
for (const r of data.results) {
|
|
319
|
-
const icon = r.score >= 0.8 ? '✅' : r.score >= 0.5 ? '
|
|
319
|
+
const icon = r.score >= 0.8 ? '✅' : r.score >= 0.5 ? '○' : r.score >= 0.2 ? '●' : '❌';
|
|
320
320
|
const label = r.type === 'question' ? r.id : r.title;
|
|
321
321
|
lines.push(` ${icon} ${colors.dim}${label}:${colors.reset} ${Math.round(r.score * 100)}% (${(r.elapsed / 1000).toFixed(1)}s)`);
|
|
322
322
|
// Show preview of answer
|
package/src/ai/orchestrator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ❄ MULTI-MODEL ORCHESTRATOR ❄
|
|
3
3
|
* Run multiple AI models in parallel, compare results, vote, and merge.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -69,7 +69,7 @@ Respond with the NUMBER of the best solution only, no explanation.
|
|
|
69
69
|
{ role: 'user', content: prompt },
|
|
70
70
|
];
|
|
71
71
|
|
|
72
|
-
console.log(`\n${colors.cyan}
|
|
72
|
+
console.log(`\n${colors.cyan}◆ Ensemble: running ${providers.length} providers in parallel...${colors.reset}`);
|
|
73
73
|
|
|
74
74
|
const results = await Promise.allSettled(
|
|
75
75
|
providers.map(async ([name, provider]) => {
|
|
@@ -160,7 +160,7 @@ Respond with the NUMBER of the best solution only, no explanation.
|
|
|
160
160
|
.join('\n\n---\n\n');
|
|
161
161
|
const votePrompt = template.replace('{solutions}', solutionBlocks);
|
|
162
162
|
|
|
163
|
-
console.log(`\n${colors.cyan}
|
|
163
|
+
console.log(`\n${colors.cyan}✓ Voting: comparing ${entries.length} solutions...${colors.reset}`);
|
|
164
164
|
|
|
165
165
|
// Choose judge — prefer best available model
|
|
166
166
|
const judgeProvider = options.judge || this._pickBestJudge(entries.map(([n]) => n));
|
|
@@ -185,7 +185,7 @@ Respond with the NUMBER of the best solution only, no explanation.
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
const [winnerName] = entries[winnerIndex];
|
|
188
|
-
console.log(` ${colors.green}
|
|
188
|
+
console.log(` ${colors.green}★ Winner: ${winnerName}${colors.reset}`);
|
|
189
189
|
|
|
190
190
|
return {
|
|
191
191
|
...ensembleResults,
|
|
@@ -202,7 +202,7 @@ Respond with the NUMBER of the best solution only, no explanation.
|
|
|
202
202
|
*/
|
|
203
203
|
async orchestrate(task, options = {}) {
|
|
204
204
|
const startTime = measureTime();
|
|
205
|
-
console.log(`\n${colors.cyan}
|
|
205
|
+
console.log(`\n${colors.cyan}» Pipeline orchestration starting...${colors.reset}`);
|
|
206
206
|
|
|
207
207
|
// Step 1: Classify task
|
|
208
208
|
const taskInfo = this._classifyTask(task);
|
|
@@ -293,7 +293,7 @@ Respond with the NUMBER of the best solution only, no explanation.
|
|
|
293
293
|
const requiredTier = complexity === 'deep' || complexity === 'complex'
|
|
294
294
|
? 'large' : complexity === 'moderate' ? 'medium' : 'small';
|
|
295
295
|
|
|
296
|
-
console.log(`\n${colors.cyan}
|
|
296
|
+
console.log(`\n${colors.cyan}◆ Smart route: required tier=${requiredTier}, task=${complexity}${colors.reset}`);
|
|
297
297
|
|
|
298
298
|
const messages = [
|
|
299
299
|
{ role: 'system', content: options.system || this.ai.getSystemPrompt() },
|
package/src/ai/providers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ❄ WINTER AI PROVIDER ❄
|
|
3
3
|
* Full Claude Code / Codex compatible AI integration
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -25,7 +25,7 @@ const RESERVED_CONFIG_SECTIONS = new Set([
|
|
|
25
25
|
'ui',
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
|
-
const DEFAULT_REQUEST_TIMEOUT_MS =
|
|
28
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 600000;
|
|
29
29
|
|
|
30
30
|
function isAuthError(error) {
|
|
31
31
|
const msg = String(error?.message || error || '');
|
|
@@ -47,6 +47,8 @@ function getRequestTimeoutMs(options = {}) {
|
|
|
47
47
|
function createTimeoutSignal(timeoutMs, externalSignal = null) {
|
|
48
48
|
const controller = new AbortController();
|
|
49
49
|
let timedOut = false;
|
|
50
|
+
let timer;
|
|
51
|
+
|
|
50
52
|
const onAbort = () => {
|
|
51
53
|
controller.abort(externalSignal?.reason || new DOMException('The operation was aborted.', 'AbortError'));
|
|
52
54
|
};
|
|
@@ -55,16 +57,24 @@ function createTimeoutSignal(timeoutMs, externalSignal = null) {
|
|
|
55
57
|
} else if (externalSignal) {
|
|
56
58
|
externalSignal.addEventListener('abort', onAbort, { once: true });
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
|
|
61
|
+
const startTimer = () => {
|
|
62
|
+
if (timer) clearTimeout(timer);
|
|
63
|
+
timer = setTimeout(() => {
|
|
64
|
+
timedOut = true;
|
|
65
|
+
controller.abort(new Error(`Winter request timed out after ${timeoutMs}ms`));
|
|
66
|
+
}, timeoutMs);
|
|
67
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
startTimer();
|
|
71
|
+
|
|
63
72
|
return {
|
|
64
73
|
signal: controller.signal,
|
|
65
74
|
timedOut: () => timedOut,
|
|
75
|
+
resetTimer: startTimer,
|
|
66
76
|
cleanup: () => {
|
|
67
|
-
clearTimeout(timer);
|
|
77
|
+
if (timer) clearTimeout(timer);
|
|
68
78
|
if (externalSignal) externalSignal.removeEventListener('abort', onAbort);
|
|
69
79
|
},
|
|
70
80
|
};
|
|
@@ -606,6 +616,8 @@ export class AIProviderManager {
|
|
|
606
616
|
let buffer = '';
|
|
607
617
|
|
|
608
618
|
for await (const chunk of response.body) {
|
|
619
|
+
if (timeout.resetTimer) timeout.resetTimer();
|
|
620
|
+
|
|
609
621
|
buffer += decoder.decode(chunk, { stream: true });
|
|
610
622
|
const lines = buffer.split(/\r?\n/);
|
|
611
623
|
buffer = lines.pop() || '';
|
package/src/cache/system.js
CHANGED
package/src/cli/at-context.js
CHANGED
package/src/cli/commands.js
CHANGED
package/src/cli/composer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ❄ COMPOSER MODE ❄
|
|
3
3
|
* Multi-file editing orchestration with batch apply/reject.
|
|
4
4
|
* Inspired by Cursor Composer.
|
|
5
5
|
*
|
|
@@ -78,7 +78,7 @@ export class Composer {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
console.log(`\n${renderBox({
|
|
81
|
-
title: `
|
|
81
|
+
title: ` ≡ Composer: ${this.pendingChanges.length} file(s) `,
|
|
82
82
|
width,
|
|
83
83
|
borderColor: colors.magenta,
|
|
84
84
|
titleColor: colors.bright,
|
package/src/cli/config.js
CHANGED
package/src/cli/diff-view.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ❄ DIFF VIEW — APPLY/REJECT UI ❄
|
|
3
3
|
* Provides diff preview and interactive accept/reject/edit workflow.
|
|
4
4
|
* Inspired by Cursor's apply/reject UI.
|
|
5
5
|
*/
|
|
@@ -8,9 +8,16 @@ import { promises as fs } from 'fs';
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import readline from 'readline';
|
|
10
10
|
import { spawn } from 'child_process';
|
|
11
|
+
import { highlight } from 'cli-highlight';
|
|
11
12
|
import { renderBox, terminalWidth, stripAnsi, wrapText, visibleWidth } from './terminal-ui.js';
|
|
12
13
|
import { colors } from './snowflake-logo.js';
|
|
13
14
|
|
|
15
|
+
// Setup background colors if not defined in snowflake-logo
|
|
16
|
+
const bgRed = '\x1b[41m';
|
|
17
|
+
const bgGreen = '\x1b[42m';
|
|
18
|
+
const bgDarkRed = '\x1b[48;5;52m';
|
|
19
|
+
const bgDarkGreen = '\x1b[48;5;22m';
|
|
20
|
+
|
|
14
21
|
export class DiffView {
|
|
15
22
|
constructor(options = {}) {
|
|
16
23
|
this.projectPath = options.projectPath || process.cwd();
|
|
@@ -124,42 +131,91 @@ export class DiffView {
|
|
|
124
131
|
// ── Private Methods ─────────────────────────────────
|
|
125
132
|
|
|
126
133
|
_renderDiff(title, diff, width) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
const innerWidth = Math.max(40, width - 6);
|
|
135
|
+
const header = `${colors.bright} ${title} ${colors.reset} ${colors.dim}— ${diff.additions} additions, ${diff.removals} deletions${colors.reset}`;
|
|
136
|
+
|
|
137
|
+
console.log(`\n${colors.magenta}┌${'─'.repeat(width - 2)}┐${colors.reset}`);
|
|
138
|
+
console.log(`${colors.magenta}│${colors.reset} ${header}${''.padEnd(Math.max(0, width - 4 - stripAnsi(header).length))}${colors.magenta}│${colors.reset}`);
|
|
139
|
+
console.log(`${colors.magenta}├${'─'.repeat(width - 2)}┤${colors.reset}`);
|
|
131
140
|
|
|
132
|
-
|
|
141
|
+
const maxLines = 40;
|
|
142
|
+
let printed = 0;
|
|
143
|
+
let lineNum = 0;
|
|
144
|
+
const contextLines = 2; // lines of context around changes
|
|
133
145
|
|
|
146
|
+
// Build display entries with context
|
|
147
|
+
const entries = [];
|
|
134
148
|
for (const part of diff.raw) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
const lines = part.value.replace(/\n$/, '').split('\n');
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
lineNum++;
|
|
152
|
+
if (part.added) {
|
|
153
|
+
entries.push({ type: 'add', num: lineNum, text: line });
|
|
154
|
+
} else if (part.removed) {
|
|
155
|
+
entries.push({ type: 'del', num: lineNum, text: line });
|
|
156
|
+
} else {
|
|
157
|
+
entries.push({ type: 'ctx', num: lineNum, text: line });
|
|
158
|
+
}
|
|
138
159
|
}
|
|
160
|
+
}
|
|
139
161
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
// Find which context lines to show (near changes)
|
|
163
|
+
const changeIndices = new Set();
|
|
164
|
+
entries.forEach((e, i) => {
|
|
165
|
+
if (e.type !== 'ctx') {
|
|
166
|
+
for (let j = Math.max(0, i - contextLines); j <= Math.min(entries.length - 1, i + contextLines); j++) {
|
|
167
|
+
changeIndices.add(j);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
143
171
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
172
|
+
let lastPrinted = -1;
|
|
173
|
+
for (let i = 0; i < entries.length && printed < maxLines; i++) {
|
|
174
|
+
const e = entries[i];
|
|
175
|
+
if (e.type === 'ctx' && !changeIndices.has(i)) continue;
|
|
176
|
+
|
|
177
|
+
// Show separator if there's a gap
|
|
178
|
+
if (lastPrinted >= 0 && i - lastPrinted > 1) {
|
|
179
|
+
console.log(`${colors.magenta}│${colors.reset} ${colors.dim}${'·'.repeat(Math.min(20, innerWidth))}${colors.reset}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const numStr = String(e.num).padStart(4);
|
|
183
|
+
const maxText = Math.max(10, innerWidth - 8);
|
|
184
|
+
const truncated = e.text.length > maxText ? e.text.slice(0, maxText - 3) + '...' : e.text;
|
|
185
|
+
|
|
186
|
+
// Detect language from file extension for highlight
|
|
187
|
+
const ext = path.extname(title).slice(1) || 'javascript';
|
|
188
|
+
const syntaxHighlight = (text) => {
|
|
189
|
+
try {
|
|
190
|
+
return highlight(text, { language: ext, ignoreIllegals: true });
|
|
191
|
+
} catch (e) {
|
|
192
|
+
return text;
|
|
152
193
|
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (e.type === 'add') {
|
|
197
|
+
const lineContent = syntaxHighlight(truncated);
|
|
198
|
+
console.log(`${colors.magenta}│${colors.reset} ${bgDarkGreen}${colors.white}${numStr} + ${lineContent}${' '.repeat(Math.max(0, innerWidth - stripAnsi(truncated).length - 8))}${colors.reset}`);
|
|
199
|
+
} else if (e.type === 'del') {
|
|
200
|
+
const lineContent = syntaxHighlight(truncated);
|
|
201
|
+
console.log(`${colors.magenta}│${colors.reset} ${bgDarkRed}${colors.white}${numStr} - ${lineContent}${' '.repeat(Math.max(0, innerWidth - stripAnsi(truncated).length - 8))}${colors.reset}`);
|
|
202
|
+
} else {
|
|
203
|
+
const lineContent = syntaxHighlight(truncated);
|
|
204
|
+
console.log(`${colors.magenta}│${colors.reset} ${colors.dim}${numStr} ${colors.reset}${lineContent}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
printed++;
|
|
208
|
+
lastPrinted = i;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (printed >= maxLines && entries.length > maxLines) {
|
|
212
|
+
const remaining = entries.filter(e => e.type !== 'ctx').length - printed;
|
|
213
|
+
if (remaining > 0) {
|
|
214
|
+
console.log(`${colors.magenta}│${colors.reset} ${colors.dim} ... and ${remaining} more changes${colors.reset}`);
|
|
153
215
|
}
|
|
154
216
|
}
|
|
155
217
|
|
|
156
|
-
console.log(
|
|
157
|
-
title: ` ${title} `,
|
|
158
|
-
width,
|
|
159
|
-
borderColor: colors.magenta,
|
|
160
|
-
titleColor: colors.bright,
|
|
161
|
-
body,
|
|
162
|
-
})}\n`);
|
|
218
|
+
console.log(`${colors.magenta}└${'─'.repeat(width - 2)}┘${colors.reset}\n`);
|
|
163
219
|
}
|
|
164
220
|
|
|
165
221
|
async _promptChoice() {
|
|
@@ -236,12 +292,12 @@ export class DiffView {
|
|
|
236
292
|
};
|
|
237
293
|
|
|
238
294
|
rl.question(
|
|
239
|
-
`\n${colors.cyan}Options:${colors.reset}\n` +
|
|
240
|
-
` ${colors.green}[a]${colors.reset} —
|
|
241
|
-
` ${colors.red}[r]${colors.reset} —
|
|
242
|
-
` ${colors.yellow}[m]${colors.reset} —
|
|
243
|
-
` ${colors.dim}[s]${colors.reset} — Skip\n` +
|
|
244
|
-
`${colors.yellow}Choose [a/r/m/s]: ${colors.reset}`,
|
|
295
|
+
`\n${colors.cyan}Edit Options:${colors.reset}\n` +
|
|
296
|
+
` ${colors.green}[a]${colors.reset} Accept — Apply the complete diff\n` +
|
|
297
|
+
` ${colors.red}[r]${colors.reset} Reject — Discard these changes\n` +
|
|
298
|
+
` ${colors.yellow}[m]${colors.reset} Manual — Open file in $EDITOR to manually resolve\n` +
|
|
299
|
+
` ${colors.dim}[s]${colors.reset} Skip — Skip for now\n` +
|
|
300
|
+
`${colors.yellow}→ Choose [a/r/m/s]: ${colors.reset}`,
|
|
245
301
|
onAnswer
|
|
246
302
|
);
|
|
247
303
|
|
package/src/cli/ecc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ❄ ECC Integration ❄
|
|
3
3
|
* Everything Claude Code — agent harness performance optimization system
|
|
4
4
|
* Browsing, searching, syncing ECC resources inside Winter
|
|
5
5
|
*/
|
|
@@ -246,7 +246,7 @@ export class ECCManager {
|
|
|
246
246
|
for (const section of sections) {
|
|
247
247
|
const entries = await this._listDir(section.path);
|
|
248
248
|
const count = entries ? entries.length : 0;
|
|
249
|
-
const icon = entries ? (count > 0 ? '
|
|
249
|
+
const icon = entries ? (count > 0 ? '►' : '►') : '⛔';
|
|
250
250
|
console.log(` ${icon} ${colors.green}${section.name}${colors.reset} ${colors.dim}(${count} items)${colors.reset}`);
|
|
251
251
|
console.log(` ${colors.dim}${section.desc}${colors.reset}`);
|
|
252
252
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import readline from 'readline';
|
|
2
2
|
import { colors } from './snowflake-logo.js';
|
|
3
|
-
import {
|
|
3
|
+
import { padVisible, renderBox, terminalWidth } from './terminal-ui.js';
|
|
4
4
|
import { buildTuiSnapshot, renderInputPanel } from './tui.js';
|
|
5
|
+
import { terminalManager } from './terminal-manager.js';
|
|
5
6
|
|
|
6
7
|
export class WinterInputController {
|
|
7
8
|
constructor(repl) {
|
|
@@ -20,17 +21,48 @@ export class WinterInputController {
|
|
|
20
21
|
: '';
|
|
21
22
|
|
|
22
23
|
const lines = [panel.top + queueTag, panel.status, panel.hint].filter(l => l && l.trim() !== '');
|
|
23
|
-
process.stdout.write('\n' + lines.join('\n') + '\n');
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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();
|
|
29
57
|
}
|
|
30
58
|
|
|
31
59
|
closeInputBox() {
|
|
32
60
|
const repl = this.repl;
|
|
33
61
|
if (!repl.running || repl.readlineClosed) return;
|
|
62
|
+
|
|
63
|
+
terminalManager.hidePrompt();
|
|
64
|
+
terminalManager.setPromptState(false);
|
|
65
|
+
|
|
34
66
|
const panel = this.buildInputPanel();
|
|
35
67
|
process.stdout.write(`${panel.bottom}\n`);
|
|
36
68
|
}
|
|
@@ -54,6 +86,12 @@ export class WinterInputController {
|
|
|
54
86
|
void this.handleDirectClipboardPaste();
|
|
55
87
|
return;
|
|
56
88
|
}
|
|
89
|
+
|
|
90
|
+
if (key.name === 'return' && (key.shift || key.meta)) {
|
|
91
|
+
repl.rl.write('\\\n');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
57
95
|
if (key.ctrl || key.meta) return;
|
|
58
96
|
|
|
59
97
|
if (typeof str === 'string' && str.length > 1) {
|
|
@@ -174,6 +212,7 @@ export class WinterInputController {
|
|
|
174
212
|
|
|
175
213
|
readline.moveCursor(process.stdout, 0, -printedLines);
|
|
176
214
|
readline.clearScreenDown(process.stdout);
|
|
215
|
+
repl.slashMenu.printedLines = 0;
|
|
177
216
|
}
|
|
178
217
|
|
|
179
218
|
handleSlashMenuKey(key = {}) {
|
|
@@ -247,23 +286,12 @@ export class WinterInputController {
|
|
|
247
286
|
|
|
248
287
|
this.clearSlashMenuRender();
|
|
249
288
|
|
|
250
|
-
const ASCII_BOX = {
|
|
251
|
-
topLeft: '+',
|
|
252
|
-
topRight: '+',
|
|
253
|
-
bottomLeft: '+',
|
|
254
|
-
bottomRight: '+',
|
|
255
|
-
horizontal: '-',
|
|
256
|
-
vertical: '|',
|
|
257
|
-
teeLeft: '+',
|
|
258
|
-
teeRight: '+',
|
|
259
|
-
};
|
|
260
289
|
const rendered = renderBox({
|
|
261
290
|
title: 'Command Palette',
|
|
262
291
|
width: terminalWidth(66, 110, 88),
|
|
263
292
|
borderColor: colors.magenta,
|
|
264
293
|
titleColor: colors.cyan,
|
|
265
294
|
body,
|
|
266
|
-
boxChars: ASCII_BOX,
|
|
267
295
|
});
|
|
268
296
|
|
|
269
297
|
process.stdout.write(`\n${rendered}\n`);
|
|
@@ -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;
|
package/src/cli/repl-commands.js
CHANGED
|
@@ -103,7 +103,7 @@ export async function handleSlashCommand(repl, input) {
|
|
|
103
103
|
console.log(`\n${colors.cyan}ECC ${result.section}:${colors.reset} ${result.description}`);
|
|
104
104
|
if (result.entries) {
|
|
105
105
|
result.entries.forEach(e => {
|
|
106
|
-
const icon = e.isDirectory ? '
|
|
106
|
+
const icon = e.isDirectory ? '►' : '📄';
|
|
107
107
|
console.log(` ${icon} ${e.name}`);
|
|
108
108
|
});
|
|
109
109
|
}
|
|
@@ -120,7 +120,7 @@ export async function handleSlashCommand(repl, input) {
|
|
|
120
120
|
console.log(` ${colors.dim}No results${colors.reset}`);
|
|
121
121
|
} else {
|
|
122
122
|
searchResult.matches.forEach(m => {
|
|
123
|
-
const icon = m.isDirectory ? '
|
|
123
|
+
const icon = m.isDirectory ? '►' : '📄';
|
|
124
124
|
console.log(` ${icon} [${m.section}] ${m.name}`);
|
|
125
125
|
});
|
|
126
126
|
}
|
|
@@ -174,7 +174,7 @@ export async function handleSlashCommand(repl, input) {
|
|
|
174
174
|
console.log(`\n${colors.cyan}=== Vote Results ===${colors.reset}`);
|
|
175
175
|
if (result.winner) {
|
|
176
176
|
const winner = result.results[result.winner];
|
|
177
|
-
console.log(`\n${colors.green}
|
|
177
|
+
console.log(`\n${colors.green}★ Winner: ${result.winner} (${winner.model})${colors.reset}`);
|
|
178
178
|
console.log(`${colors.dim}${'─'.repeat(50)}${colors.reset}`);
|
|
179
179
|
console.log(winner.content.slice(0, 2000));
|
|
180
180
|
if (winner.content.length > 2000) console.log(`${colors.dim}... (${winner.content.length - 2000} more chars)${colors.reset}`);
|
|
@@ -219,7 +219,7 @@ export async function handleSlashCommand(repl, input) {
|
|
|
219
219
|
}
|
|
220
220
|
{
|
|
221
221
|
const url = args[0].replace(/^['"]|['"]$/g, '');
|
|
222
|
-
console.log(`${colors.cyan}
|
|
222
|
+
console.log(`${colors.cyan}@ Đang mở: ${url}${colors.reset}`);
|
|
223
223
|
try {
|
|
224
224
|
// Thử WebFetch trước (nhanh, lấy text)
|
|
225
225
|
const result = await repl.tools.execute('WebFetch', { url });
|
|
@@ -289,6 +289,46 @@ export async function handleSlashCommand(repl, input) {
|
|
|
289
289
|
case '/history':
|
|
290
290
|
repl.showReplay(args[0] ? parseInt(args[0], 10) : 20);
|
|
291
291
|
return;
|
|
292
|
+
case '/tool':
|
|
293
|
+
const toolCalls = [];
|
|
294
|
+
const history = repl.session.getHistory(30);
|
|
295
|
+
let idxCounter = 1;
|
|
296
|
+
for (const msg of history) {
|
|
297
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
298
|
+
for (const tc of msg.tool_calls) {
|
|
299
|
+
toolCalls.push({ index: idxCounter++, call: tc });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (args.length === 0) {
|
|
304
|
+
if (toolCalls.length === 0) {
|
|
305
|
+
console.log(`${colors.dim}No recent tool calls found.${colors.reset}`);
|
|
306
|
+
} else {
|
|
307
|
+
console.log(`${colors.cyan}Recent Tool Calls:${colors.reset}`);
|
|
308
|
+
for (const tc of toolCalls.slice(-10)) {
|
|
309
|
+
const funcName = tc.call.function?.name || tc.call.toolName || 'Unknown';
|
|
310
|
+
console.log(` ${colors.green}[${tc.index}]${colors.reset} ${funcName}`);
|
|
311
|
+
}
|
|
312
|
+
console.log(`${colors.dim}Use /tool <index> to view arguments.${colors.reset}`);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
const targetIndex = parseInt(args[0], 10);
|
|
316
|
+
const target = toolCalls.find(tc => tc.index === targetIndex);
|
|
317
|
+
if (!target) {
|
|
318
|
+
console.log(`${colors.red}Tool call [${targetIndex}] not found.${colors.reset}`);
|
|
319
|
+
} else {
|
|
320
|
+
const funcName = target.call.function?.name || target.call.toolName || 'Unknown';
|
|
321
|
+
const argsStr = target.call.function?.arguments || target.call.toolArgs || '{}';
|
|
322
|
+
console.log(`\n${colors.cyan}=== Tool Call [${targetIndex}]: ${funcName} ===${colors.reset}`);
|
|
323
|
+
try {
|
|
324
|
+
const parsed = typeof argsStr === 'string' ? JSON.parse(argsStr) : argsStr;
|
|
325
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
326
|
+
} catch (e) {
|
|
327
|
+
console.log(argsStr);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
292
332
|
case '/new':
|
|
293
333
|
await repl.session.newSession({ project: repl.projectPath });
|
|
294
334
|
repl.history = [];
|