sam-coder-cli 1.0.66 → 1.0.68

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 +116 -39
  2. package/package.json +1 -1
package/bin/agi-cli.js CHANGED
@@ -418,8 +418,10 @@ You are not just executing commands - you are solving problems intelligently. Ev
418
418
 
419
419
  // Agent utilities
420
420
  const agentUtils = {
421
- async readFile(filePath) {
421
+ async readFile(input) {
422
422
  try {
423
+ const filePath = typeof input === 'string' ? input : input?.path;
424
+ if (!filePath) throw new Error('readFile: missing path');
423
425
  const content = await fs.readFile(filePath, 'utf-8');
424
426
  return content;
425
427
  } catch (error) {
@@ -427,9 +429,12 @@ const agentUtils = {
427
429
  }
428
430
  },
429
431
 
430
- async writeFile(filePath, content) {
432
+ async writeFile(input, maybeContent) {
431
433
  try {
432
- // Ensure directory exists
434
+ const filePath = typeof input === 'string' ? input : input?.path;
435
+ const content = typeof input === 'string' ? maybeContent : input?.content;
436
+ if (!filePath) throw new Error('writeFile: missing path');
437
+ if (typeof content !== 'string') throw new Error('writeFile: missing content');
433
438
  const dir = path.dirname(filePath);
434
439
  await fs.mkdir(dir, { recursive: true });
435
440
  await fs.writeFile(filePath, content, 'utf-8');
@@ -439,10 +444,14 @@ const agentUtils = {
439
444
  }
440
445
  },
441
446
 
442
- async editFile(path, edits) {
447
+ async editFile(inputPathOrObj, maybeEdits) {
443
448
  try {
449
+ const targetPath = typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path;
450
+ const edits = typeof inputPathOrObj === 'string' ? maybeEdits : inputPathOrObj?.edits;
451
+ if (!targetPath) throw new Error('editFile: missing path');
452
+ if (!edits) throw new Error('editFile: missing edits');
444
453
  // Read the current file content
445
- let content = await fs.readFile(path, 'utf-8');
454
+ let content = await fs.readFile(targetPath, 'utf-8');
446
455
  const lines = content.split('\n');
447
456
 
448
457
  // Process each edit operation
@@ -495,16 +504,18 @@ const agentUtils = {
495
504
  }
496
505
 
497
506
  // Write the modified content back to the file
498
- await fs.writeFile(path, lines.join('\n'), 'utf-8');
499
- return `Successfully edited ${path}`;
507
+ await fs.writeFile(targetPath, lines.join('\n'), 'utf-8');
508
+ return `Successfully edited ${targetPath}`;
500
509
 
501
510
  } catch (error) {
502
- throw new Error(`Failed to edit file ${path}: ${error.message}`);
511
+ throw new Error(`Failed to edit file ${typeof inputPathOrObj === 'string' ? inputPathOrObj : inputPathOrObj?.path}: ${error.message}`);
503
512
  }
504
513
  },
505
514
 
506
- async runCommand(command) {
515
+ async runCommand(input) {
507
516
  try {
517
+ const command = typeof input === 'string' ? input : input?.command;
518
+ if (!command) throw new Error('runCommand: missing command');
508
519
  const { stdout, stderr } = await execAsync(command, { cwd: process.cwd() });
509
520
  if (stderr) {
510
521
  console.error('Command stderr:', stderr);
@@ -515,40 +526,77 @@ const agentUtils = {
515
526
  }
516
527
  },
517
528
 
518
- async searchFiles(pattern) {
529
+ async searchFiles(input) {
519
530
  try {
520
- // Convert glob pattern to regex for Windows compatibility
521
- const globToRegex = (pattern) => {
531
+ const isString = typeof input === 'string';
532
+ let pattern = isString ? input : input?.pattern;
533
+ let basePathRaw = isString ? null : (input?.path || null);
534
+ const recursive = isString ? true : (input?.recursive !== false);
535
+
536
+ // Handle wildcards embedded in the path (e.g., C:\\foo\\bar\\*)
537
+ const hasWildcard = (p) => typeof p === 'string' && /[\*\?]/.test(p);
538
+ if (!pattern && hasWildcard(basePathRaw)) {
539
+ pattern = path.basename(basePathRaw);
540
+ basePathRaw = path.dirname(basePathRaw);
541
+ }
542
+
543
+ const basePath = basePathRaw
544
+ ? (path.isAbsolute(basePathRaw) ? basePathRaw : path.join(process.cwd(), basePathRaw))
545
+ : process.cwd();
546
+
547
+ const results = [];
548
+
549
+ const matchByPattern = (name) => {
550
+ if (!pattern) return true; // if no pattern, match all
522
551
  const escaped = pattern.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
523
- return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
552
+ const regex = new RegExp('^' + escaped.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
553
+ return regex.test(name);
524
554
  };
525
555
 
526
- const regex = globToRegex(pattern);
527
- const searchDir = process.cwd();
528
- const results = [];
556
+ // If base path is a file, test it directly
557
+ try {
558
+ const stat = await fs.stat(basePath);
559
+ if (stat.isFile()) {
560
+ if (matchByPattern(path.basename(basePath))) {
561
+ results.push(basePath);
562
+ }
563
+ return results.length > 0
564
+ ? `Found ${results.length} files:\n${results.join('\n')}`
565
+ : 'No files found';
566
+ }
567
+ } catch (e) {
568
+ if (e && e.code === 'ENOENT') {
569
+ return 'No files found';
570
+ }
571
+ // Non-ENOENT errors are handled by traversal below
572
+ }
529
573
 
530
- // Recursive directory search
531
574
  const search = async (dir) => {
532
- const entries = await fs.readdir(dir, { withFileTypes: true });
533
-
575
+ let entries;
576
+ try {
577
+ entries = await fs.readdir(dir, { withFileTypes: true });
578
+ } catch (e) {
579
+ if (e && e.code === 'ENOENT') return; // directory missing
580
+ throw e;
581
+ }
534
582
  for (const entry of entries) {
535
583
  const fullPath = path.join(dir, entry.name);
536
-
537
584
  try {
538
585
  if (entry.isDirectory()) {
539
- await search(fullPath);
540
- } else if (regex.test(entry.name)) {
586
+ if (recursive) {
587
+ await search(fullPath);
588
+ }
589
+ } else if (matchByPattern(entry.name)) {
541
590
  results.push(fullPath);
542
591
  }
543
- } catch (error) {
544
- // Skip files/directories we can't access
592
+ } catch (_) {
545
593
  continue;
546
594
  }
547
595
  }
548
596
  };
549
597
 
550
- await search(searchDir);
551
- return results.length > 0
598
+ await search(basePath);
599
+ return results.length > 0
552
600
  ? `Found ${results.length} files:\n${results.join('\n')}`
553
601
  : 'No files found';
554
602
  } catch (error) {
@@ -789,8 +837,8 @@ function parseSegmentedTranscript(text) {
789
837
 
790
838
  // If no blocks matched, return original
791
839
  if (visibleParts.length === 0 && thoughts.length === 0 && commentaryParts.length === 0) {
792
- // Try channel-only segments: <|channel|>X [to=Y] [<|constrain|>Z] <|message|>... (<|end|>|<|call|>|<|return|>)
793
- const chanRegex = /\<\|channel\|>\s*([a-zA-Z]+)\s*(?:to=([^\s<]+))?\s*(?:\<\|constrain\|>(\w+))?\s*\<\|message\|>([\s\S]*?)(?:\<\|end\|>|\<\|call\|>|\<\|return\|>)/gi;
840
+ // Try channel-only segments: allow missing trailing stop tokens; stop at next token or end
841
+ const chanRegex = /\<\|channel\|>\s*([a-zA-Z]+)\s*(?:to=([^\s<]+))?\s*(?:\<\|constrain\|>(\w+))?\s*\<\|message\|>([\s\S]*?)(?=(?:\<\|start\|>|\<\|channel\|>|\<\|end\|>|\<\|call\|>|\<\|return\|>|$))/gi;
794
842
  let anyChannel = false;
795
843
  let commsWithRecipients = [];
796
844
  while ((match = chanRegex.exec(text)) !== null) {
@@ -825,16 +873,8 @@ function parseSegmentedTranscript(text) {
825
873
  if (funcName.startsWith('functions.')) {
826
874
  funcName = funcName.slice('functions.'.length);
827
875
  }
828
- // parse args
829
- let args = {};
830
- if (item.constraint === 'json' || item.body.startsWith('{') || item.body.startsWith('[')) {
831
- try {
832
- const parsed = JSON.parse(item.body);
833
- args = parsed;
834
- } catch (_) {
835
- // ignore parse error; leave empty
836
- }
837
- }
876
+ // parse args (robust: try code blocks, then first JSON object)
877
+ let args = extractArgsJson(item.body);
838
878
  recoveredToolCalls.push({
839
879
  id: `inline-${recoveredToolCalls.length + 1}`,
840
880
  type: 'function',
@@ -882,6 +922,34 @@ function parseSegmentedTranscript(text) {
882
922
  };
883
923
  }
884
924
 
925
+ // Extract first JSON object/array from arbitrary text, including fenced code blocks
926
+ function extractArgsJson(text) {
927
+ if (!text || typeof text !== 'string') return {};
928
+ // Prefer fenced code block
929
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
930
+ if (fence && fence[1]) {
931
+ try { return JSON.parse(fence[1].trim()); } catch (_) {}
932
+ }
933
+ // Try straightforward parse
934
+ const trimmed = text.trim();
935
+ if ((trimmed.startsWith('{') && trimmed.includes('}')) || (trimmed.startsWith('[') && trimmed.includes(']'))) {
936
+ try { return JSON.parse(trimmed); } catch (_) {}
937
+ }
938
+ // Fallback: find first {...} minimally
939
+ const braceRegex = /\{[\s\S]*?\}/g;
940
+ const m = braceRegex.exec(text);
941
+ if (m && m[0]) {
942
+ try { return JSON.parse(m[0]); } catch (_) {}
943
+ }
944
+ // Fallback: find first [...] minimally
945
+ const arrRegex = /\[[\s\S]*?\]/g;
946
+ const a = arrRegex.exec(text);
947
+ if (a && a[0]) {
948
+ try { return JSON.parse(a[0]); } catch (_) {}
949
+ }
950
+ return {};
951
+ }
952
+
885
953
  // Call OpenRouter API with tool calling
886
954
  async function callOpenRouter(messages, currentModel, useJson = false) {
887
955
  const apiKey = OPENROUTER_API_KEY;
@@ -1072,7 +1140,16 @@ async function handleToolCalls(toolCalls, messages) {
1072
1140
  throw new Error(`Tool '${functionName}' not found`);
1073
1141
  }
1074
1142
 
1075
- const result = await agentUtils[functionName](...Object.values(args));
1143
+ let result;
1144
+ if (Array.isArray(args)) {
1145
+ result = await agentUtils[functionName](...args);
1146
+ } else if (args && typeof args === 'object') {
1147
+ result = await agentUtils[functionName](args);
1148
+ } else if (args !== undefined) {
1149
+ result = await agentUtils[functionName](args);
1150
+ } else {
1151
+ result = await agentUtils[functionName]();
1152
+ }
1076
1153
  console.log('✅ Tool executed successfully');
1077
1154
 
1078
1155
  const resultContent = typeof result === 'string' ? result : JSON.stringify(result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {