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.
Files changed (2) hide show
  1. package/bin/agi-cli.js +99 -44
  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,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
- 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
+ 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
- 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})`);
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
- const insertLines = op.text.split('\n');
503
- lines.splice(op.line - 1, 0, ...insertLines);
504
- }
505
- break;
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
- 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;
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
- default:
515
- throw new Error(`Unknown operation type: ${op.type}`);
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
- throw new Error(`Failed to edit file ${typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path}: ${error.message}`);
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
 
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.7",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {