@bestdefense/bd-agent
Version:
An AI-powered coding assistant CLI that connects to AWS Bedrock
321 lines • 12.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchTools = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const chalk_1 = __importDefault(require("chalk"));
const glob_1 = require("glob");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
// Check if ripgrep is installed
async function checkRipgrep() {
try {
await execAsync('which rg');
return true;
}
catch {
return false;
}
}
// Fallback grep implementation using Node.js
async function fallbackGrep(pattern, searchPath, options) {
try {
const files = [];
const matches = [];
// Get list of files to search
const globPattern = options.glob || '**/*';
const searchFiles = await (0, glob_1.glob)(globPattern, {
cwd: searchPath,
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
nodir: true
});
for (const file of searchFiles) {
const filePath = path.join(searchPath, file);
// Skip binary files
if (await isBinaryFile(filePath))
continue;
const content = await fs.promises.readFile(filePath, 'utf-8');
const lines = content.split('\n');
const regex = new RegExp(pattern, options.case_insensitive ? 'gi' : 'g');
lines.forEach((line, index) => {
if (regex.test(line)) {
files.push(file);
if (options.output_mode === 'content' || !options.output_mode) {
matches.push({
file,
line: index + 1,
match: line.trim()
});
}
}
});
}
if (options.output_mode === 'files_with_matches') {
return {
success: true,
files: [...new Set(files)].slice(0, options.head_limit || undefined)
};
}
else if (options.output_mode === 'count') {
return {
success: true,
count: matches.length
};
}
return {
success: true,
matches: matches.slice(0, options.head_limit || undefined)
};
}
catch (error) {
return { success: false, error: error.message };
}
}
async function isBinaryFile(filePath) {
const buffer = Buffer.alloc(512);
const fd = await fs.promises.open(filePath, 'r');
await fd.read(buffer, 0, 512, 0);
await fd.close();
// Check for null bytes
return buffer.includes(0);
}
exports.searchTools = [
{
name: 'grep',
description: 'Search for patterns in files using ripgrep or fallback implementation',
input_schema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'The regular expression pattern to search for'
},
path: {
type: 'string',
description: 'File or directory to search in (defaults to current directory)'
},
glob: {
type: 'string',
description: 'Glob pattern to filter files (e.g., "*.js", "**/*.tsx")'
},
type: {
type: 'string',
description: 'File type to search (e.g., "js", "py", "rust")'
},
output_mode: {
type: 'string',
enum: ['content', 'files_with_matches', 'count'],
description: 'Output mode (defaults to "content")'
},
case_insensitive: {
type: 'boolean',
description: 'Case insensitive search',
default: false
},
context_lines: {
type: 'number',
description: 'Number of context lines to show',
default: 0
},
head_limit: {
type: 'number',
description: 'Limit output to first N results'
},
multiline: {
type: 'boolean',
description: 'Enable multiline mode',
default: false
}
},
required: ['pattern']
},
execute: async (params) => {
const { pattern, path: searchPath = '.', glob: globPattern, type: fileType, output_mode = 'content', case_insensitive = false, context_lines = 0, head_limit, multiline = false } = params;
const hasRipgrep = await checkRipgrep();
if (!hasRipgrep) {
console.log(chalk_1.default.yellow('⚠️ ripgrep not found, using fallback search implementation'));
return await fallbackGrep(pattern, searchPath, params);
}
try {
// Build ripgrep command
let cmd = 'rg';
const args = [];
// Add pattern
args.push(`"${pattern.replace(/"/g, '\\"')}"`);
// Add path
args.push(searchPath);
// Add options
if (case_insensitive)
args.push('-i');
if (context_lines > 0) {
args.push(`-C${context_lines}`);
}
if (multiline)
args.push('-U', '--multiline-dotall');
if (globPattern)
args.push('--glob', `"${globPattern}"`);
if (fileType)
args.push('--type', fileType);
// Output format
if (output_mode === 'files_with_matches') {
args.push('-l');
}
else if (output_mode === 'count') {
args.push('-c');
}
else {
args.push('-n'); // Show line numbers
if (context_lines === 0) {
args.push('--no-heading');
}
}
// Execute ripgrep
const fullCmd = `${cmd} ${args.join(' ')}`;
const { stdout, stderr } = await execAsync(fullCmd, {
maxBuffer: 10 * 1024 * 1024 // 10MB buffer
});
if (stderr && !stdout) {
return { success: false, error: stderr };
}
// Parse output based on mode
if (output_mode === 'files_with_matches') {
const files = stdout.trim().split('\n').filter(Boolean);
return {
success: true,
files: head_limit ? files.slice(0, head_limit) : files
};
}
else if (output_mode === 'count') {
const total = stdout.trim().split('\n')
.map(line => {
const match = line.match(/:(\d+)$/);
return match ? parseInt(match[1]) : 0;
})
.reduce((sum, count) => sum + count, 0);
return {
success: true,
count: total
};
}
else {
// Parse content matches
const lines = stdout.trim().split('\n').filter(Boolean);
const matches = lines.map(line => {
const match = line.match(/^(.+?):(\d+):(.*)$/);
if (match) {
return {
file: match[1],
line: parseInt(match[2]),
match: match[3]
};
}
return null;
}).filter((m) => m !== null);
return {
success: true,
matches: head_limit ? matches.slice(0, head_limit) : matches
};
}
}
catch (error) {
// Handle case where no matches found (ripgrep exits with code 1)
if (error.code === 1) {
return {
success: true,
matches: [],
files: [],
count: 0
};
}
return { success: false, error: error.message };
}
}
},
{
name: 'glob',
description: 'Find files matching glob patterns',
input_schema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'The glob pattern to match files against'
},
path: {
type: 'string',
description: 'The directory to search in (defaults to current directory)'
},
ignore: {
type: 'array',
items: { type: 'string' },
description: 'Patterns to ignore',
default: ['**/node_modules/**', '**/.git/**']
}
},
required: ['pattern']
},
execute: async ({ pattern, path: searchPath = '.', ignore = ['**/node_modules/**', '**/.git/**'] }) => {
try {
const files = await (0, glob_1.glob)(pattern, {
cwd: searchPath,
ignore,
nodir: true
});
// Sort by modification time (newest first)
const filesWithStats = await Promise.all(files.map(async (file) => {
const fullPath = path.join(searchPath, file);
const stats = await fs.promises.stat(fullPath);
return { file, mtime: stats.mtime };
}));
filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
const sortedFiles = filesWithStats.map(f => f.file);
return {
success: true,
files: sortedFiles,
count: sortedFiles.length
};
}
catch (error) {
return { success: false, error: error.message };
}
}
}
];
//# sourceMappingURL=search-tools.js.map