winter-super-cli 2026.5.21 → 2026.5.22

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.21",
3
+ "version": "2026.5.22",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
@@ -209,13 +209,7 @@ export class ToolExecutor {
209
209
  case 'Write':
210
210
  return await this.writeFile(this.resolveInputPath(input.file_path ?? input.path ?? input.file, cwd), input.content);
211
211
  case 'Edit':
212
- const oldStr = input.old_string ?? input.oldString ?? input.old_text ?? input.oldText ?? input.search ?? input.target;
213
- const newStr = input.new_string ?? input.newString ?? input.new_text ?? input.newText ?? input.replace ?? input.content;
214
- return await this.editFile(
215
- this.resolveInputPath(input.file_path ?? input.path ?? input.file, cwd),
216
- oldStr,
217
- newStr
218
- );
212
+ return await this.executeEdit(input, cwd);
219
213
  case 'Bash':
220
214
  return await this.bash(input.command ?? input.cmd, input.cwd || cwd, input.timeout, input.shell);
221
215
  case 'Glob':
@@ -410,12 +404,85 @@ export class ToolExecutor {
410
404
  }
411
405
  }
412
406
 
407
+ async executeEdit(input, cwd) {
408
+ const request = this.unwrapToolInput(input);
409
+ const batch = request.edits ?? request.replacements ?? request.changes;
410
+
411
+ if (Array.isArray(batch)) {
412
+ const results = [];
413
+ for (const item of batch) {
414
+ const edit = this.normalizeEditArgs({ ...request, ...this.unwrapToolInput(item) }, cwd);
415
+ const result = await this.editFile(edit.filePath, edit.oldString, edit.newString);
416
+ results.push(result);
417
+ if (result.success === false) {
418
+ return { ...result, batchResults: results };
419
+ }
420
+ }
421
+
422
+ return {
423
+ success: true,
424
+ path: results[results.length - 1]?.path,
425
+ replacements: results.reduce((sum, result) => sum + (result.replacements || 0), 0),
426
+ batchResults: results,
427
+ diff: results.map(result => result.diff).filter(Boolean).join('\n'),
428
+ };
429
+ }
430
+
431
+ const edit = this.normalizeEditArgs(request, cwd);
432
+ return await this.editFile(edit.filePath, edit.oldString, edit.newString);
433
+ }
434
+
435
+ unwrapToolInput(input) {
436
+ let current = input && typeof input === 'object' ? input : {};
437
+ for (const key of ['input', 'args', 'arguments', 'parameters']) {
438
+ if (
439
+ current[key]
440
+ && typeof current[key] === 'object'
441
+ && !Array.isArray(current[key])
442
+ && Object.keys(current).length === 1
443
+ ) {
444
+ current = current[key];
445
+ }
446
+ }
447
+ return current;
448
+ }
449
+
450
+ normalizeEditArgs(input, cwd) {
451
+ const pick = (keys) => {
452
+ for (const key of keys) {
453
+ if (typeof input[key] === 'string') return input[key];
454
+ }
455
+ return undefined;
456
+ };
457
+
458
+ const filePath = this.resolveInputPath(pick([
459
+ 'file_path', 'filepath', 'filePath', 'path', 'file', 'filename', 'target_file', 'targetFile',
460
+ ]), cwd);
461
+ const oldString = pick([
462
+ 'old_string', 'oldString', 'old_text', 'oldText', 'old_str', 'oldStr',
463
+ 'search', 'search_string', 'searchString', 'find', 'find_text', 'findText',
464
+ 'target', 'target_string', 'targetString', 'text_to_replace', 'textToReplace',
465
+ 'pattern', 'original', 'before',
466
+ ]);
467
+ const newString = pick([
468
+ 'new_string', 'newString', 'new_text', 'newText', 'new_str', 'newStr',
469
+ 'replace', 'replacement', 'replace_with', 'replaceWith',
470
+ 'new_content', 'newContent', 'content', 'value', 'after',
471
+ ]);
472
+
473
+ return { filePath, oldString, newString };
474
+ }
475
+
413
476
  async editFile(filePath, oldString, newString) {
414
477
  if (!filePath) {
415
478
  return { success: false, error: 'file_path is required' };
416
479
  }
417
480
  if (typeof oldString !== 'string' || typeof newString !== 'string') {
418
- return { success: false, error: 'old_string and new_string are required', path: filePath };
481
+ return {
482
+ success: false,
483
+ error: 'old_string and new_string are required. Accepted aliases: oldString/old_str/search/find/text_to_replace and newString/new_str/replace/replacement/replace_with. For full-file replacement use Write instead of Edit.',
484
+ path: filePath,
485
+ };
419
486
  }
420
487
 
421
488
  try {
@@ -38,6 +38,55 @@ test('tool names accept common model aliases', () => {
38
38
  assert.equal(tools.normalizeToolName('web-search'), 'WebSearch');
39
39
  });
40
40
 
41
+ test('Edit accepts common model argument aliases', async () => {
42
+ const root = await mkdtemp(path.join(tmpdir(), 'winter-edit-alias-'));
43
+ await writeFile(path.join(root, 'file.txt'), 'hello world\n');
44
+
45
+ const tools = new ToolExecutor({ projectPath: root });
46
+ const result = await tools.execute('Edit', {
47
+ path: 'file.txt',
48
+ find: 'hello',
49
+ replacement: 'hi',
50
+ });
51
+
52
+ assert.equal(result.success, true);
53
+ const read = await tools.execute('Read', { path: 'file.txt' });
54
+ assert.equal(read.content, 'hi world\n');
55
+ });
56
+
57
+ test('Edit accepts nested and batch edit arguments', async () => {
58
+ const root = await mkdtemp(path.join(tmpdir(), 'winter-edit-batch-'));
59
+ await writeFile(path.join(root, 'file.txt'), 'alpha beta gamma\n');
60
+
61
+ const tools = new ToolExecutor({ projectPath: root });
62
+ const result = await tools.execute('replace_in_file', {
63
+ arguments: {
64
+ file_path: 'file.txt',
65
+ edits: [
66
+ { old_str: 'alpha', new_str: 'one' },
67
+ { text_to_replace: 'gamma', replace_with: 'three' },
68
+ ],
69
+ },
70
+ });
71
+
72
+ assert.equal(result.success, true);
73
+ assert.equal(result.replacements, 2);
74
+ const read = await tools.execute('Read', { path: 'file.txt' });
75
+ assert.equal(read.content, 'one beta three\n');
76
+ });
77
+
78
+ test('Edit missing strings returns recovery guidance', async () => {
79
+ const root = await mkdtemp(path.join(tmpdir(), 'winter-edit-recovery-'));
80
+ await writeFile(path.join(root, 'file.txt'), 'hello\n');
81
+
82
+ const tools = new ToolExecutor({ projectPath: root });
83
+ const result = await tools.execute('Edit', { path: 'file.txt' });
84
+
85
+ assert.equal(result.success, false);
86
+ assert.match(result.error, /Accepted aliases/);
87
+ assert.match(result.error, /Write instead of Edit/);
88
+ });
89
+
41
90
  test('Bash supports model-style heredoc file writes', async () => {
42
91
  const root = await mkdtemp(path.join(tmpdir(), 'winter-heredoc-'));
43
92
  const tools = new ToolExecutor({ projectPath: root });