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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "winter-super-cli",
3
- "version": "2026.6.7",
3
+ "version": "2026.6.9",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
@@ -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' ? '' : canonicalToolName === 'Read' ? '📖' : canonicalToolName === 'Write' ? '✏️' : canonicalToolName === 'Edit' ? '🔧' : canonicalToolName === 'Grep' ? '🔍' : canonicalToolName === 'Glob' ? '📂' : '')
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
- console.log('');
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) {
@@ -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} 🧠 WINTER MODEL BENCHMARK${colors.reset}`);
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}🏆 RANKING${colors.reset}`);
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 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : ` ${i + 1}.`;
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}📊 ${name}${colors.reset} ${colors.dim}(${data.model})${colors.reset}`);
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 ? '🟡' : r.score >= 0.2 ? '🟠' : '❌';
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ MODEL CAPABILITIES ❄️
2
+ * MODEL CAPABILITIES
3
3
  * Detect AI model capability tier from model name.
4
4
  * Small models need aggressive prompting to compete with large ones.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ MULTI-MODEL ORCHESTRATOR ❄️
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}🧠 Ensemble: running ${providers.length} providers in parallel...${colors.reset}`);
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}🗳️ Voting: comparing ${entries.length} solutions...${colors.reset}`);
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}🏆 Winner: ${winnerName}${colors.reset}`);
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}🔀 Pipeline orchestration starting...${colors.reset}`);
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}🧠 Smart route: required tier=${requiredTier}, task=${complexity}${colors.reset}`);
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() },
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ WINTER AI PROVIDER ❄️
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 = 120000;
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
- const timer = setTimeout(() => {
59
- timedOut = true;
60
- controller.abort(new Error(`Winter request timed out after ${timeoutMs}ms`));
61
- }, timeoutMs);
62
- if (typeof timer.unref === 'function') timer.unref();
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() || '';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ CACHE SYSTEM ❄️
2
+ * CACHE SYSTEM
3
3
  * Context embedding and response caching
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ @-SYMBOLS CONTEXT SYSTEM ❄️
2
+ * @-SYMBOLS CONTEXT SYSTEM
3
3
  * Parses @-symbols in user input and resolves them to context.
4
4
  * Supports: @file, @folder, @def, @code, @problems, @web, @docs, @search
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ COMMAND PARSER ❄️
2
+ * COMMAND PARSER
3
3
  * Parse and execute CLI commands
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ COMPOSER MODE ❄️
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: ` 📋 Composer: ${this.pendingChanges.length} file(s) `,
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ CONFIG LOADER ❄️
2
+ * CONFIG LOADER
3
3
  * Load and save Winter CLI configuration
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ❄️ DIFF VIEW — APPLY/REJECT UI ❄️
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 body = [];
128
- const maxLines = Math.min(30, diff.additionsList.length + diff.removalsList.length);
129
- let added = 0;
130
- let removed = 0;
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
- body.push(` ${colors.dim}${path.basename(title)} ${diff.additions} additions, ${diff.removals} deletions${colors.reset}`);
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
- if (added + removed >= maxLines) {
136
- body.push(` ${colors.dim}... and ${diff.changes - maxLines} more changes${colors.reset}`);
137
- break;
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
- const lines = part.value.split('\n').filter(Boolean);
141
- const isAdded = part.added;
142
- const isRemoved = part.removed;
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
- for (const line of lines) {
145
- if (added + removed >= maxLines) break;
146
- if (isAdded) {
147
- added++;
148
- body.push(` ${colors.green}+ ${line}${colors.reset}`);
149
- } else if (isRemoved) {
150
- removed++;
151
- body.push(` ${colors.red}- ${line}${colors.reset}`);
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(`\n${renderBox({
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} — Accept all (apply full diff)\n` +
241
- ` ${colors.red}[r]${colors.reset} — Reject all\n` +
242
- ` ${colors.yellow}[m]${colors.reset} — Manual edit (opens file in $EDITOR)\n` +
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
- * ❄️ ECC Integration ❄️
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 { drawInFixedArea, enableFixedPanel, moveToPromptRow, moveToScrollRegion, padVisible, renderBox, terminalWidth } from './terminal-ui.js';
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
- if (typeof repl.rl?.setPrompt === 'function') {
26
- repl.rl.setPrompt(panel.prompt);
27
- }
28
- repl.rl?.prompt?.();
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;
@@ -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}🏆 Winner: ${result.winner} (${winner.model})${colors.reset}`);
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}🌐 Đang mở: ${url}${colors.reset}`);
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 = [];