Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ See [SCAN_COVERAGE.md](SCAN_COVERAGE.md) for the full catalog of supported detec
| Category | Examples |
| -------------------- | ---------------------------------------------------------------------------------------- |
| IDEs & Desktop Apps | VS Code, Cursor, Windsurf, Antigravity, Zed, Claude, Copilot, JetBrains suite (13 IDEs), Eclipse, Android Studio |
| AI CLI Tools | Claude Code, Codex, Gemini CLI, Kiro, GitHub Copilot CLI, Aider, OpenCode, Cursor Agent, Pi, Factory Droid, Amp |
| AI CLI Tools | Claude Code, Codex, Gemini CLI, Kiro, GitHub Copilot CLI, Aider, OpenCode, Cursor Agent, Pi, Factory Droid, Amp, Grok Build, Kimi Code, Muse Code, Hermes Agent, Oh My Pi |
| AI Agents | Claude Cowork, OpenClaw, ClawdBot, GPT-Engineer |
| AI Frameworks | Ollama, LM Studio, LocalAI, Text Generation WebUI |
| MCP Server Configs | Claude Desktop, Claude Code, Cursor, Windsurf, Antigravity, Zed, Open Interpreter, Codex, OpenCode |
Expand Down
9 changes: 7 additions & 2 deletions SCAN_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ Detection is cross-platform — binaries are located via `$PATH` lookup and home
| Pi | Earendil | `pi` | `~/.pi/agent` |
| Factory Droid | Factory | `droid` | `~/.factory` |
| Amp | Sourcegraph| `amp` | `~/.config/amp` |
| Grok Build | xAI | `grok` | `~/.grok` |
| Kimi Code | Moonshot | `kimi` | `~/.kimi-code` |
| Muse Code | Meta | `muse` | `~/.config/muse` |
| Hermes Agent | Nous Research | `hermes` | `~/AppData/Local/hermes`, `~/.hermes` |
| Oh My Pi | Stencil | `omp` | `~/.omp/agent` |

† `gh copilot` launches this same `@github/copilot` CLI, downloading it into gh's own data directory when it isn't already on `$PATH` — so that install never lands on `$PATH`. After the two binary names miss, Copilot is also looked for at `~/.local/share/gh/copilot/copilot`, `~/.local/bin/copilot`, `~/AppData/Local/GitHub CLI/copilot/copilot`, `~/AppData/Local/Microsoft/WinGet/Links/copilot.exe`, `~/AppData/Roaming/npm/copilot.cmd`, and the `gh-copilot` extension directory under both `~/.local/share/gh/extensions` and `~/AppData/Local/GitHub CLI/extensions`. A non-default `$XDG_DATA_HOME` is not followed, and WinGet's hashed `Packages\GitHub.Copilot_<hash>\` payload directory is not globbed — only its `Links` shim.

Pi, Factory Droid and Amp share their binary names with unrelated popular tools, so a `$PATH` hit alone does not report them — each is confirmed from an on-disk artifact (a package manifest, an installer anchor directory, a Homebrew cask root, a winget or pacman package entry), and is searched for in the common global-install prefixes as well as on `$PATH`. Pi and Amp are never executed because macOS Gatekeeper prompts on their binaries; their versions come from disk or are reported as `unknown`.
Pi, Factory Droid, Amp, Grok Build, Kimi Code, Muse Code, Hermes Agent and Oh My Pi share their binary names with unrelated popular tools, so a `$PATH` hit alone does not report them — each is confirmed from an on-disk artifact (a package manifest, an installer anchor directory plus a corroborating sidecar, virtualenv or size floor, a Homebrew Cellar or cask root, a winget or pacman package entry), and is searched for in the common global-install prefixes (including mise's Oh My Pi install tree) as well as on `$PATH`. Of these eight, all but Factory Droid are never executed macOS Gatekeeper prompts on their binaries — so their versions come from disk (a manifest, a versioned filename, a Python `dist-info` directory name, a Homebrew version segment) or are reported as `unknown`. Every winget-installed one reports `unknown`. No agent's config, auth, session or log files are read.

## General-Purpose AI Agents

Expand Down Expand Up @@ -99,7 +104,7 @@ On Windows, `~` refers to the user's home directory (`%USERPROFILE%`). Claude De

## AI Agent Skills

Dev Machine Guard inventories every installed **agent skill** — a directory containing a `SKILL.md` manifest — across Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot, Pi, Factory, Amp, the cross-agent `~/.agents` convention, and skills installed via [skills.sh](https://skills.sh). It probes each agent's global, system, project, and plugin skill directories; skills.sh lock files add upstream provenance (joined by symlink-resolved path). Detection is pure filesystem reads (no subprocesses), bounded by a 60-second budget and per-root caps.
Dev Machine Guard inventories every installed **agent skill** — a directory containing a `SKILL.md` manifest — across Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot, Pi, Factory, Amp, Grok Build (`~/.grok/skills`, `.grok/skills`), Kimi Code (`~/.kimi-code/skills`, `.kimi-code/skills`), Muse Code (`~/.config/muse/skills`), Hermes Agent (`~/.hermes/skills` or `%LOCALAPPDATA%\hermes\skills`, `.hermes/skills`), Oh My Pi (`~/.omp/agent/skills`, `~/.omp/agent/managed-skills`, `.omp/skills`), the cross-agent `~/.agents` convention, and skills installed via [skills.sh](https://skills.sh). It probes each agent's global, system, project, and plugin skill directories; skills.sh lock files add upstream provenance (joined by symlink-resolved path). Detection is pure filesystem reads (no subprocesses), bounded by a 60-second budget and per-root caps.

**Privacy: only metadata and a single SHA-256 hash of each `SKILL.md` are collected — no other file is ever read, and file contents are never transmitted.** The file census (counts, sizes, timestamps) comes entirely from directory listings and `stat`. For skills installed from a local path, the on-disk source path is never serialized — only the skill's alias.

Expand Down
77 changes: 76 additions & 1 deletion internal/detector/aicli.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,76 @@ var cliToolDefinitions = []cliToolSpec{
ResolveFunc: resolveAmp,
StaticVersionOnly: true,
},
{
Name: "grok-build",
Vendor: "xAI",
// Anchor first so binary_path reports the installer's own link even when
// ~/.local/bin/grok or an npm prefix also resolves to it. `agent` is a
// second launcher name Grok installs; it is generic and never searched.
Binaries: []string{
"~/.grok/bin/grok", "~/.grok/bin/grok.exe",
"grok", "~/.local/bin/grok",
"~/AppData/Roaming/npm/grok.cmd",
"~/AppData/Local/Microsoft/WinGet/Links/grok.exe",
},
ConfigDirs: []string{"~/.grok"},
ResolveFunc: resolveGrok,
StaticVersionOnly: true,
},
{
Name: "kimi-code",
Vendor: "Moonshot",
Binaries: []string{
"~/.kimi-code/bin/kimi", "~/.kimi-code/bin/kimi.exe",
"kimi", "~/.local/bin/kimi",
"/opt/homebrew/opt/kimi-code/bin/kimi", "/usr/local/opt/kimi-code/bin/kimi",
"/home/linuxbrew/.linuxbrew/opt/kimi-code/bin/kimi",
"~/AppData/Roaming/npm/kimi.cmd",
"~/AppData/Local/Microsoft/WinGet/Links/kimi.exe",
},
ConfigDirs: []string{"~/.kimi-code"},
ResolveFunc: resolveKimi,
StaticVersionOnly: true,
},
{
Name: "muse-code",
Vendor: "Meta",
Binaries: []string{
"~/.local/bin/muse", "muse",
"/opt/homebrew/opt/muse-code/bin/muse", // not created by the cask; harmless
},
ConfigDirs: []string{"~/.config/muse"},
ResolveFunc: resolveMuse,
StaticVersionOnly: true,
},
{
Name: "hermes-agent",
Vendor: "Nous Research",
Binaries: []string{
"~/.local/bin/hermes", "/usr/local/bin/hermes", "hermes",
"/opt/homebrew/opt/hermes-agent/bin/hermes", "/usr/local/opt/hermes-agent/bin/hermes",
"/home/linuxbrew/.linuxbrew/opt/hermes-agent/bin/hermes",
"~/AppData/Local/hermes/bin/hermes.exe", "~/AppData/Local/hermes/bin/hermes.cmd",
},
ConfigDirs: []string{"~/AppData/Local/hermes", "~/.hermes"},
ResolveFunc: resolveHermes,
StaticVersionOnly: true,
},
{
Name: "oh-my-pi",
Vendor: "Stencil",
Binaries: []string{
"omp", "~/.local/bin/omp", "~/.bun/bin/omp", "~/.bun/bin/omp.exe",
"/opt/homebrew/opt/omp/bin/omp", "/usr/local/opt/omp/bin/omp",
"/home/linuxbrew/.linuxbrew/opt/omp/bin/omp",
"~/AppData/Local/omp/omp.exe",
"~/AppData/Roaming/npm/omp.cmd",
"~/AppData/Local/Microsoft/WinGet/Links/omp.exe",
},
ConfigDirs: []string{"~/.omp/agent"},
ResolveFunc: resolveOMP,
StaticVersionOnly: true,
},
}

// AICLIDetector detects AI CLI tools.
Expand Down Expand Up @@ -468,7 +538,8 @@ func resolveEnvPath(exec executor.Executor, path string) string {
}

// ---------------------------------------------------------------------------
// Shared helpers for the ResolveFunc ladders (pi, factory, amp).
// Shared helpers for the ResolveFunc ladders (pi, factory, amp here; the
// wave-2 agents in aicli_agents2.go).
//
// Every path manipulation below is separator-agnostic, and that is a
// correctness requirement rather than tidiness: these ladders run against a
Expand Down Expand Up @@ -750,6 +821,10 @@ func aiCLIBinaryCandidateDirs(exec executor.Executor, homeDir string) []string {
// all that manager needs. The rest are real install trees, globbed.
dirs = append(dirs, globDirs(exec, home(".local", "share", "fnm", "node-versions", "*", "installation", "bin"))...)
dirs = append(dirs, globDirs(exec, home(".local", "share", "mise", "installs", "node", "*", "bin"))...)
// mise's github backend keeps each Oh My Pi release in its own version
// dir with the binary at the root; the shim on PATH resolves to the
// mise binary itself, so this is the only way that channel is seen.
dirs = append(dirs, globDirs(exec, home(".local", "share", "mise", "installs", "github-can1357-oh-my-pi", "*"))...)
dirs = append(dirs, globDirs(exec, home(".volta", "tools", "image", "packages", "*", "bin"))...)
dirs = append(dirs, globDirs(exec, home(".volta", "tools", "image", "packages", "*", "*", "bin"))...)
dirs = append(dirs, globDirs(exec, home(".asdf", "installs", "nodejs", "*", "bin"))...)
Expand Down
Loading
Loading