# Tool Map

> Cross-platform inventory of CLI tools, scripts, and MCP servers installed on the user's machine. Use when the user asks what is installed, where a tool lives, or how to run something - read the cached summary first instead of re-walking the filesystem. Refresh the catalog with the bundled scan.mjs only when the user asks, just installed a tool, or the cached summary is missing a tool the user mentions.

- Skill: `minimax-ai/tool-map` (Agent Skill)
- Install (CLI): `npx skillmds@latest add minimax-ai/tool-map`
- Raw SKILL.md: https://api.skillmd.com/api/skills/minimax-ai/tool-map/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: MiniMax AI (https://skillmd.com/u/minimax-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/minimax-ai/tool-map

---


# tool-map

This Plugin generates and refreshes a persistent inventory of the executable tools on the user's machine. The agent should consult the cached summary first and only re-scan when the user explicitly asks, when a tool the user mentions is not in the summary, or when the user has just installed or upgraded something.

## Where the inventory lives

The catalog is written to the Plugin data directory, exposed to the agent as `${PLUGIN_DATA}`. Three files are always written together:

- `${PLUGIN_DATA}/tools.summary.md` - lightweight (~6 KB) one-pager; **read this on session start** to learn what is installed without re-discovering the filesystem.
- `${PLUGIN_DATA}/tools.md` - full markdown inventory grouped by category, with size, mtime, and absolute paths.
- `${PLUGIN_DATA}/tools.json` - machine-readable JSON (same content as `tools.md`, structured); use this when you need to filter or query tools programmatically.

If `${PLUGIN_DATA}/tools.summary.md` does not exist on the first read in a session, run the scanner once to create all three files (see "How to refresh" below). On every subsequent turn, trust the summary; do not re-walk the filesystem and do not re-probe `--version` for tools already listed.

## How to refresh

To regenerate the inventory, run the bundled scanner:

```bash
node "${PLUGIN_ROOT}/scripts/scan.mjs"
```

The scanner walks known tool roots and the user's `$PATH`, probes a fixed list of well-known CLIs for `--version` (5 s timeout each, never throws), and writes all three files with bundle-level atomicity (two-phase commit: backup previous targets → write to staging → atomic rename per file → restore on failure). The scan is read-only and never modifies anything outside `${PLUGIN_DATA}`. Typical run: under 2 s on a developer workstation.

You may pass an optional output path to redirect the catalog (useful for testing):

```bash
node "${PLUGIN_ROOT}/scripts/scan.mjs" /tmp/my-inventory.md
```

When redirected, the scanner derives `tools.json` and `tools.summary.md` from the given path's stem (replace `.md` with `.json` and `.summary.md`).

## When to re-scan

Re-run the scanner when **any** of these is true:

- The user explicitly asks "what is installed?", "refresh the inventory", or "re-scan tools".
- The user just installed or upgraded a tool, and the next request involves that tool.
- The user mentions a tool that is not in the summary.
- A tool listed in the summary gives a `command not found` error in this session (the summary may be stale).

In all other cases, trust the summary. Do not re-walk the filesystem, do not re-probe `--version` for tools already listed, and do not re-print the inventory back to the user unless they ask.

## Cross-platform roots

The scanner walks these well-known locations, derived from the user's home directory and environment variables (no hardcoded absolute paths in source code):

- **Windows**: `%ProgramFiles%`, `%ProgramFiles(x86)%`, `%APPDATA%\npm`, `%LOCALAPPDATA%\Microsoft\WindowsApps`, and the user's `~/.minimax-code`, `~/.minimax`, `~/.npm-global/bin`, `~/pwsh7_6`, `~/.Codex`, `~/.claude`.
- **macOS / Linux**: `~/.minimax-code`, `~/.minimax`, `~/.local/bin`, `~/.local/share/npm/bin`, `/usr/local/bin`, `/opt/homebrew/bin`, `~/.Codex`, `~/.claude`.

