sam-coder-cli 2.0.5 → 2.0.7
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 +99 -44
- 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,6 +145,14 @@ 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' },
|
|
@@ -463,56 +479,94 @@ 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
|
+
if (typeof op.newText !== 'string') {
|
|
499
|
+
throw new Error(`'newText' must be a string for replace operation`);
|
|
500
|
+
}
|
|
501
|
+
const before = lines.slice(0, op.startLine - 1);
|
|
502
|
+
const after = lines.slice(op.endLine);
|
|
503
|
+
const newLines = op.newText.split('\n');
|
|
504
|
+
lines = [...before, ...newLines, ...after];
|
|
505
|
+
} else if (op.pattern) {
|
|
506
|
+
// Pattern-based replacement
|
|
507
|
+
if (typeof op.replacement !== 'string') {
|
|
508
|
+
throw new Error(`'replacement' must be a string for pattern-based replace`);
|
|
509
|
+
}
|
|
510
|
+
const regex = new RegExp(op.pattern, op.flags || '');
|
|
511
|
+
content = lines.join('\n');
|
|
512
|
+
content = content.replace(regex, op.replacement);
|
|
513
|
+
lines = content.split('\n');
|
|
514
|
+
} else {
|
|
515
|
+
throw new Error('Replace operation requires either startLine/endLine/newText or pattern/replacement');
|
|
478
516
|
}
|
|
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})`);
|
|
517
|
+
break;
|
|
518
|
+
|
|
519
|
+
case 'replace_block':
|
|
520
|
+
if (typeof op.oldText !== 'string' || typeof op.replacementContent !== 'string') {
|
|
521
|
+
throw new Error('replace_block requires both oldText and replacementContent as strings');
|
|
501
522
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
523
|
+
content = lines.join('\n');
|
|
524
|
+
if (!content.includes(op.oldText)) {
|
|
525
|
+
throw new Error('Could not find exact match for oldText in file');
|
|
526
|
+
}
|
|
527
|
+
// Check for uniqueness
|
|
528
|
+
const occurrences = content.split(op.oldText).length - 1;
|
|
529
|
+
if (occurrences > 1) {
|
|
530
|
+
throw new Error(`Found ${occurrences} occurrences of oldText. Please be more specific to ensure a unique match.`);
|
|
531
|
+
}
|
|
532
|
+
content = content.replace(op.oldText, op.replacementContent);
|
|
533
|
+
lines = content.split('\n');
|
|
534
|
+
break;
|
|
506
535
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
536
|
+
case 'insert':
|
|
537
|
+
if (typeof op.text !== 'string') {
|
|
538
|
+
throw new Error(`'text' must be a string for insert operation`);
|
|
539
|
+
}
|
|
540
|
+
if (op.position === 'start') {
|
|
541
|
+
lines.unshift(...op.text.split('\n'));
|
|
542
|
+
} else if (op.position === 'end') {
|
|
543
|
+
lines.push(...op.text.split('\n'));
|
|
544
|
+
} else if (op.line !== undefined) {
|
|
545
|
+
if (op.line < 1 || op.line > lines.length + 1) {
|
|
546
|
+
throw new Error(`Line number out of range (1-${lines.length + 1})`);
|
|
547
|
+
}
|
|
548
|
+
const insertLines = op.text.split('\n');
|
|
549
|
+
lines.splice(op.line - 1, 0, ...insertLines);
|
|
550
|
+
} else {
|
|
551
|
+
throw new Error('Insert operation requires either position (start/end) or line number');
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
513
554
|
|
|
514
|
-
|
|
515
|
-
|
|
555
|
+
case 'delete':
|
|
556
|
+
if (op.startLine === undefined || op.endLine === undefined) {
|
|
557
|
+
throw new Error('Delete operation requires both startLine and endLine');
|
|
558
|
+
}
|
|
559
|
+
if (op.startLine < 1 || op.endLine > lines.length) {
|
|
560
|
+
throw new Error(`Line numbers out of range (1-${lines.length})`);
|
|
561
|
+
}
|
|
562
|
+
lines.splice(op.startLine - 1, op.endLine - op.startLine + 1);
|
|
563
|
+
break;
|
|
564
|
+
|
|
565
|
+
default:
|
|
566
|
+
throw new Error(`Unknown operation type: ${op.type}`);
|
|
567
|
+
}
|
|
568
|
+
} catch (opError) {
|
|
569
|
+
throw new Error(`Operation #${i + 1} (${op.type}) failed: ${opError.message}`);
|
|
516
570
|
}
|
|
517
571
|
}
|
|
518
572
|
|
|
@@ -521,7 +575,8 @@ const agentUtils = {
|
|
|
521
575
|
return `Successfully edited ${targetPath}`;
|
|
522
576
|
|
|
523
577
|
} catch (error) {
|
|
524
|
-
|
|
578
|
+
const pathInfo = typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path;
|
|
579
|
+
throw new Error(`Failed to edit file ${pathInfo}: ${error.message}`);
|
|
525
580
|
}
|
|
526
581
|
},
|
|
527
582
|
|