Files
oh-my-openagent/src/tools/grep/constants.ts
YeonGyu-Kim d3e317663e feat(grep): add ripgrep auto-download and installation
Port ripgrep auto-installation feature from original OpenCode (sst/opencode).
When ripgrep is not available, automatically downloads and installs it from
GitHub releases.

Features:
- Platform detection (darwin/linux/win32, arm64/x64)
- Archive extraction (tar.gz/zip)
- Caches binary in ~/.cache/oh-my-opencode/bin/
- New resolveGrepCliWithAutoInstall() async function
- Falls back to grep if auto-install fails

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-14 10:52:18 +09:00

124 lines
2.9 KiB
TypeScript

import { existsSync } from "node:fs"
import { join, dirname } from "node:path"
import { spawnSync } from "node:child_process"
import { getInstalledRipgrepPath, downloadAndInstallRipgrep } from "./downloader"
export type GrepBackend = "rg" | "grep"
interface ResolvedCli {
path: string
backend: GrepBackend
}
let cachedCli: ResolvedCli | null = null
let autoInstallAttempted = false
function findExecutable(name: string): string | null {
const isWindows = process.platform === "win32"
const cmd = isWindows ? "where" : "which"
try {
const result = spawnSync(cmd, [name], { encoding: "utf-8", timeout: 5000 })
if (result.status === 0 && result.stdout.trim()) {
return result.stdout.trim().split("\n")[0]
}
} catch {
// Command execution failed
}
return null
}
function getOpenCodeBundledRg(): string | null {
const execPath = process.execPath
const execDir = dirname(execPath)
const isWindows = process.platform === "win32"
const rgName = isWindows ? "rg.exe" : "rg"
const candidates = [
join(execDir, rgName),
join(execDir, "bin", rgName),
join(execDir, "..", "bin", rgName),
join(execDir, "..", "libexec", rgName),
]
for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate
}
}
return null
}
export function resolveGrepCli(): ResolvedCli {
if (cachedCli) return cachedCli
const bundledRg = getOpenCodeBundledRg()
if (bundledRg) {
cachedCli = { path: bundledRg, backend: "rg" }
return cachedCli
}
const systemRg = findExecutable("rg")
if (systemRg) {
cachedCli = { path: systemRg, backend: "rg" }
return cachedCli
}
const installedRg = getInstalledRipgrepPath()
if (installedRg) {
cachedCli = { path: installedRg, backend: "rg" }
return cachedCli
}
const grep = findExecutable("grep")
if (grep) {
cachedCli = { path: grep, backend: "grep" }
return cachedCli
}
cachedCli = { path: "rg", backend: "rg" }
return cachedCli
}
export async function resolveGrepCliWithAutoInstall(): Promise<ResolvedCli> {
const current = resolveGrepCli()
if (current.backend === "rg") {
return current
}
if (autoInstallAttempted) {
return current
}
autoInstallAttempted = true
try {
const rgPath = await downloadAndInstallRipgrep()
cachedCli = { path: rgPath, backend: "rg" }
return cachedCli
} catch {
return current
}
}
export const DEFAULT_MAX_DEPTH = 20
export const DEFAULT_MAX_FILESIZE = "10M"
export const DEFAULT_MAX_COUNT = 500
export const DEFAULT_MAX_COLUMNS = 1000
export const DEFAULT_CONTEXT = 2
export const DEFAULT_TIMEOUT_MS = 300_000
export const DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024
export const RG_SAFETY_FLAGS = [
"--no-follow",
"--color=never",
"--no-heading",
"--line-number",
"--with-filename",
] as const
export const GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"] as const