UNPKG

@vibe-kit/grok-cli

Version:

An open-source AI agent that brings the power of Grok directly into your terminal.

331 lines 12.7 kB
"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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchTool = void 0; const child_process_1 = require("child_process"); const confirmation_service_1 = require("../utils/confirmation-service"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); class SearchTool { constructor() { this.confirmationService = confirmation_service_1.ConfirmationService.getInstance(); this.currentDirectory = process.cwd(); } /** * Unified search method that can search for text content or find files */ async search(query, options = {}) { try { const searchType = options.searchType || "both"; const results = []; // Search for text content if requested if (searchType === "text" || searchType === "both") { const textResults = await this.executeRipgrep(query, options); results.push(...textResults.map((r) => ({ type: "text", file: r.file, line: r.line, column: r.column, text: r.text, match: r.match, }))); } // Search for files if requested if (searchType === "files" || searchType === "both") { const fileResults = await this.findFilesByPattern(query, options); results.push(...fileResults.map((r) => ({ type: "file", file: r.path, score: r.score, }))); } if (results.length === 0) { return { success: true, output: `No results found for "${query}"`, }; } const formattedOutput = this.formatUnifiedResults(results, query, searchType); return { success: true, output: formattedOutput, }; } catch (error) { return { success: false, error: `Search error: ${error.message}`, }; } } /** * Execute ripgrep command with specified options */ async executeRipgrep(query, options) { return new Promise((resolve, reject) => { const args = [ "--json", "--with-filename", "--line-number", "--column", "--no-heading", "--color=never", ]; // Add case sensitivity if (!options.caseSensitive) { args.push("--ignore-case"); } // Add whole word matching if (options.wholeWord) { args.push("--word-regexp"); } // Add regex mode if (!options.regex) { args.push("--fixed-strings"); } // Add max results limit if (options.maxResults) { args.push("--max-count", options.maxResults.toString()); } // Add file type filters if (options.fileTypes) { options.fileTypes.forEach((type) => { args.push("--type", type); }); } // Add include pattern if (options.includePattern) { args.push("--glob", options.includePattern); } // Add exclude pattern if (options.excludePattern) { args.push("--glob", `!${options.excludePattern}`); } // Add exclude files if (options.excludeFiles) { options.excludeFiles.forEach((file) => { args.push("--glob", `!${file}`); }); } // Respect gitignore and common ignore patterns args.push("--no-require-git", "--follow", "--glob", "!.git/**", "--glob", "!node_modules/**", "--glob", "!.DS_Store", "--glob", "!*.log"); // Add query and search directory args.push(query, this.currentDirectory); const rg = (0, child_process_1.spawn)("rg", args); let output = ""; let errorOutput = ""; rg.stdout.on("data", (data) => { output += data.toString(); }); rg.stderr.on("data", (data) => { errorOutput += data.toString(); }); rg.on("close", (code) => { if (code === 0 || code === 1) { // 0 = found, 1 = not found const results = this.parseRipgrepOutput(output); resolve(results); } else { reject(new Error(`Ripgrep failed with code ${code}: ${errorOutput}`)); } }); rg.on("error", (error) => { reject(error); }); }); } /** * Parse ripgrep JSON output into SearchResult objects */ parseRipgrepOutput(output) { const results = []; const lines = output .trim() .split("\n") .filter((line) => line.length > 0); for (const line of lines) { try { const parsed = JSON.parse(line); if (parsed.type === "match") { const data = parsed.data; results.push({ file: data.path.text, line: data.line_number, column: data.submatches[0]?.start || 0, text: data.lines.text.trim(), match: data.submatches[0]?.match?.text || "", }); } } catch (e) { // Skip invalid JSON lines continue; } } return results; } /** * Find files by pattern using a simple file walking approach */ async findFilesByPattern(pattern, options) { const files = []; const maxResults = options.maxResults || 50; const searchPattern = pattern.toLowerCase(); const walkDir = async (dir, depth = 0) => { if (depth > 10 || files.length >= maxResults) return; // Prevent infinite recursion and limit results try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { if (files.length >= maxResults) break; const fullPath = path.join(dir, entry.name); const relativePath = path.relative(this.currentDirectory, fullPath); // Skip hidden files unless explicitly included if (!options.includeHidden && entry.name.startsWith(".")) { continue; } // Skip common directories if (entry.isDirectory() && [ "node_modules", ".git", ".svn", ".hg", "dist", "build", ".next", ".cache", ].includes(entry.name)) { continue; } // Apply exclude pattern if (options.excludePattern && relativePath.includes(options.excludePattern)) { continue; } if (entry.isFile()) { const score = this.calculateFileScore(entry.name, relativePath, searchPattern); if (score > 0) { files.push({ path: relativePath, name: entry.name, score, }); } } else if (entry.isDirectory()) { await walkDir(fullPath, depth + 1); } } } catch (error) { // Skip directories we can't read } }; await walkDir(this.currentDirectory); // Sort by score (descending) and return top results return files.sort((a, b) => b.score - a.score).slice(0, maxResults); } /** * Calculate fuzzy match score for file names */ calculateFileScore(fileName, filePath, pattern) { const lowerFileName = fileName.toLowerCase(); const lowerFilePath = filePath.toLowerCase(); // Exact matches get highest score if (lowerFileName === pattern) return 100; if (lowerFileName.includes(pattern)) return 80; // Path matches get medium score if (lowerFilePath.includes(pattern)) return 60; // Fuzzy matching - check if all characters of pattern exist in order let patternIndex = 0; for (let i = 0; i < lowerFileName.length && patternIndex < pattern.length; i++) { if (lowerFileName[i] === pattern[patternIndex]) { patternIndex++; } } if (patternIndex === pattern.length) { // All characters found in order - score based on how close they are return Math.max(10, 40 - (fileName.length - pattern.length)); } return 0; } /** * Format unified search results for display */ formatUnifiedResults(results, query, searchType) { if (results.length === 0) { return `No results found for "${query}"`; } let output = `Search results for "${query}":\n`; // Separate text and file results const textResults = results.filter((r) => r.type === "text"); const fileResults = results.filter((r) => r.type === "file"); // Show all unique files (from both text matches and file matches) const allFiles = new Set(); // Add files from text results textResults.forEach((result) => { allFiles.add(result.file); }); // Add files from file search results fileResults.forEach((result) => { allFiles.add(result.file); }); const fileList = Array.from(allFiles); const displayLimit = 8; // Show files in compact format fileList.slice(0, displayLimit).forEach((file) => { // Count matches in this file for text results const matchCount = textResults.filter((r) => r.file === file).length; const matchIndicator = matchCount > 0 ? ` (${matchCount} matches)` : ""; output += ` ${file}${matchIndicator}\n`; }); // Show "+X more" if there are additional results if (fileList.length > displayLimit) { const remaining = fileList.length - displayLimit; output += ` ... +${remaining} more\n`; } return output.trim(); } /** * Update current working directory */ setCurrentDirectory(directory) { this.currentDirectory = directory; } /** * Get current working directory */ getCurrentDirectory() { return this.currentDirectory; } } exports.SearchTool = SearchTool; //# sourceMappingURL=search.js.map