sam-coder-cli 2.0.5 → 2.0.8
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/bin/agi-cli.js +105 -48
- package/package.json +1 -1
package/bin/agi-cli.js
CHANGED
|
@@ -80,7 +80,7 @@ const tools = [
|
|
|
80
80
|
properties: {
|
|
81
81
|
type: {
|
|
82
82
|
type: 'string',
|
|
83
|
-
enum: ['replace', 'insert', 'delete'],
|
|
83
|
+
enum: ['replace', 'insert', 'delete', 'replace_block'],
|
|
84
84
|
description: 'Type of edit operation'
|
|
85
85
|
},
|
|
86
86
|
startLine: {
|
|
@@ -95,6 +95,14 @@ const tools = [
|
|
|
95
95
|
type: 'string',
|
|
96
96
|
description: 'New text to insert or replace with'
|
|
97
97
|
},
|
|
98
|
+
oldText: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Exact text to search for and replace (for replace_block)'
|
|
101
|
+
},
|
|
102
|
+
replacementContent: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'New text to replace oldText with (for replace_block)'
|
|
105
|
+
},
|
|
98
106
|
pattern: {
|
|
99
107
|
type: 'string',
|
|
100
108
|
description: 'Pattern to search for (for replace operations)'
|
|
@@ -137,21 +145,29 @@ const tools = [
|
|
|
137
145
|
},
|
|
138
146
|
required: ['pattern', 'replacement']
|
|
139
147
|
},
|
|
148
|
+
{
|
|
149
|
+
properties: {
|
|
150
|
+
type: { const: 'replace_block' },
|
|
151
|
+
oldText: { type: 'string' },
|
|
152
|
+
replacementContent: { type: 'string' }
|
|
153
|
+
},
|
|
154
|
+
required: ['oldText', 'replacementContent']
|
|
155
|
+
},
|
|
140
156
|
{
|
|
141
157
|
properties: {
|
|
142
158
|
type: { const: 'insert' },
|
|
143
159
|
position: { type: 'string', enum: ['start', 'end'] },
|
|
144
|
-
|
|
160
|
+
newText: { type: 'string' }
|
|
145
161
|
},
|
|
146
|
-
required: ['position', '
|
|
162
|
+
required: ['position', 'newText']
|
|
147
163
|
},
|
|
148
164
|
{
|
|
149
165
|
properties: {
|
|
150
166
|
type: { const: 'insert' },
|
|
151
167
|
line: { type: 'number' },
|
|
152
|
-
|
|
168
|
+
newText: { type: 'string' }
|
|
153
169
|
},
|
|
154
|
-
required: ['line', '
|
|
170
|
+
required: ['line', 'newText']
|
|
155
171
|
},
|
|
156
172
|
{
|
|
157
173
|
properties: {
|
|
@@ -463,56 +479,96 @@ const agentUtils = {
|
|
|
463
479
|
const edits = typeof inputPathOrObj === 'string' ? maybeEdits : inputPathOrObj?.edits;
|
|
464
480
|
if (!targetPath) throw new Error('editFile: missing path');
|
|
465
481
|
if (!edits) throw new Error('editFile: missing edits');
|
|
482
|
+
|
|
466
483
|
// Read the current file content
|
|
467
484
|
let content = await fs.readFile(targetPath, 'utf-8');
|
|
468
|
-
|
|
485
|
+
let lines = content.split('\n');
|
|
469
486
|
|
|
470
487
|
// Process each edit operation
|
|
471
|
-
for (
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (op.startLine
|
|
477
|
-
|
|
488
|
+
for (let i = 0; i < edits.operations.length; i++) {
|
|
489
|
+
const op = edits.operations[i];
|
|
490
|
+
try {
|
|
491
|
+
switch (op.type) {
|
|
492
|
+
case 'replace':
|
|
493
|
+
if (op.startLine !== undefined && op.endLine !== undefined) {
|
|
494
|
+
// Line-based replacement
|
|
495
|
+
if (op.startLine < 1 || op.endLine > lines.length) {
|
|
496
|
+
throw new Error(`Line numbers out of range (1-${lines.length})`);
|
|
497
|
+
}
|
|
498
|
+
const newText = op.newText ?? op.text;
|
|
499
|
+
if (typeof newText !== 'string') {
|
|
500
|
+
throw new Error(`'newText' must be a string for replace operation`);
|
|
501
|
+
}
|
|
502
|
+
const before = lines.slice(0, op.startLine - 1);
|
|
503
|
+
const after = lines.slice(op.endLine);
|
|
504
|
+
const newLines = newText.split('\n');
|
|
505
|
+
lines = [...before, ...newLines, ...after];
|
|
506
|
+
} else if (op.pattern) {
|
|
507
|
+
// Pattern-based replacement
|
|
508
|
+
if (typeof op.replacement !== 'string') {
|
|
509
|
+
throw new Error(`'replacement' must be a string for pattern-based replace`);
|
|
510
|
+
}
|
|
511
|
+
const regex = new RegExp(op.pattern, op.flags || '');
|
|
512
|
+
content = lines.join('\n');
|
|
513
|
+
content = content.replace(regex, op.replacement);
|
|
514
|
+
lines = content.split('\n');
|
|
515
|
+
} else {
|
|
516
|
+
throw new Error('Replace operation requires either startLine/endLine/newText or pattern/replacement');
|
|
478
517
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// Pattern-based replacement
|
|
485
|
-
const regex = new RegExp(op.pattern, op.flags || '');
|
|
486
|
-
content = content.replace(regex, op.replacement);
|
|
487
|
-
// Update lines array for subsequent operations
|
|
488
|
-
lines.length = 0;
|
|
489
|
-
lines.push(...content.split('\n'));
|
|
490
|
-
}
|
|
491
|
-
break;
|
|
492
|
-
|
|
493
|
-
case 'insert':
|
|
494
|
-
if (op.position === 'start') {
|
|
495
|
-
lines.unshift(...op.text.split('\n'));
|
|
496
|
-
} else if (op.position === 'end') {
|
|
497
|
-
lines.push(...op.text.split('\n'));
|
|
498
|
-
} else if (op.line !== undefined) {
|
|
499
|
-
if (op.line < 1 || op.line > lines.length + 1) {
|
|
500
|
-
throw new Error(`Line number out of range (1-${lines.length + 1})`);
|
|
518
|
+
break;
|
|
519
|
+
|
|
520
|
+
case 'replace_block':
|
|
521
|
+
if (typeof op.oldText !== 'string' || typeof (op.replacementContent ?? op.newText) !== 'string') {
|
|
522
|
+
throw new Error('replace_block requires both oldText and replacementContent as strings');
|
|
501
523
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
524
|
+
content = lines.join('\n');
|
|
525
|
+
if (!content.includes(op.oldText)) {
|
|
526
|
+
throw new Error('Could not find exact match for oldText in file');
|
|
527
|
+
}
|
|
528
|
+
// Check for uniqueness
|
|
529
|
+
const occurrences = content.split(op.oldText).length - 1;
|
|
530
|
+
if (occurrences > 1) {
|
|
531
|
+
throw new Error(`Found ${occurrences} occurrences of oldText. Please be more specific to ensure a unique match.`);
|
|
532
|
+
}
|
|
533
|
+
content = content.replace(op.oldText, op.replacementContent ?? op.newText);
|
|
534
|
+
lines = content.split('\n');
|
|
535
|
+
break;
|
|
536
|
+
|
|
537
|
+
case 'insert':
|
|
538
|
+
const insertText = op.newText ?? op.text;
|
|
539
|
+
if (typeof insertText !== 'string') {
|
|
540
|
+
throw new Error(`'newText' must be a string for insert operation`);
|
|
541
|
+
}
|
|
542
|
+
if (op.position === 'start') {
|
|
543
|
+
lines.unshift(...insertText.split('\n'));
|
|
544
|
+
} else if (op.position === 'end') {
|
|
545
|
+
lines.push(...insertText.split('\n'));
|
|
546
|
+
} else if (op.line !== undefined) {
|
|
547
|
+
if (op.line < 1 || op.line > lines.length + 1) {
|
|
548
|
+
throw new Error(`Line number out of range (1-${lines.length + 1})`);
|
|
549
|
+
}
|
|
550
|
+
const insertLines = insertText.split('\n');
|
|
551
|
+
lines.splice(op.line - 1, 0, ...insertLines);
|
|
552
|
+
} else {
|
|
553
|
+
throw new Error('Insert operation requires either position (start/end) or line number');
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
506
556
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
557
|
+
case 'delete':
|
|
558
|
+
if (op.startLine === undefined || op.endLine === undefined) {
|
|
559
|
+
throw new Error('Delete operation requires both startLine and endLine');
|
|
560
|
+
}
|
|
561
|
+
if (op.startLine < 1 || op.endLine > lines.length) {
|
|
562
|
+
throw new Error(`Line numbers out of range (1-${lines.length})`);
|
|
563
|
+
}
|
|
564
|
+
lines.splice(op.startLine - 1, op.endLine - op.startLine + 1);
|
|
565
|
+
break;
|
|
513
566
|
|
|
514
|
-
|
|
515
|
-
|
|
567
|
+
default:
|
|
568
|
+
throw new Error(`Unknown operation type: ${op.type}`);
|
|
569
|
+
}
|
|
570
|
+
} catch (opError) {
|
|
571
|
+
throw new Error(`Operation #${i + 1} (${op.type}) failed: ${opError.message}`);
|
|
516
572
|
}
|
|
517
573
|
}
|
|
518
574
|
|
|
@@ -521,7 +577,8 @@ const agentUtils = {
|
|
|
521
577
|
return `Successfully edited ${targetPath}`;
|
|
522
578
|
|
|
523
579
|
} catch (error) {
|
|
524
|
-
|
|
580
|
+
const pathInfo = typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path;
|
|
581
|
+
throw new Error(`Failed to edit file ${pathInfo}: ${error.message}`);
|
|
525
582
|
}
|
|
526
583
|
},
|
|
527
584
|
|