Files
claude-engineering-plugin/src/utils/detect-tools.ts
Trevin Chow c2d60b47be
Some checks failed
CI / pr-title (push) Has been cancelled
CI / test (push) Has been cancelled
Release PR / release-pr (push) Has been cancelled
Release PR / publish-cli (push) Has been cancelled
refactor(install): prefer native plugin install across targets (#609)
Co-authored-by: John Cavanaugh <cavanaug@users.noreply.github.com>
2026-04-20 18:47:07 -07:00

105 lines
2.7 KiB
TypeScript

import os from "os"
import path from "path"
import { pathExists } from "./files"
import { resolveOpenCodeGlobalRoot } from "./opencode-config"
export type DetectedTool = {
name: string
detected: boolean
reason: string
}
type DetectableTool = {
name: string
detectPaths: (home: string, cwd: string) => string[]
}
const detectableTools: DetectableTool[] = [
{
name: "opencode",
detectPaths: (home, cwd) => {
// Resolve the OpenCode global root through the shared helper so that
// detection agrees with install/cleanup on `OPENCODE_CONFIG_DIR`. When
// the env var is unset, the helper falls back to `os.homedir()`, which
// may differ from the `home` arg threaded through for testability; in
// that case prefer the explicit `home` param so existing callers that
// override it keep working.
const envDir = process.env.OPENCODE_CONFIG_DIR?.trim()
const globalRoot = envDir
? resolveOpenCodeGlobalRoot()
: path.join(home, ".config", "opencode")
return [globalRoot, path.join(cwd, ".opencode")]
},
},
{
name: "codex",
detectPaths: (home) => [path.join(home, ".codex")],
},
{
name: "pi",
detectPaths: (home) => [path.join(home, ".pi")],
},
{
name: "droid",
detectPaths: (home) => [path.join(home, ".factory")],
},
{
name: "copilot",
detectPaths: (home, cwd) => [
path.join(home, ".copilot"),
path.join(cwd, ".github", "skills"),
path.join(cwd, ".github", "agents"),
path.join(cwd, ".github", "copilot-instructions.md"),
],
},
{
name: "gemini",
detectPaths: (home, cwd) => [
path.join(cwd, ".gemini"),
path.join(home, ".gemini"),
],
},
{
name: "kiro",
detectPaths: (home, cwd) => [
path.join(home, ".kiro"),
path.join(cwd, ".kiro"),
],
},
{
name: "qwen",
detectPaths: (home, cwd) => [
path.join(home, ".qwen"),
path.join(cwd, ".qwen"),
],
},
]
export async function detectInstalledTools(
home: string = os.homedir(),
cwd: string = process.cwd(),
): Promise<DetectedTool[]> {
const results: DetectedTool[] = []
for (const target of detectableTools) {
let detected = false
let reason = "not found"
for (const p of target.detectPaths(home, cwd)) {
if (await pathExists(p)) {
detected = true
reason = `found ${p}`
break
}
}
results.push({ name: target.name, detected, reason })
}
return results
}
export async function getDetectedTargetNames(
home: string = os.homedir(),
cwd: string = process.cwd(),
): Promise<string[]> {
const tools = await detectInstalledTools(home, cwd)
return tools.filter((t) => t.detected).map((t) => t.name)
}