winter-super-cli 2026.5.17 → 2026.5.18

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.5.17",
3
+ "version": "2026.5.18",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
@@ -5,11 +5,12 @@
5
5
 
6
6
  import { promises as fs } from 'fs';
7
7
  import path from 'path';
8
- import { exec } from 'child_process';
8
+ import { exec, execFile } from 'child_process';
9
9
  import { promisify } from 'util';
10
10
  import { diffLines } from 'diff';
11
11
 
12
12
  const execAsync = promisify(exec);
13
+ const execFileAsync = promisify(execFile);
13
14
 
14
15
  export class ToolExecutor {
15
16
  constructor(repl) {
@@ -262,6 +263,7 @@ export class ToolExecutor {
262
263
  bash: 'Bash',
263
264
  shell: 'Bash',
264
265
  command: 'Bash',
266
+ commandexecutor: 'Bash',
265
267
  executecommand: 'Bash',
266
268
  runcommand: 'Bash',
267
269
  terminal: 'Bash',
@@ -445,6 +447,9 @@ export class ToolExecutor {
445
447
  timeout = parseInt(timeout, 10);
446
448
  if (isNaN(timeout) || timeout < 0) timeout = 60000;
447
449
 
450
+ const heredocResult = await this.tryHandleHeredocWrite(command, cwd);
451
+ if (heredocResult) return heredocResult;
452
+
448
453
  command = await this.translateWindowsCommand(command);
449
454
 
450
455
  // Security check
@@ -456,7 +461,14 @@ export class ToolExecutor {
456
461
  }
457
462
 
458
463
  try {
459
- const { stdout, stderr } = await execAsync(command, { cwd, timeout, shell: true });
464
+ const { stdout, stderr } = process.platform === 'win32'
465
+ ? await execFileAsync('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], {
466
+ cwd,
467
+ timeout,
468
+ windowsHide: true,
469
+ maxBuffer: 10 * 1024 * 1024,
470
+ })
471
+ : await execAsync(command, { cwd, timeout, shell: true, maxBuffer: 10 * 1024 * 1024 });
460
472
  return {
461
473
  success: true,
462
474
  stdout: stdout || '',
@@ -557,16 +569,17 @@ export class ToolExecutor {
557
569
  if (process.platform !== 'win32') return command;
558
570
 
559
571
  const trimmed = command.trim();
560
- const lsRecursive = trimmed.match(/^ls\s+-R\s+(.+)$/i);
561
- if (lsRecursive) {
562
- const target = lsRecursive[1].trim();
563
- return `powershell -NoProfile -Command "Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Recurse -Force | Select-Object -ExpandProperty FullName"`;
564
- }
565
-
566
- const ls = trimmed.match(/^ls\s+(.+)$/i);
572
+ const ls = trimmed.match(/^ls(?:\s+(.+))?$/i);
567
573
  if (ls) {
568
- const target = ls[1].trim();
569
- return `powershell -NoProfile -Command "Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Force | Select-Object -ExpandProperty Name"`;
574
+ const parsed = this.parseLsArgs(ls[1] || '');
575
+ const target = parsed.target || '.';
576
+ const recurse = parsed.flags.has('r') || parsed.flags.has('recursive');
577
+ const long = parsed.flags.has('l') || parsed.flags.has('la') || parsed.flags.has('al');
578
+ const force = parsed.flags.has('a') || parsed.flags.has('force') || parsed.flags.has('la') || parsed.flags.has('al');
579
+ const fields = long
580
+ ? 'Mode,Length,LastWriteTime,Name'
581
+ : 'Name';
582
+ return `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)}${recurse ? ' -Recurse' : ''}${force ? ' -Force' : ''} | Select-Object ${fields}`;
570
583
  }
571
584
 
572
585
  const cat = trimmed.match(/^cat\s+(.+)$/i);
@@ -576,15 +589,82 @@ export class ToolExecutor {
576
589
  try {
577
590
  const stat = await fs.stat(normalizedTarget);
578
591
  if (stat.isDirectory()) {
579
- return `powershell -NoProfile -Command "Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Force | Select-Object -ExpandProperty Name"`;
592
+ return `Get-ChildItem -LiteralPath ${this.quotePowerShellString(target)} -Force | Select-Object -ExpandProperty Name`;
580
593
  }
581
594
  } catch {}
582
- return `powershell -NoProfile -Command "Get-Content -LiteralPath ${this.quotePowerShellString(target)}"`;
595
+ return `Get-Content -LiteralPath ${this.quotePowerShellString(target)}`;
583
596
  }
584
597
 
585
598
  return command;
586
599
  }
587
600
 
601
+ async tryHandleHeredocWrite(command, cwd) {
602
+ const match = String(command || '').match(/^cat\s*>\s*(.+?)\s*<<\s*['"]?([A-Za-z0-9_-]+)['"]?\r?\n([\s\S]*?)\r?\n\2\s*$/);
603
+ if (!match) return null;
604
+
605
+ const [, rawTarget, , content] = match;
606
+ const target = this.resolveInputPath(rawTarget.trim(), cwd);
607
+ return await this.writeFile(target, content.endsWith('\n') ? content : `${content}\n`);
608
+ }
609
+
610
+ parseLsArgs(rawArgs) {
611
+ const tokens = this.splitShellLike(rawArgs);
612
+ const flags = new Set();
613
+ const paths = [];
614
+
615
+ for (const token of tokens) {
616
+ if (token.startsWith('-') && token.length > 1) {
617
+ const flag = token.replace(/^-+/, '').toLowerCase();
618
+ flags.add(flag);
619
+ if (!flag.includes('=')) {
620
+ for (const ch of flag) flags.add(ch);
621
+ }
622
+ } else {
623
+ paths.push(token);
624
+ }
625
+ }
626
+
627
+ return { flags, target: paths.join(' ') };
628
+ }
629
+
630
+ splitShellLike(text) {
631
+ const tokens = [];
632
+ let current = '';
633
+ let quote = null;
634
+ let escaped = false;
635
+
636
+ for (const ch of String(text || '')) {
637
+ if (escaped) {
638
+ current += ch;
639
+ escaped = false;
640
+ continue;
641
+ }
642
+ if (ch === '\\') {
643
+ escaped = true;
644
+ continue;
645
+ }
646
+ if ((ch === '"' || ch === "'") && !quote) {
647
+ quote = ch;
648
+ continue;
649
+ }
650
+ if (ch === quote) {
651
+ quote = null;
652
+ continue;
653
+ }
654
+ if (/\s/.test(ch) && !quote) {
655
+ if (current) {
656
+ tokens.push(current);
657
+ current = '';
658
+ }
659
+ continue;
660
+ }
661
+ current += ch;
662
+ }
663
+
664
+ if (current) tokens.push(current);
665
+ return tokens;
666
+ }
667
+
588
668
  quotePowerShellString(value) {
589
669
  const unquoted = value.replace(/^['"]|['"]$/g, '');
590
670
  return `'${unquoted.replace(/'/g, "''")}'`;
@@ -20,6 +20,7 @@ test('tool names accept common model aliases', () => {
20
20
  assert.equal(tools.normalizeToolName('read_file'), 'Read');
21
21
  assert.equal(tools.normalizeToolName('write_to_file'), 'Write');
22
22
  assert.equal(tools.normalizeToolName('replace_in_file'), 'Edit');
23
+ assert.equal(tools.normalizeToolName('command_executor'), 'Bash');
23
24
  assert.equal(tools.normalizeToolName('execute_command'), 'Bash');
24
25
  assert.equal(tools.normalizeToolName('list_files'), 'Glob');
25
26
  assert.equal(tools.normalizeToolName('search_files'), 'Grep');
@@ -27,6 +28,40 @@ test('tool names accept common model aliases', () => {
27
28
  assert.equal(tools.normalizeToolName('web-search'), 'WebSearch');
28
29
  });
29
30
 
31
+ test('Bash supports model-style heredoc file writes', async () => {
32
+ const root = await mkdtemp(path.join(tmpdir(), 'winter-heredoc-'));
33
+ const tools = new ToolExecutor({ projectPath: root });
34
+ const result = await tools.execute('command_executor', {
35
+ command: "cat > src/components/InstallSection.tsx << 'EOF'\nconst ok = true;\nEOF",
36
+ });
37
+
38
+ assert.equal(result.success, true);
39
+ assert.equal(result.path, path.join(root, 'src', 'components', 'InstallSection.tsx'));
40
+
41
+ const read = await tools.execute('Read', { file_path: 'src/components/InstallSection.tsx' });
42
+ assert.equal(read.content, 'const ok = true;\n');
43
+ });
44
+
45
+ test('Windows Bash runs PowerShell commands and translates ls flags', async (t) => {
46
+ if (process.platform !== 'win32') {
47
+ t.skip('Windows-only shell behavior');
48
+ return;
49
+ }
50
+
51
+ const root = await mkdtemp(path.join(tmpdir(), 'winter-powershell-'));
52
+ await mkdir(path.join(root, 'src', 'components'), { recursive: true });
53
+ await writeFile(path.join(root, 'src', 'components', 'a.txt'), 'hello');
54
+
55
+ const tools = new ToolExecutor({ projectPath: root });
56
+ const ls = await tools.execute('Bash', { command: 'ls -la src/components/' });
57
+ assert.equal(ls.success, true);
58
+ assert.match(ls.stdout, /a\.txt/);
59
+
60
+ const ps = await tools.execute('Bash', { command: 'Get-ChildItem -Path src/components/ -Name' });
61
+ assert.equal(ps.success, true);
62
+ assert.match(ps.stdout, /a\.txt/);
63
+ });
64
+
30
65
  test('Read lists directories instead of failing on directory paths', async () => {
31
66
  const root = await mkdtemp(path.join(tmpdir(), 'winter-tools-'));
32
67
  await mkdir(path.join(root, 'sub'));