Plus everything on the user's `$PATH`. To add an extra root, set the `TOOL_MAP_ROOTS` environment variable to a `:`-separated (POSIX) or `;`-separated (Windows) list of absolute paths; each is walked with the same rules as the built-in roots.

## What the scanner reads and writes

- **Reads**: filesystem metadata (size, mtime, mode) for executables under known roots and `$PATH`; the first line of stdout for `tool --version` for a fixed list of 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell); `~/.gitconfig` for user/email; the list of filenames under `~/.ssh/` (NOT the key contents, NOT any other directory).
- **Writes**: `${PLUGIN_DATA}/tools.{md,json,summary.md}` (or the path given as `argv[2]`) only. Bundle-level atomicity: existing targets are backed up, new content is written to a staging directory, then each staging file is renamed onto its target. If any rename fails the previous catalog is restored and the staging/backup directories are removed.
- **Does not read**: the contents of any file under `~/.ssh/`; environment variable values that look like secrets; any registry, browser data, source code, or user documents.
- **Does not write**: any file outside the output directory; any user or host install area; any registry or config under `~/.config/`, `~/.minimax/`, or `~/.openclaw*/`.
- **Does not send**: any network request, any telemetry, any data to any third party. The scanner is fully offline.

## Side effects (subprocess execution)

The scanner's only side effect beyond writing the catalog files is **executing 15 well-known CLI programs** with `--version` (or, for PowerShell, a single read-only `$PSVersionTable.PSVersion.ToString()` call). This is a deliberate, declared behaviour — version strings make the catalog more useful.

- The exact set of executable names is hardcoded as `VERSION_PROBES` in `scripts/scan.mjs` and is mirrored in `ALLOWED_PROBE_NAMES`. Any probe request for a name outside the whitelist is refused inside `probeVersion` (fail-closed).
- Probes are run via `execFile`, not `shell`, on every platform. The program name and its single `--version` argument (or the `-NoProfile -Command $PSVersionTable.PSVersion.ToString()` triple for PowerShell) are passed as a separate argv, so a same-named wrapper on `$PATH` cannot be tricked into executing arbitrary code from shell metacharacters in the path.
- One Windows-only exception: `.cmd` and `.bat` shims are routed through `cmd.exe`. The Node.js 21.7.3 fix for CVE-2024-27980 refuses to spawn batch files via `execFile` without `shell: true`; this Plugin requires Node >= 22 so the fix is in force. The shell decision is per-program: the scanner walks `$PATH` and `$PATHEXT` to find the actual file the OS would execute, and sets `shell: true` only for programs whose resolved path ends in `.cmd` or `.bat`. Native `.exe` binaries (including `powershell.exe`) are spawned directly. POSIX always uses no shell.
- Every probe has a hard 5 s `execFile` timeout; timeouts, ENOENT, and non-zero exits are all swallowed. A tool that hangs longer than 5 s is simply omitted from the `core` versions table.
- No user input is ever passed to a probe. The whitelist is the single source of truth for what may run.

Review your `$PATH` and any same-named wrappers in the well-known roots before installing this Plugin if you consider arbitrary command execution a concern.

## Failure modes

- A tool's `cmd --version` hangs - the 5 s timeout aborts the probe; that tool is omitted from the `core` versions table but stays in the file-walk inventory.
- A directory is unreadable (permission denied, broken symlink) - skipped silently; the walk continues.
- Output path is on a different filesystem from the staging location - atomic rename still works because staging lives next to the target file, not in `os.tmpdir()`.
- `${PLUGIN_DATA}` is not set - the scanner falls back to `$XDG_DATA_HOME/tool-map` (or `~/.local/share/tool-map` when the env var is also unset).
- A mid-bundle rename fails (extremely rare: disk full, AV lock) - the previous catalog is restored from backup and the staging/backup directories are removed. The agent sees the same catalog it saw before the failed scan.
- `TOOL_MAP_ROOTS` contains a non-existent path - that path is skipped; the rest of the walk continues.

