count-matches
Count occurrences of a specific pattern in files or directories using ripgrep, with options for case sensitivity, file filtering, line-based counting, and colored output.
Instructions
Count matches in files using ripgrep
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| caseSensitive | No | Use case sensitive search (default: auto) | |
| countLines | No | Count matching lines instead of total matches | |
| filePattern | No | Filter by file type or glob | |
| path | Yes | Directory or file(s) to search. | |
| pattern | Yes | The search pattern (regex by default) | |
| useColors | No | Use colors in output (default: false) |
Implementation Reference
- src/index.ts:380-439 (handler)The handler for the 'count-matches' tool. Parses input arguments, constructs a ripgrep command with appropriate flags for counting matches (either lines with -c or total matches with --count-matches), executes it via the exec helper, processes the output, and returns the result.case "count-matches": { const pattern = String(args.pattern || ""); const path = String(args.path); const caseSensitive = typeof args.caseSensitive === 'boolean' ? args.caseSensitive : undefined; const filePattern = args.filePattern ? String(args.filePattern) : undefined; const countLines = typeof args.countLines === 'boolean' ? args.countLines : true; const useColors = typeof args.useColors === 'boolean' ? args.useColors : false; if (!pattern) { return { isError: true, content: [{ type: "text", text: "Error: Pattern is required" }] }; } // Build the rg command with flags let command = "rg"; // Add case sensitivity flag if specified if (caseSensitive === true) { command += " -s"; // Case sensitive } else if (caseSensitive === false) { command += " -i"; // Case insensitive } // Add file pattern if specified if (filePattern) { command += ` -g ${escapeShellArg(filePattern)}`; } // Add count flag if (countLines) { command += " -c"; // Count lines } else { command += " --count-matches"; // Count total matches } // Add color setting command += useColors ? " --color always" : " --color never"; // Add pattern and path command += ` ${escapeShellArg(pattern)} ${escapeShellArg(path)}`; console.error(`Executing: ${command}`); const { stdout, stderr } = await exec(command); // If there's anything in stderr, log it for debugging if (stderr) { console.error(`ripgrep stderr: ${stderr}`); } return { content: [ { type: "text", text: processOutput(stdout, useColors) || "No matches found" } ] }; }
- src/index.ts:142-152 (schema)Input schema defining parameters for the 'count-matches' tool, including pattern, path, case sensitivity, file filtering, count type, and color options.inputSchema: { type: "object", properties: { pattern: { type: "string", description: "The search pattern (regex by default)" }, path: { type: "string", description: "Directory or file(s) to search." }, caseSensitive: { type: "boolean", description: "Use case sensitive search (default: auto)" }, filePattern: { type: "string", description: "Filter by file type or glob" }, countLines: { type: "boolean", description: "Count matching lines instead of total matches" }, useColors: { type: "boolean", description: "Use colors in output (default: false)" } }, required: ["pattern", "path"]
- src/index.ts:139-154 (registration)Tool registration in the ListTools response, specifying name, description, and input schema for 'count-matches'.{ name: "count-matches", description: "Count matches in files using ripgrep", inputSchema: { type: "object", properties: { pattern: { type: "string", description: "The search pattern (regex by default)" }, path: { type: "string", description: "Directory or file(s) to search." }, caseSensitive: { type: "boolean", description: "Use case sensitive search (default: auto)" }, filePattern: { type: "string", description: "Filter by file type or glob" }, countLines: { type: "boolean", description: "Count matching lines instead of total matches" }, useColors: { type: "boolean", description: "Use colors in output (default: false)" } }, required: ["pattern", "path"] } },
- src/index.ts:185-188 (registration)Checks if the requested tool name is 'count-matches' (among others) before handling the CallToolRequest in the switch statement.if (!["search", "advanced-search", "count-matches", "list-files", "list-file-types"].includes(toolName)) { // Return ServerResult.NEXT to allow the next handler to process the request return Object.create(null); }
- src/index.ts:51-91 (helper)Helper function to execute shell commands (ripgrep) safely with spawn, capturing stdout/stderr, handling exit codes appropriately for ripgrep.function exec(command: string): Promise<{ stdout: string; stderr: string }> { return new Promise((resolve, reject) => { const parts = command.split(" "); const program = parts[0]; const args = parts.slice(1).filter(arg => arg.length > 0); // Use spawn with explicit stdio control const child = spawn(program, args, { shell: true, // Use shell to handle quotes and escaping }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (data) => { stdout += data; }); child.stderr.on("data", (data) => { stderr += data; }); child.on("close", (code) => { if (code === 0 || code === 1) { // Code 1 is "no matches" for ripgrep resolve({ stdout, stderr }); } else { const error = new Error(`Command failed with exit code ${code}`); Object.assign(error, { code, stdout, stderr }); reject(error); } }); // Handle process errors child.on("error", (error) => { reject(error); }); }); }