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.
Files changed (2) hide show
  1. package/bin/agi-cli.js +105 -48
  2. 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
- text: { type: 'string' }
160
+ newText: { type: 'string' }
145
161
  },
146
- required: ['position', 'text']
162
+ required: ['position', 'newText']
147
163
  },
148
164
  {
149
165
  properties: {
150
166
  type: { const: 'insert' },
151
167
  line: { type: 'number' },
152
- text: { type: 'string' }
168
+ newText: { type: 'string' }
153
169
  },
154
- required: ['line', 'text']
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
- const lines = content.split('\n');
485
+ let lines = content.split('\n');
469
486
 
470
487
  // Process each edit operation
471
- for (const op of edits.operations) {
472
- switch (op.type) {
473
- case 'replace':
474
- if (op.startLine !== undefined && op.endLine !== undefined) {
475
- // Line-based replacement
476
- if (op.startLine < 1 || op.endLine > lines.length) {
477
- throw new Error(`Line numbers out of range (1-${lines.length})`);
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
- const before = lines.slice(0, op.startLine - 1);
480
- const after = lines.slice(op.endLine);
481
- const newLines = op.newText.split('\n');
482
- lines.splice(0, lines.length, ...before, ...newLines, ...after);
483
- } else if (op.pattern) {
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
- const insertLines = op.text.split('\n');
503
- lines.splice(op.line - 1, 0, ...insertLines);
504
- }
505
- break;
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
- case 'delete':
508
- if (op.startLine < 1 || op.endLine > lines.length) {
509
- throw new Error(`Line numbers out of range (1-${lines.length})`);
510
- }
511
- lines.splice(op.startLine - 1, op.endLine - op.startLine + 1);
512
- break;
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
- default:
515
- throw new Error(`Unknown operation type: ${op.type}`);
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
- throw new Error(`Failed to edit file ${typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path}: ${error.message}`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "2.0.5",
3
+ "version": "2.0.8",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {