Local-only inventory of what your AI coding tools can access.
Landing page: ankui.vercel.app
AI coding tools accumulate configuration, skills, rules, and MCP servers across your filesystem, and it is easy to lose track of what each one can access. Ankui is a local-first scanner and terminal UI that inventories those resources and surfaces access findings. It never executes user code, follows remote URLs, or sends scan data anywhere. Scans are read-only. Writes happen only after an explicit user action: managing Ankui-owned snapshots/bundles/settings, or applying a confirmed reversible skill enable/disable action. Supported tools: Claude, Codex, Cursor, Gemini, OpenCode, Antigravity, and skills.sh.
This public open-source repository contains the local, single-device, local-first Ankui scanner/UI. It does not include a hosted control plane, centralized org inventory, endpoint fleet management, remote policy management, centralized audit/evidence retention, SSO, SIEM integrations, or runtime enforcement.
See SECURITY.md, CONTRIBUTING.md, and BRAND.md for project policy details.
npx -y ankui@latestFor a persistent install:
npm install -g ankui
ankuiRequires Node >= 20.
Running ankui with no arguments opens the interactive terminal UI. Running ankui scan prints a summary to stdout:
Ankui scan complete
Detected tools: 6
MCP servers: 8 configured, 6 unique
Agent skills: 178
Commands/prompts/agents/rules/tools: 19
Memory files: 0
Access findings: 14
Warnings: 1
Tools:
✓ Claude 1 MCP · 49 agent skills · 3 rules · 7 plugins · 3 findings
✓ Codex 3 MCP · 58 agent skills · 1 rules · 5 findings
✓ Cursor 1 MCP · 1 findings
✓ Gemini 1 MCP · 47 agent skills · 4 plugins · 3 findings
✓ OpenCode detected
✓ Antigravity 2 MCP · 24 agent skills · 4 plugins · 2 findings
- skills.sh not detected
Add --json to get the full sanitized scan result as JSON:
ankui --json | jq '[.tools[] | select(.id == "claude") | .skills[]] | length'.tools is an array of { id, skills, findings, ... } objects, not an object keyed by tool id.
Each item Ankui reports falls into exactly one kind, and the buckets do not overlap. The distinction worth knowing up front:
agent_skillcounts your first-partySKILL.mddirectories — the ones living directly under~/.{tool}/skills/(and per-project.{tool}/skills/). One row per skill directory.pluginscounts the plugin/extension entries that are enabled — Claude plugins listed insettings.json, Gemini extensions under~/.gemini/extensions/, Antigravity plugins under~/.gemini/antigravity-cli/plugins/. Each plugin appears as exactly one row, regardless of how manySKILL.mdfiles it bundles inside. Ankui treats a plugin as a single trust boundary — the decision you made was to enable the plugin; the skills it ships with are the plugin author's choice and belong under that one entry.
So a per-tool line like 49 agent skills · 7 plugins for Claude means 49 first-party skills under ~/.claude/skills/ plus 7 enabled plugins. The SKILL.md files inside those 7 plugins (often several hundred on a heavily-pluginned machine) are intentionally not added to the 49 — they live under each plugin entry. The same applies to Gemini extensions and Antigravity plugins.
The other kinds (mcp_server, custom_agents, custom_commands, custom_prompts, rules, memory_file) each map one-to-one to the files or config entries that define them.
From the Actions tab — in the TUI or the ankui web browser UI — you can turn individual markdown-backed skills off or back on. Changes are staged in the UI and only written when you save them — nothing touches disk until then.
↑/↓selects a skill.[d]stages a disable,[e]stages an enable. The row marker flips immediately (●enabled /○disabled) like a checkbox, and every staged change is listed under Pending (unsaved) in the right panel. Toggling a skill back to its on-disk state removes its pending entry.[s]saves. Each pending change moves the skill directory between<skills>/<name>/and<skills>/.disabled/<name>/. Saved skills move to Saved this session; any that fail stay pending with the reason shown. The move is a single directory rename and is refused if it would escape$HOME/$CWD, if the source is missing, or if it would overwrite an existing directory. Nothing is read, modified, or deleted.- Quitting with unsaved changes prompts a confirm:
[s]save first,[q]discard & quit,[esc]cancel. On exit, Ankui prints a net summary of what was actually saved this session — toggles that cancel out, or staged changes you discarded, are omitted because nothing changed on disk. - Disabled skills still appear in scans (marked disabled), so the inventory stays honest.
Only agent_skill and skills.sh skills (a directory with SKILL.md) at user scope are eligible. MCP servers, config entries, and built-in defaults are never written.
The Settings tab — in the TUI (bottom row, alongside Actions) and the ankui web browser UI — manages the dev-root list in ~/.config/ankui/config.json that powers ankui scan-all and the per-project rows on every other tab. Add a new root by typing its path; remove one with [d] in the TUI (or the row's delete control in web). The panel also shows last scan · YYYY-MM-DD HH:MM · N skills. Changes are written atomically via POST /api/config (web) or directly to the config file (TUI); the next scan picks up the new roots immediately. The web endpoint uses optimistic concurrency — if the on-disk config has drifted (e.g. a CLI run rewrote it), the request gets a 409 with the fresh scan and the UI re-applies your edit on top of it.
Ankui sends no data anywhere and calls no external APIs.
Scans are read-only. Ankui writes only after an explicit user action: its own settings, tracked bundle state, and semantic snapshots live under ~/.config/ankui/ or ~/.ankui/; confirmed Actions-tab changes move an eligible skill directory into or out of a sibling .disabled/ folder. Snapshot files never contain raw previews or secret values. Skill moves remain reversible, never overwrite, and are refused if they would escape $HOME/$CWD, clobber an existing directory, or act on a missing source.
The ankui web server is loopback-only. It binds 127.0.0.1, generates a fresh per-session token, and rejects any /api/* request without it; write requests additionally require an Origin header matching the server's own origin, and every request must carry a Host header pointing at a loopback alias of the bound origin (any mismatch is rejected with 421 Misdirected Request, which defeats DNS-rebinding attacks). It serves only local files and calls no external APIs.
Sensitive files skipped. Any file matching these patterns is skipped with a sensitive_file_skipped warning rather than read:
.envand any file starting with.env- Files containing
token,secret, orcredentialin the name - Files starting with
auth,cookies, orsession - Files ending with
.pemor.key - Files containing
apikeyorapi_key - Files starting with
private_key
Sensitive directories skipped. Any path containing these directory segments is never entered:
- Common across all tools:
sessions,session,history,histories,conversation,conversations - Additional for OpenCode paths:
auth,log,logs,share,cache,database,databases,db,runtime
For Antigravity specifically, the adapter opens only known files (settings.json, per-plugin plugin.json / mcp_config.json, SKILL.md files inside skills/ directories, mcp_config.json in the legacy location). The sensitive sibling files inside ~/.gemini/antigravity-cli/ — conversations/, brain/, implicit/, knowledge/, log/, cache/, bin/, updater/, cli.log, history.jsonl — are never enumerated or read.
File size cap. Files larger than 1 MB (MAX_SAFE_FILE_BYTES = 1024 * 1024) are skipped with a file_too_large warning.
Symlinks. Symlinks whose resolved targets fall inside $HOME or $CWD are followed and reported with details.linked: true and details.linkTarget. Symlinks pointing outside those roots, or whose resolved target hits a sensitive path segment, produce a symlink_skipped warning and are not read.
Secret masking. MCP server env blocks have all values replaced with ....... Any value under a key matching auth, authorization, token, secret, credential, password, passwd, apikey, private_key, access_token, refresh_token, auth_token, or client_secret is masked before the result is returned. Credential URLs (Basic auth username/password) are masked in MCP command args.
Session, history, auth, log, and database files are never read. The directory exclusions above cover OpenCode's runtime database, session store, share store, auth store, and log directories. No conversation data or model response history is ever accessed.
| Tool | User-scope paths | Project-scope paths |
|---|---|---|
| Claude | ~/.claude/ (settings, skills, commands, agents), ~/.claude.json |
.claude/ (settings, skills, commands, agents), CLAUDE.md, CLAUDE.local.md, .mcp.json |
| Codex | ~/.codex/config.toml (MCP servers), ~/.codex/prompts/ (custom prompts), ~/.codex/skills/ (agent skills), ~/.codex/rules/ (permission rules) |
.codex/config.toml, .codex/prompts/, .codex/skills/, AGENTS.md |
| Cursor | ~/.cursor/mcp.json (MCP servers), ~/.cursor/rules/ (.mdc rules files) |
.cursor/mcp.json, .cursor/rules/, .mcp.json, .cursorrules |
| Gemini | ~/.gemini/settings.json (MCP servers), ~/.gemini/commands/ (custom commands), ~/.gemini/skills/ (agent skills), ~/.gemini/extensions/ (extensions) |
.gemini/commands/, .gemini/skills/, GEMINI.md |
| OpenCode | ~/.config/opencode/ (agents, commands, tools, skills) |
opencode.json, opencode.jsonc (MCP servers, plugins, tool permissions), .opencode/ (agents, commands, tools, skills), AGENTS.md |
| Antigravity | ~/.antigravity/skills/ (IDE skills), ~/.gemini/antigravity-cli/settings.json (MCP servers), ~/.gemini/antigravity-cli/plugins/<name>/{plugin.json, mcp_config.json, skills/}, ~/.gemini/antigravity/mcp_config.json (legacy) |
AGENTS.md |
| skills.sh | ~/.skills/, ~/.config/skills/ |
.skills/ |
Each tool adapter reads only from the paths listed above. All access goes through the safety layer described in the previous section.
Global flags that apply to all commands:
--json— print the full sanitized result as JSON instead of human-readable output.--no-color— disable ANSI colour codes (useful in CI or when piping output).
Run a local scan and print a summary.
Usage: ankui scan [options]
Run a local scan and print a summary.
Options:
--show-builtins include CLI-bundled built-in skill defaults
--show-builtins injects the opt-in built-in skill registry — the CLI-bundled defaults that ship inside Claude, Codex, and Gemini. Without the flag, no built-in records appear; this flag exists only on ankui scan. Otherwise the command reads user-scope and project-scope paths for all six tools and prints the counts shown in the Quick start section above.
Open the interactive terminal UI.
Usage: ankui tui [options]
Open the interactive terminal UI.
ankui with no arguments is equivalent to ankui tui. On first launch (before ~/.config/ankui/config.json exists), the first-run wizard runs to let you pick dev roots. After that, the full multi-project TUI opens. See the TUI keybindings section below.
Open the TUI and live-rescan when config files change.
Usage: ankui watch [options]
Open the TUI and live-rescan when config files change.
Uses chokidar to watch known tool directories and AI-project files under registered dev roots. File changes are debounced (300 ms stabilisation threshold). Sensitive directories are excluded from the watch list. Press q or Ctrl-C to quit.
Open a local browser UI.
Usage: ankui web [options]
Open a local browser UI.
Options:
--port <port> preferred port (default: 7373)
--no-open do not open the browser automatically
Starts a loopback HTTP server bound to 127.0.0.1 and opens your default browser at it. The single-page app has eight tabs — Overview, Changes, Tools, MCPs, Access, Doctor, Actions, Settings. Changes manages local semantic snapshots; Actions applies skill enable/disable through the same staged-changes + save flow as the TUI. --port sets the preferred port; if it is taken the server tries the next free port up to +20. --no-open skips launching the browser. Runs until Ctrl-C.
$ ankui web
Ankui web UI http://127.0.0.1:7373
local files only · scans are read-only · saved skill toggles are reversible · Ctrl+C to stop
The server binds 127.0.0.1 only and generates a fresh per-session token. Every /api/* request must carry that token in an x-ankui-token header, and write requests (POST /api/actions, POST /api/config) additionally require an Origin header matching the server's own origin — this defeats localhost CSRF from other browser tabs. A DNS-rebinding guard rejects any request whose Host header isn't a loopback alias of the bound origin (421 Misdirected Request). It serves only local files; nothing is sent anywhere.
Print findings and review recommendations from the scan.
Usage: ankui access [options]
Print findings and review recommendations from the scan.
Findings are grouped by category and ordered by severity: broad_access_capability (broad-access MCP servers), dangerous_pattern (skills containing curl | sh, rm -rf, or similar patterns), secret_reference (MCP servers with secret-bearing env keys), unknown_capability (MCP servers not in the built-in catalog), and duplicate_mcp (the same MCP server configured across multiple tools).
Every finding also carries a high, medium, or low severity so Access views can put the most review-worthy items first. High covers dangerous command patterns and broad-access MCPs; medium covers secret references and uncatalogued MCPs; low covers duplicate MCP configurations.
Example output structure:
Ankui Access Review
───────────────────
Findings 10
Severity 6 high · 2 medium · 2 low
Mix 6 dangerous_pattern · 2 duplicate_mcp · 1 secret_reference · 1 unknown_capability
Duplicate MCP servers (2)
─────────────────────────
○ [LOW] Reddit MCP is configured in 2 tools
Scope: cross_tool · Tools: codex, gemini
...
Print a cross-tool MCP server overview.
Usage: ankui mcp [options]
Print a cross-tool MCP server overview.
Groups all configured MCP servers by canonical name. Shows capability category, access level, which tools have the server configured, and whether any configuration carries secret-bearing env keys. Cross-tool duplicates are flagged with a warning line.
Print MCP capability categories overview.
Usage: ankui caps [options]
Print MCP capability categories overview.
Lists classified MCP servers grouped by capability category (network, communication, filesystem, etc.) in descending order of count, then prints a footer with the count of markdown-backed skills that are not categorised in this view. Unknown MCP servers (not in the built-in catalog) are not included in the category groups.
Print detection status and scanner warnings.
Usage: ankui doctor [options]
Print detection status and scanner warnings.
Shows which tools were detected and which paths each tool found at user scope and project scope. After the tool list, warnings are grouped by reason (sensitive_file_skipped, symlink_skipped, file_too_large, permission_denied, parse_error, timeout). A clean machine ends with No warnings.
Run scans across every project in every registered dev root.
Usage: ankui scan-all [options]
Run scans across every project in every registered dev root.
Reads dev roots from ~/.config/ankui/config.json (written by ankui discover --apply or the first-run TUI wizard). Scans up to 10 projects in parallel with a 5-second per-project timeout. Output shows user-scope skill counts followed by a per-project table.
Example:
Ankui multi-project scan — 16 projects across 3 dev roots, 178 user-scope skills
User scope
──────────
✓ Claude 63 skills
✓ Codex 62 skills
...
Projects (16)
─────────────
~/Developer/Ceto's Projects/ankui 1 skills · 0 findings
...
Capture and compare local semantic inventory baselines.
ankui snapshot --label "before tool upgrade"
ankui snapshot list
ankui diff
ankui diff --from <snapshot-id> --to <snapshot-id-or-current>
ankui snapshot delete <snapshot-id> --yesSnapshots cover user scope and every project under registered dev roots. They are explicit — normal scans, watch mode, and UI refreshes never move the baseline. ankui diff defaults to the latest snapshot versus a fresh current scan and reports added, removed, and field-level modified tools, skills, MCPs, findings, and warnings. IDs may be supplied in full or as an unambiguous prefix.
Snapshot files live under ~/.ankui/snapshots/; only the newest 30 valid snapshots are retained. They store normalized semantic fields and masked MCP configuration details, but never raw skill previews, raw file contents, or secret values. Adapter timeouts and other transient warnings are reported separately as scan-health changes and do not count as configuration drift. Pass global --json for machine-readable metadata or diff output.
The TUI and ankui web Changes views expose the same baseline picker and semantic diff. TUI shortcuts are Tab to switch From/To, ↑/↓ to select, [n] to capture, and [d] followed by [y] to delete.
Export a shareable sanitized Markdown report.
Usage: ankui report [options]
Export a shareable sanitized Markdown report.
Options:
--output <file> write the report to a new file instead of stdout
The report uses the same multi-project scan path as ankui scan-all, then renders an executive Markdown summary with tool counts, MCP counts, finding severity totals, access findings, grouped warnings, and recommendations. It is designed for sharing: absolute paths, home/cwd/dev-root/project names, raw skill previews/details, and secret-like environment variable names are omitted or replaced with stable placeholders such as <HOME>, <PROJECT_1>, and <PATH_1>.
Pass global --json to emit the sanitized report model instead of raw scan JSON:
ankui --json report
ankui report --output ankui-report.mdThe ankui web Overview screen also includes an Export report button that downloads the same Markdown format from the currently loaded scan.
List skills, optionally filtered by --kind and --tool.
Usage: ankui list [options]
List skills, optionally filtered by --kind and --tool.
Options:
--kind <kind> filter by skill kind (e.g., mcp_server, agent_skill)
--tool <tool> filter by tool id (e.g., claude, codex)
Valid skill kinds include mcp_server, agent_skill, custom_agents, custom_commands, custom_prompts, custom_tools, memory_file, rules, plugins, skills_sh_skill. Valid tool ids are claude, codex, cursor, gemini, opencode, antigravity, skills-sh. An invalid filter value exits with code 1 and a usage message.
Print one tool's detected paths and skills.
Usage: ankui show [options] <tool>
Print one tool's detected paths and skills.
Prints detected paths grouped by scope (user vs project), then all skills grouped by kind in canonical order. An unknown tool id exits with code 1 and lists the valid ids.
Example:
Ankui — claude
Detected at:
user:
~/.claude
~/.claude.json
project:
./.claude
./.claude/settings.local.json
mcp_server (1)
──────────────
expo-mcp ~/.claude.json
agent_skill (49)
────────────────
autoplan ~/.claude/skills/autoplan/SKILL.md
...
Crawl ~ for AI projects and propose dev roots for ~/.config/ankui/config.json.
Usage: ankui discover [options]
Crawl ~ for AI projects and propose dev roots for ~/.config/ankui/config.json.
Options:
--apply write the default-ON dev roots into the config file (default: false)
Crawls your home directory (max depth 6, concurrency 16) for directories that contain AI-tool marker files or directories. Groups found projects by parent directory into dev-root candidates. Parents with 3 or more AI projects are marked default-ON. Prints a dry-run summary by default; pass --apply to merge the selected roots into ~/.config/ankui/config.json. A second --apply with the same roots is a no-op.
The sidebar has a Tools section (Overview plus one entry per tool) and a Views section containing MCPs · Access · Doctor · Actions · Bundles · Settings · Changes. Use ↑ / ↓ while the sidebar is focused, then → or Enter to focus the selected panel.
| Key | Action |
|---|---|
← / → |
Move between sidebar and active panel |
↑ / ↓ |
Scroll the skill list in a drill-in screen |
Enter |
Drill into the selected item |
Esc / Backspace |
Drill out (return to the previous screen) |
/ |
Open incremental search (drill-in screens only) |
[d] / [e] |
Stage a disable / enable for the selected skill — UI only until [s] (Actions tab) |
[s] |
Save staged skill changes to disk (Actions tab) |
Tab |
Switch From/To selector (Changes tab) |
[n] / [d] |
Create / request deletion of a snapshot (Changes tab) |
r |
Rescan (refresh data from disk) |
q |
Quit (if there are unsaved staged changes, prompts [s] save / [q] discard / [esc] cancel; prints the net saved-this-session summary) |
Tab is scoped to the Changes panel and does not cycle application tabs.
npm install
npm run typecheck # TypeScript strict-mode check (no emit)
npm test # runs the node:test + tsx suite
npm run build # two-stage build (see below)
node dist/cli.js scan # smoke test against your real local configTests use the built-in node:test runner via tsx. Do not add Jest or Vitest unless explicitly required.
npm run build runs two stages: build:cli (tsc — the Node CLI, emitted to dist/) then build:web (Vite — the ankui web single-page app, emitted to dist/web-ui/). For front-end work, npm run dev:web runs the Vite dev server with hot reload.
After a build, use node dist/cli.js <command> for local testing. All commands support --json for machine-readable output.
Adapters live in src/scanner/adapters/. Each adapter exports a ScannerAdapter object with a toolId and an async scan(context) method.
Invariants for any new adapter:
- Use
safeReadOptions(filePath, context)fromshared.tsfor every file read. This enforces the symlink allowlist and file size cap consistently across all adapters. - Never throw. Wrap all I/O in try/catch or rely on the safe helpers; produce
Warningobjects for every failure and attach them to the adapter result. The adapter runner isolates each adapter with a 1-second timeout, but adapters should still handle their own errors gracefully. - Respect the 1 MB file size cap (
MAX_SAFE_FILE_BYTESfromsafety.ts) and the 1-second per-adapter time budget. - For markdown-backed skill paths (skills with
SKILL.mdor similar), callawait buildLinkDetails(filePath, context)fromshared.tsand spread the result into thedetailsobject. This ensures symlink metadata (linked,linkTarget) is recorded on every markdown skill. - Add the adapter to
src/scanner/adapters/index.tsso the scanner runner picks it up.
The scanner invariant is: every file or directory access goes through src/scanner/safety.ts. No adapter should call fs.readFile or fs.readdir directly.
MIT