scripts/tooling

June 5, 2026 · View on GitHub

Automatically records how AI agent tooling (Yarn scripts, Claude Code skills, Cursor skills) is used into a local CSV log. Developer-only, stored locally, never sent anywhere.

How it works

Every collection path appends one CSV row to a project-scoped log file:

  • start when a skill or Yarn script begins

The log accumulates locally. The dev-tooling-explorer and a nightly cron job drain the log into SQLite for reporting and summarisation.

Skip conditions

Collection is disabled when either of these is true:

ConditionHow to trigger
CI env var is setAutomatic on GitHub Actions and most CI systems
TOOL_USAGE_COLLECTION_OPT_IN=falseSet in your shell profile or .env to opt out locally

All four collection paths (Yarn plugin, Claude hook, Cursor preToolUse hook, Cursor prompt hook) check both conditions. The shell dispatchers exit immediately when either is set — zero filesystem or subprocess overhead. Both Cursor entry scripts emit {"permission":"allow"} before the guard so Cursor never blocks a tool use or prompt submission.

Log file location

ScenarioPath
Default~/.tool-usage-collection/metamask-mobile-events.log
CustomSet TOOL_USAGE_COLLECTION_LOG_PATH to any absolute path

Log format

One CSV row per event, with a header row written on first creation:

tool_name,tool_type,event_type,agent_vendor,session_id,success,duration_ms,created_at

Example rows:

skill:pr-create,skill,start,cursor,abc-123,,,2026-05-12T10:00:00Z
yarn:test:unit,yarn_script,start,,,true,8423,2026-05-12T10:01:00Z

Appends are done with printf … >> file (Yarn plugin) or fs.appendFileSync (Yarn Berry plugin), both of which are atomic for single-line writes on macOS/Linux.

Architecture

flowchart LR
    yarn[Yarn Berry plugin\nwrapScriptExecution]
    claude[Claude Code PreToolUse hook\n.claude/settings.json]
    cursor_pretool[Cursor preToolUse hook\n.cursor/hooks.json]
    cursor_prompt[Cursor beforeSubmitPrompt hook\n.cursor/hooks.json]
    cursor_entry[hook-cursor-dispatch.sh\nprints allow + sources common]
    cursor_prompt_entry[hook-cursor-prompt-dispatch.sh\nprints allow + extracts slash cmd]
    claude_entry[hook-claude-dispatch.sh\nsources common]
    common[hook-common.sh\nCI guard · skill extract · CSV append]
    log[(~/.tool-usage-collection/\nmetamask-mobile-events.log)]
    explorer[dev-tooling-explorer\nor nightly cron]
    db[(SQLite DB\n~/.tool-usage-collection/events.db)]

    yarn -->|appendFileSync CSV line| log
    claude --> claude_entry --> common
    cursor_pretool --> cursor_entry --> common
    cursor_prompt --> cursor_prompt_entry -->|printf CSV line| log
    common -->|printf CSV line| log
    log -->|drain on demand| explorer --> db

Files

FilePurpose
hook-cursor-dispatch.shCursor preToolUse entry point — emits {"permission":"allow"} unconditionally, then sources common
hook-cursor-prompt-dispatch.shCursor beforeSubmitPrompt entry point — emits {"permission":"allow"} unconditionally, extracts skill from slash command prompt, appends CSV
hook-claude-dispatch.shClaude entry point — sources common
hook-common.shShared logic — CI/opt-out guard, skill extraction, CSV append; receives agent name as $1
hook-dispatchers.test.tsJest tests for all entry scripts (runs shell scripts via child_process)

Collection paths

Path 1 — Yarn Berry plugin

.yarn/plugins/plugin-usage-tracking.cjs wraps every yarn <script> via wrapScriptExecution. On start it appends a CSV row; on finish it updates success and duration_ms in a second row.

Path 2 — Claude Code skills

.claude/settings.json registers a project-level PreToolUse hook for the Skill tool pointing to hook-claude-dispatch.sh. That script sources hook-common.sh which extracts the skill name from the "skill" field in the tool input, appends one CSV row, and exits.

Path 3 — Cursor skills (agent reads SKILL.md)

.cursor/hooks.json registers a preToolUse hook pointing to hook-cursor-dispatch.sh. That script emits {"permission":"allow"} as its first output (unconditionally), then sources hook-common.sh which extracts the skill name from the SKILL.md file path in the payload (matches .agents/skills/, .cursor/skills/, or .claude/skills/) and appends one CSV row.

Path 4 — Cursor slash commands

.cursor/hooks.json also registers a beforeSubmitPrompt hook pointing to hook-cursor-prompt-dispatch.sh. When a user invokes a skill via a slash command (e.g. /mms-pr-changelog), Cursor injects the skill content directly into the prompt context without the agent reading a SKILL.md file — so Path 3 never fires. This hook intercepts the raw prompt before submission, extracts the skill name from the "prompt" field (pattern: "/mms-[a-z0-9-]*"), and appends one CSV row. The {"permission":"allow"} response is always emitted first so Cursor never blocks the submission.

Inspecting the log

# Tail live events
tail -f ~/.tool-usage-collection/metamask-mobile-events.log

# Count skill uses by name
awk -F, '{print \$1}' ~/.tool-usage-collection/metamask-mobile-events.log | sort | uniq -c | sort -rn

To see events in the SQLite database populated by the explorer or cron:

sqlite3 ~/.tool-usage-collection/events.db \
  "SELECT tool_name, tool_type, event_type, agent_vendor, created_at FROM events ORDER BY created_at DESC LIMIT 20;"

Using dev-tooling-explorer

dev-tooling-explorer is a local web UI for browsing the SQLite database written by the collection hooks. It lets you filter events by repo, tool, agent vendor, or date; view per-session history; explore usage charts; and prune or reset the database.

Installation

git clone https://github.com/MetaMask/experimental-dev-tooling-explorer
cd experimental-dev-tooling-explorer
yarn install

Usage

yarn start                    # opens local web UI
yarn start -- --port 4242    # fixed port

Demo workflow (no collection setup needed)

yarn demo:generate
yarn start:demo

DB path resolution

The explorer reads the database from TOOL_USAGE_COLLECTION_DB_PATH if set, otherwise defaults to ~/.tool-usage-collection/events.db.