Skip to main content
Glama
by microsoft
grep.ts4.3 kB
import { TraceOptions } from "./trace" import { runtimeHost } from "./host" import { JSONLTryParse } from "./jsonl" import { resolveFileContent, resolveFileContents } from "./file" import { uniq } from "es-toolkit" import { addLineNumbers } from "./liner" import { arrayify } from "./util" import { filterGitIgnore } from "./gitignore" import { genaiscriptDebug } from "./debug" import { tryStat } from "./fs" import { CancellationOptions, checkCancelled } from "./cancellation" const dbg = genaiscriptDebug("grep") async function importRipGrep(options?: TraceOptions) { const { trace } = options || {} try { const { rgPath } = await import("@lvce-editor/ripgrep") dbg(`rg: %s`, rgPath) const rgStat = await tryStat(rgPath) if (!rgStat?.isFile()) throw new Error( `ripgrep not found at ${rgPath}. Please reinstall genaiscript.` ) return rgPath } catch (e) { dbg(`%O`, e) trace?.error(`failed to ripgrep`, e) throw e } } export type GrepResult = { type: "match" | "context" | "begin" | "end" data: { path: { text: string } lines: { text: string } line_number: number } }[] /** * Executes a grep-like search across the workspace using ripgrep. * * @param pattern - The search pattern, either a string or a regular expression. * @param options - Optional settings to customize the search behavior: * - `path`: Specifies one or more paths to search. * - `glob`: Array of glob patterns to include or exclude files. * - `readText`: When false, avoids reading file content. * - `applyGitIgnore`: When false, bypasses .gitignore filtering. * - Accepts other trace and workspace-specific options. * @returns An object containing: * - `files`: List of files that matched the pattern. * - `matches`: List of detailed matches including filenames and content with line numbers. */ export async function grepSearch( pattern: string | RegExp, options?: TraceOptions & CancellationOptions & WorkspaceGrepOptions ): Promise<{ data: GrepResult files: WorkspaceFile[] matches: WorkspaceFile[] }> { const { cancellationToken, trace } = options || {} const rgPath = await importRipGrep() let { path: paths, glob: globs, readText, applyGitIgnore, debug, } = options || {} globs = arrayify(globs) paths = arrayify(paths) const args: string[] = ["--json", "--multiline", "--context", "3"] if (debug) args.push("--debug") if (typeof pattern === "string") { args.push("--smart-case", pattern) } else { if (pattern.ignoreCase) args.push("--ignore-case") args.push(pattern.source) } if (globs) for (const glob of globs) { args.push("--glob") args.push(glob) } if (paths.length) args.push(...paths) else if (globs?.length) args.push(".") dbg(`args: %o`, args) const res = await runtimeHost.exec(undefined, rgPath, args, options) if (!res.stdout) { dbg(`no output: %s`, res.stderr) return { data: [], files: [], matches: [] } } const resl = JSONLTryParse(res.stdout || "") as GrepResult checkCancelled(cancellationToken) let filenames = uniq( resl .filter(({ type }) => type === "match") .map(({ data }) => data.path.text) ) if (applyGitIgnore !== false) { dbg(`apply git ignore`) filenames = await filterGitIgnore(filenames) } const files = filenames.map((filename) => ({ filename })) const filesSet = new Set(filenames) const matches = resl .filter(({ type }) => type === "match") .filter(({ data }) => filesSet.has(data.path.text)) .map( ({ data }) => <WorkspaceFile>{ filename: data.path.text, content: addLineNumbers(data.lines.text.trimEnd(), { startLine: data.line_number, }), } ) dbg(`read text: `, readText) if (readText !== false) await resolveFileContents(files, { trace, cancellationToken }) return { data: resl, files, matches } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server