winter-super-cli 2026.5.19 → 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 +4 -4
- package/src/tools/executor.js +68 -16
- package/src/tools/executor.test.js +40 -0
package/package.json
CHANGED
package/src/cli/repl.js
CHANGED
|
@@ -2149,7 +2149,7 @@ ${colors.reset}
|
|
|
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
2151
|
- Edit - Replace exact text in existing files.
|
|
2152
|
-
- Bash - Execute shell commands. Current OS is ${process.platform === 'win32' ? 'Windows; Bash
|
|
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}.
|
|
2153
2153
|
- Glob - Find files
|
|
2154
2154
|
- Grep - Search content
|
|
2155
2155
|
- TaskCreate, TaskUpdate, TaskList - Task management
|
|
@@ -2160,7 +2160,7 @@ ${colors.reset}
|
|
|
2160
2160
|
- Call tools when they help - be proactive
|
|
2161
2161
|
- You DO have file write tools. Never say "there is no write tool"; use Write or Edit.
|
|
2162
2162
|
- If a tool name fails, call the canonical tool name next: Write, Edit, Read, Bash, Glob, or Grep.
|
|
2163
|
-
-
|
|
2163
|
+
- On Windows, Bash accepts both PowerShell and cmd.exe commands. Prefer Write with full content for file writes.
|
|
2164
2164
|
- After using tools, always provide a direct final answer to the user.
|
|
2165
2165
|
- Answer normal questions directly without unnecessary legal or policy disclaimers.
|
|
2166
2166
|
- If a request is illegal, unsafe, or harmful, refuse briefly and offer a safe alternative.
|
|
@@ -2246,8 +2246,8 @@ ${rolePrompt}
|
|
|
2246
2246
|
|
|
2247
2247
|
## Tool Rules
|
|
2248
2248
|
- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
|
|
2249
|
-
- Current OS is ${process.platform === 'win32' ? 'Windows; Bash
|
|
2250
|
-
- Prefer Write/Edit for writing files.
|
|
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
2251
|
- If a tool call fails because of an unknown alias, call the canonical tool name next.
|
|
2252
2252
|
|
|
2253
2253
|
## Project
|
package/src/tools/executor.js
CHANGED
|
@@ -64,13 +64,14 @@ export class ToolExecutor {
|
|
|
64
64
|
{
|
|
65
65
|
type: 'function',
|
|
66
66
|
name: 'Bash',
|
|
67
|
-
description: 'Execute a shell command. On Windows
|
|
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':
|
|
@@ -444,7 +445,7 @@ export class ToolExecutor {
|
|
|
444
445
|
}
|
|
445
446
|
}
|
|
446
447
|
|
|
447
|
-
async bash(command, cwd, timeout = 60000) {
|
|
448
|
+
async bash(command, cwd, timeout = 60000, shell = 'auto') {
|
|
448
449
|
if (typeof command !== 'string' || command.trim() === '') {
|
|
449
450
|
return { success: false, error: 'command is required', exitCode: 1 };
|
|
450
451
|
}
|
|
@@ -455,7 +456,10 @@ export class ToolExecutor {
|
|
|
455
456
|
const heredocResult = await this.tryHandleHeredocWrite(command, cwd);
|
|
456
457
|
if (heredocResult) return heredocResult;
|
|
457
458
|
|
|
458
|
-
|
|
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;
|
|
459
463
|
|
|
460
464
|
// Security check
|
|
461
465
|
const lowerCommand = command.toLowerCase();
|
|
@@ -467,12 +471,7 @@ export class ToolExecutor {
|
|
|
467
471
|
|
|
468
472
|
try {
|
|
469
473
|
const { stdout, stderr } = process.platform === 'win32'
|
|
470
|
-
? await
|
|
471
|
-
cwd,
|
|
472
|
-
timeout,
|
|
473
|
-
windowsHide: true,
|
|
474
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
475
|
-
})
|
|
474
|
+
? await this.execWindowsCommand(command, cwd, timeout, requestedShell)
|
|
476
475
|
: await execAsync(command, { cwd, timeout, shell: true, maxBuffer: 10 * 1024 * 1024 });
|
|
477
476
|
return {
|
|
478
477
|
success: true,
|
|
@@ -491,6 +490,50 @@ export class ToolExecutor {
|
|
|
491
490
|
}
|
|
492
491
|
}
|
|
493
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
|
+
|
|
494
537
|
async glob(pattern, cwd) {
|
|
495
538
|
const normalizedRequest = await this.normalizeGlobRequest(pattern, cwd);
|
|
496
539
|
if (normalizedRequest.file) {
|
|
@@ -571,7 +614,7 @@ export class ToolExecutor {
|
|
|
571
614
|
}
|
|
572
615
|
|
|
573
616
|
async translateWindowsCommand(command) {
|
|
574
|
-
if (process.platform !== 'win32') return command;
|
|
617
|
+
if (process.platform !== 'win32') return { command };
|
|
575
618
|
|
|
576
619
|
const trimmed = command.trim();
|
|
577
620
|
const ls = trimmed.match(/^ls(?:\s+(.+))?$/i);
|
|
@@ -584,7 +627,10 @@ export class ToolExecutor {
|
|
|
584
627
|
const fields = long
|
|
585
628
|
? 'Mode,Length,LastWriteTime,Name'
|
|
586
629
|
: 'Name';
|
|
587
|
-
return
|
|
630
|
+
return {
|
|
631
|
+
command: `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)}${recurse ? ' -Recurse' : ''}${force ? ' -Force' : ''} | Select-Object ${fields}`,
|
|
632
|
+
shell: 'powershell',
|
|
633
|
+
};
|
|
588
634
|
}
|
|
589
635
|
|
|
590
636
|
const cat = trimmed.match(/^cat\s+(.+)$/i);
|
|
@@ -594,13 +640,19 @@ export class ToolExecutor {
|
|
|
594
640
|
try {
|
|
595
641
|
const stat = await fs.stat(normalizedTarget);
|
|
596
642
|
if (stat.isDirectory()) {
|
|
597
|
-
return
|
|
643
|
+
return {
|
|
644
|
+
command: `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Force | Select-Object -ExpandProperty Name`,
|
|
645
|
+
shell: 'powershell',
|
|
646
|
+
};
|
|
598
647
|
}
|
|
599
648
|
} catch {}
|
|
600
|
-
return
|
|
649
|
+
return {
|
|
650
|
+
command: `Get-Content -LiteralPath ${this.quotePowerShellString(target)}`,
|
|
651
|
+
shell: 'powershell',
|
|
652
|
+
};
|
|
601
653
|
}
|
|
602
654
|
|
|
603
|
-
return command;
|
|
655
|
+
return { command };
|
|
604
656
|
}
|
|
605
657
|
|
|
606
658
|
async tryHandleHeredocWrite(command, cwd) {
|
|
@@ -72,6 +72,46 @@ test('Windows Bash runs PowerShell commands and translates ls flags', async (t)
|
|
|
72
72
|
assert.match(ps.stdout, /a\.txt/);
|
|
73
73
|
});
|
|
74
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
|
+
|
|
75
115
|
test('Read lists directories instead of failing on directory paths', async () => {
|
|
76
116
|
const root = await mkdtemp(path.join(tmpdir(), 'winter-tools-'));
|
|
77
117
|
await mkdir(path.join(root, 'sub'));
|