winter-super-cli 2026.5.18 → 2026.5.20
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/cli/repl.js +13 -1
- package/src/tools/executor.js +76 -19
- package/src/tools/executor.test.js +50 -0
package/package.json
CHANGED
package/src/cli/repl.js
CHANGED
|
@@ -2147,7 +2147,9 @@ ${colors.reset}
|
|
|
2147
2147
|
|
|
2148
2148
|
## Tools Available
|
|
2149
2149
|
- Read, Write, Edit - File operations
|
|
2150
|
-
-
|
|
2150
|
+
- Write - Create/overwrite files directly. Use this instead of Bash echo/cat/heredoc for writing code.
|
|
2151
|
+
- Edit - Replace exact text in existing files.
|
|
2152
|
+
- Bash - Execute shell commands. Current OS is ${process.platform === 'win32' ? 'Windows; Bash auto-detects PowerShell and cmd.exe syntax. Use shell="powershell" or shell="cmd" when needed.' : process.platform}.
|
|
2151
2153
|
- Glob - Find files
|
|
2152
2154
|
- Grep - Search content
|
|
2153
2155
|
- TaskCreate, TaskUpdate, TaskList - Task management
|
|
@@ -2156,6 +2158,9 @@ ${colors.reset}
|
|
|
2156
2158
|
|
|
2157
2159
|
## Guidelines
|
|
2158
2160
|
- Call tools when they help - be proactive
|
|
2161
|
+
- You DO have file write tools. Never say "there is no write tool"; use Write or Edit.
|
|
2162
|
+
- If a tool name fails, call the canonical tool name next: Write, Edit, Read, Bash, Glob, or Grep.
|
|
2163
|
+
- On Windows, Bash accepts both PowerShell and cmd.exe commands. Prefer Write with full content for file writes.
|
|
2159
2164
|
- After using tools, always provide a direct final answer to the user.
|
|
2160
2165
|
- Answer normal questions directly without unnecessary legal or policy disclaimers.
|
|
2161
2166
|
- If a request is illegal, unsafe, or harmful, refuse briefly and offer a safe alternative.
|
|
@@ -2235,9 +2240,16 @@ Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì n
|
|
|
2235
2240
|
2. [DESIGN EXCELLENCE]: Use rich aesthetics. Default to modern UI frameworks if applicable. Never output plain, ugly HTML/CSS. Ensure responsive, premium feel with micro-animations.
|
|
2236
2241
|
3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
|
|
2237
2242
|
4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
|
|
2243
|
+
5. [TOOL EXECUTION FIRST]: You DO have file tools. Use Write to create/overwrite files and Edit to patch files. Never say there is no write tool.
|
|
2238
2244
|
|
|
2239
2245
|
${rolePrompt}
|
|
2240
2246
|
|
|
2247
|
+
## Tool Rules
|
|
2248
|
+
- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
|
|
2249
|
+
- Current OS is ${process.platform === 'win32' ? 'Windows; Bash auto-detects PowerShell and cmd.exe syntax. Use shell="powershell" or shell="cmd" when needed.' : process.platform}.
|
|
2250
|
+
- Prefer Write/Edit for writing files. Bash accepts both PowerShell and cmd.exe on Windows, but do not use long echo chains for code files.
|
|
2251
|
+
- If a tool call fails because of an unknown alias, call the canonical tool name next.
|
|
2252
|
+
|
|
2241
2253
|
## Project
|
|
2242
2254
|
Working directory: ${this.projectPath}
|
|
2243
2255
|
Current session: ${this.session.getSessionId().substring(0, 8)}
|
package/src/tools/executor.js
CHANGED
|
@@ -37,7 +37,7 @@ export class ToolExecutor {
|
|
|
37
37
|
{
|
|
38
38
|
type: 'function',
|
|
39
39
|
name: 'Write',
|
|
40
|
-
description: 'Create or overwrite file with content.',
|
|
40
|
+
description: 'Create or overwrite a file with content. Prefer this for file creation instead of Bash echo/cat/heredoc. Also handles model aliases like write_to_file.',
|
|
41
41
|
parameters: {
|
|
42
42
|
type: 'object',
|
|
43
43
|
properties: {
|
|
@@ -50,7 +50,7 @@ export class ToolExecutor {
|
|
|
50
50
|
{
|
|
51
51
|
type: 'function',
|
|
52
52
|
name: 'Edit',
|
|
53
|
-
description: 'Make surgical changes. Replace exact old_string with new_string.',
|
|
53
|
+
description: 'Make surgical changes. Replace exact old_string with new_string. Also handles model aliases like replace_in_file and str_replace_editor.',
|
|
54
54
|
parameters: {
|
|
55
55
|
type: 'object',
|
|
56
56
|
properties: {
|
|
@@ -64,13 +64,14 @@ export class ToolExecutor {
|
|
|
64
64
|
{
|
|
65
65
|
type: 'function',
|
|
66
66
|
name: 'Bash',
|
|
67
|
-
description: 'Execute shell command.
|
|
67
|
+
description: 'Execute a shell command. On Windows, shell auto-detects PowerShell or cmd.exe syntax. Prefer Write/Edit for file writes.',
|
|
68
68
|
parameters: {
|
|
69
69
|
type: 'object',
|
|
70
70
|
properties: {
|
|
71
71
|
command: { type: 'string', description: 'Shell command to execute' },
|
|
72
72
|
cwd: { type: 'string', description: 'Working directory' },
|
|
73
|
-
timeout: { type: 'number', description: 'Timeout in ms (default: 60000)' }
|
|
73
|
+
timeout: { type: 'number', description: 'Timeout in ms (default: 60000)' },
|
|
74
|
+
shell: { type: 'string', description: 'Windows shell: auto, powershell, or cmd' }
|
|
74
75
|
},
|
|
75
76
|
required: ['command']
|
|
76
77
|
}
|
|
@@ -216,7 +217,7 @@ export class ToolExecutor {
|
|
|
216
217
|
newStr
|
|
217
218
|
);
|
|
218
219
|
case 'Bash':
|
|
219
|
-
return await this.bash(input.command ?? input.cmd, input.cwd || cwd, input.timeout);
|
|
220
|
+
return await this.bash(input.command ?? input.cmd, input.cwd || cwd, input.timeout, input.shell);
|
|
220
221
|
case 'Glob':
|
|
221
222
|
return await this.glob(input.pattern ?? input.glob ?? '**/*', input.cwd || input.path || cwd);
|
|
222
223
|
case 'Grep':
|
|
@@ -236,7 +237,12 @@ export class ToolExecutor {
|
|
|
236
237
|
case 'WebSearch':
|
|
237
238
|
return await this.webSearch(input.query ?? input.q);
|
|
238
239
|
default:
|
|
239
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: `Unknown tool: ${toolName}`,
|
|
243
|
+
availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'BrowserDebug', 'WebFetch', 'WebSearch'],
|
|
244
|
+
recovery: 'Call one of the available tools. For file writes use Write with { "file_path": "...", "content": "..." }. For shell commands use Bash with { "command": "..." }.',
|
|
245
|
+
};
|
|
240
246
|
}
|
|
241
247
|
}
|
|
242
248
|
|
|
@@ -439,7 +445,7 @@ export class ToolExecutor {
|
|
|
439
445
|
}
|
|
440
446
|
}
|
|
441
447
|
|
|
442
|
-
async bash(command, cwd, timeout = 60000) {
|
|
448
|
+
async bash(command, cwd, timeout = 60000, shell = 'auto') {
|
|
443
449
|
if (typeof command !== 'string' || command.trim() === '') {
|
|
444
450
|
return { success: false, error: 'command is required', exitCode: 1 };
|
|
445
451
|
}
|
|
@@ -450,7 +456,10 @@ export class ToolExecutor {
|
|
|
450
456
|
const heredocResult = await this.tryHandleHeredocWrite(command, cwd);
|
|
451
457
|
if (heredocResult) return heredocResult;
|
|
452
458
|
|
|
453
|
-
|
|
459
|
+
let requestedShell = this.normalizeWindowsShell(shell);
|
|
460
|
+
const translated = await this.translateWindowsCommand(command);
|
|
461
|
+
command = translated.command;
|
|
462
|
+
if (requestedShell === 'auto' && translated.shell) requestedShell = translated.shell;
|
|
454
463
|
|
|
455
464
|
// Security check
|
|
456
465
|
const lowerCommand = command.toLowerCase();
|
|
@@ -462,12 +471,7 @@ export class ToolExecutor {
|
|
|
462
471
|
|
|
463
472
|
try {
|
|
464
473
|
const { stdout, stderr } = process.platform === 'win32'
|
|
465
|
-
? await
|
|
466
|
-
cwd,
|
|
467
|
-
timeout,
|
|
468
|
-
windowsHide: true,
|
|
469
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
470
|
-
})
|
|
474
|
+
? await this.execWindowsCommand(command, cwd, timeout, requestedShell)
|
|
471
475
|
: await execAsync(command, { cwd, timeout, shell: true, maxBuffer: 10 * 1024 * 1024 });
|
|
472
476
|
return {
|
|
473
477
|
success: true,
|
|
@@ -486,6 +490,50 @@ export class ToolExecutor {
|
|
|
486
490
|
}
|
|
487
491
|
}
|
|
488
492
|
|
|
493
|
+
normalizeWindowsShell(shell) {
|
|
494
|
+
const value = String(shell || 'auto').toLowerCase();
|
|
495
|
+
if (['cmd', 'cmd.exe', 'commandprompt', 'command-prompt'].includes(value)) return 'cmd';
|
|
496
|
+
if (['powershell', 'pwsh', 'ps', 'powershell.exe'].includes(value)) return 'powershell';
|
|
497
|
+
return 'auto';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async execWindowsCommand(command, cwd, timeout, requestedShell = 'auto') {
|
|
501
|
+
const shell = requestedShell === 'auto' ? this.detectWindowsShell(command) : requestedShell;
|
|
502
|
+
if (shell === 'cmd') {
|
|
503
|
+
return await execFileAsync('cmd.exe', ['/d', '/s', '/c', command], {
|
|
504
|
+
cwd,
|
|
505
|
+
timeout,
|
|
506
|
+
windowsHide: true,
|
|
507
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return await execFileAsync('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], {
|
|
512
|
+
cwd,
|
|
513
|
+
timeout,
|
|
514
|
+
windowsHide: true,
|
|
515
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
detectWindowsShell(command) {
|
|
520
|
+
const trimmed = String(command || '').trim();
|
|
521
|
+
if (this.looksLikePowerShell(trimmed)) return 'powershell';
|
|
522
|
+
if (this.looksLikeCmd(trimmed)) return 'cmd';
|
|
523
|
+
return 'powershell';
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
looksLikePowerShell(command) {
|
|
527
|
+
return /(^|[;&|]\s*)(Get-|Set-|New-|Remove-|Copy-|Move-|Select-|Where-|ForEach-|Test-Path|Out-File|Write-Host|powershell(?:\.exe)?\b|pwsh\b)/i.test(command);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
looksLikeCmd(command) {
|
|
531
|
+
return /\s(&&|\|\|)\s/.test(command)
|
|
532
|
+
|| /(^|[&]\s*)(dir|copy|xcopy|del|erase|move|ren|type|echo|set|if|for|mkdir|rmdir)\b/i.test(command)
|
|
533
|
+
|| /^\s*@?echo\s+/i.test(command)
|
|
534
|
+
|| /(^|\s)(\/b|\/s|\/q|\/y)\b/i.test(command);
|
|
535
|
+
}
|
|
536
|
+
|
|
489
537
|
async glob(pattern, cwd) {
|
|
490
538
|
const normalizedRequest = await this.normalizeGlobRequest(pattern, cwd);
|
|
491
539
|
if (normalizedRequest.file) {
|
|
@@ -566,7 +614,7 @@ export class ToolExecutor {
|
|
|
566
614
|
}
|
|
567
615
|
|
|
568
616
|
async translateWindowsCommand(command) {
|
|
569
|
-
if (process.platform !== 'win32') return command;
|
|
617
|
+
if (process.platform !== 'win32') return { command };
|
|
570
618
|
|
|
571
619
|
const trimmed = command.trim();
|
|
572
620
|
const ls = trimmed.match(/^ls(?:\s+(.+))?$/i);
|
|
@@ -579,7 +627,10 @@ export class ToolExecutor {
|
|
|
579
627
|
const fields = long
|
|
580
628
|
? 'Mode,Length,LastWriteTime,Name'
|
|
581
629
|
: 'Name';
|
|
582
|
-
return
|
|
630
|
+
return {
|
|
631
|
+
command: `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)}${recurse ? ' -Recurse' : ''}${force ? ' -Force' : ''} | Select-Object ${fields}`,
|
|
632
|
+
shell: 'powershell',
|
|
633
|
+
};
|
|
583
634
|
}
|
|
584
635
|
|
|
585
636
|
const cat = trimmed.match(/^cat\s+(.+)$/i);
|
|
@@ -589,13 +640,19 @@ export class ToolExecutor {
|
|
|
589
640
|
try {
|
|
590
641
|
const stat = await fs.stat(normalizedTarget);
|
|
591
642
|
if (stat.isDirectory()) {
|
|
592
|
-
return
|
|
643
|
+
return {
|
|
644
|
+
command: `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Force | Select-Object -ExpandProperty Name`,
|
|
645
|
+
shell: 'powershell',
|
|
646
|
+
};
|
|
593
647
|
}
|
|
594
648
|
} catch {}
|
|
595
|
-
return
|
|
649
|
+
return {
|
|
650
|
+
command: `Get-Content -LiteralPath ${this.quotePowerShellString(target)}`,
|
|
651
|
+
shell: 'powershell',
|
|
652
|
+
};
|
|
596
653
|
}
|
|
597
654
|
|
|
598
|
-
return command;
|
|
655
|
+
return { command };
|
|
599
656
|
}
|
|
600
657
|
|
|
601
658
|
async tryHandleHeredocWrite(command, cwd) {
|
|
@@ -14,6 +14,16 @@ test('Bash validates missing command instead of throwing', async () => {
|
|
|
14
14
|
assert.equal(result.error, 'command is required');
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
test('unknown tools return recovery guidance instead of a bare failure', async () => {
|
|
18
|
+
const tools = new ToolExecutor({ projectPath: process.cwd() });
|
|
19
|
+
const result = await tools.execute('bad_tool_name', {});
|
|
20
|
+
|
|
21
|
+
assert.equal(result.success, false);
|
|
22
|
+
assert.equal(result.error, 'Unknown tool: bad_tool_name');
|
|
23
|
+
assert(result.availableTools.includes('Write'));
|
|
24
|
+
assert.match(result.recovery, /Write/);
|
|
25
|
+
});
|
|
26
|
+
|
|
17
27
|
test('tool names accept common model aliases', () => {
|
|
18
28
|
const tools = new ToolExecutor({ projectPath: process.cwd() });
|
|
19
29
|
|
|
@@ -62,6 +72,46 @@ test('Windows Bash runs PowerShell commands and translates ls flags', async (t)
|
|
|
62
72
|
assert.match(ps.stdout, /a\.txt/);
|
|
63
73
|
});
|
|
64
74
|
|
|
75
|
+
test('Windows Bash accepts explicit cmd and PowerShell shells', async (t) => {
|
|
76
|
+
if (process.platform !== 'win32') {
|
|
77
|
+
t.skip('Windows-only shell behavior');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const root = await mkdtemp(path.join(tmpdir(), 'winter-shells-'));
|
|
82
|
+
const tools = new ToolExecutor({ projectPath: root });
|
|
83
|
+
|
|
84
|
+
const cmd = await tools.execute('Bash', {
|
|
85
|
+
shell: 'cmd',
|
|
86
|
+
command: 'echo hello>cmd.txt && type cmd.txt',
|
|
87
|
+
});
|
|
88
|
+
assert.equal(cmd.success, true);
|
|
89
|
+
assert.match(cmd.stdout, /hello/);
|
|
90
|
+
|
|
91
|
+
const ps = await tools.execute('Bash', {
|
|
92
|
+
shell: 'powershell',
|
|
93
|
+
command: "Set-Content -LiteralPath ps.txt -Value 'world'; Get-Content -LiteralPath ps.txt",
|
|
94
|
+
});
|
|
95
|
+
assert.equal(ps.success, true);
|
|
96
|
+
assert.match(ps.stdout, /world/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('Windows Bash auto-detects cmd chaining syntax', async (t) => {
|
|
100
|
+
if (process.platform !== 'win32') {
|
|
101
|
+
t.skip('Windows-only shell behavior');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const root = await mkdtemp(path.join(tmpdir(), 'winter-cmd-auto-'));
|
|
106
|
+
const tools = new ToolExecutor({ projectPath: root });
|
|
107
|
+
const result = await tools.execute('Bash', {
|
|
108
|
+
command: 'echo auto>auto.txt && type auto.txt',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.equal(result.success, true);
|
|
112
|
+
assert.match(result.stdout, /auto/);
|
|
113
|
+
});
|
|
114
|
+
|
|
65
115
|
test('Read lists directories instead of failing on directory paths', async () => {
|
|
66
116
|
const root = await mkdtemp(path.join(tmpdir(), 'winter-tools-'));
|
|
67
117
|
await mkdir(path.join(root, 'sub'));
|