How to run, test, and iterate on this plugin locally without going through the public marketplace flow.
- Node 22+ (uses the built-in test runner; no
npm installstep, zero dependencies). uvxon PATH (for the MCP server).curlon PATH (for the bin/ shim and update check).- A ZeroPath API token (only for end-to-end runs that hit the API).
From any Claude Code session, point the marketplace at the absolute path of your local checkout:
/plugin marketplace add /absolute/path/to/zeropath-agent-plugin
/plugin install zeropath@ZeroPathAI
Claude Code:
- Copies the plugin into
~/.claude/plugins/cache/zeropath-ZeroPathAI/. - Shows a
userConfigform asking for Token ID, Token Secret (masked), and optional Org ID + Custom base URL. - Stores the secret in your system keychain; non-sensitive values go to
~/.claude/settings.json.
Source edits made AFTER this don't propagate until you run:
/plugin marketplace update ZeroPathAI
...followed by a session restart. To change credentials later: open
/plugin -> zeropath -> Configure.
After the first install, replace the cache copy with a symlink:
# Run from inside your local checkout so $(pwd) is the source dir.
PLUGIN_SRC="$(pwd)"
PLUGIN_CACHE=~/.claude/plugins/cache/zeropath-ZeroPathAI
rm -rf "$PLUGIN_CACHE"
ln -s "$PLUGIN_SRC" "$PLUGIN_CACHE"Now every source edit takes effect on the next session restart, no
marketplace update needed. Reverse it (and restore a real copy) before
running /plugin uninstall.
/plugin uninstall zeropath@ZeroPathAI
/plugin marketplace remove ZeroPathAI
node --test tests/smoke.test.mjs90 tests across 12 suites. No network, no real CLI download. Finishes in a few seconds on any modern machine.
To run a single suite:
node --test --test-name-pattern "session-start" tests/smoke.test.mjsEvery hook reads JSON from stdin, writes JSON (or silence) to stdout.
DATA=/tmp/zp-dev && mkdir -p "$DATA/sessions"
# SessionStart: vanilla
node hooks/session-start.mjs < /dev/null
# SessionStart with simulated update-available
cat > "$DATA/update-check.json" <<EOF
{"ok":true,"current":"0.3.0","latest":"0.4.0","updateAvailable":true,"checkedAt":1}
EOF
CLAUDE_PLUGIN_DATA="$DATA" node hooks/session-start.mjs < /dev/null
# PostToolUse: track an edit
echo '{"session_id":"dev","tool_input":{"file_path":"src/foo.js"}}' \
| CLAUDE_PLUGIN_DATA="$DATA" node hooks/post-tool-use.mjs
cat "$DATA/sessions/dev.touched.log"
# PreToolUse: push warning (will fire - dev.touched.log has content)
echo '{"session_id":"dev","tool_input":{"command":"git push origin main"}}' \
| CLAUDE_PLUGIN_DATA="$DATA" node hooks/pre-tool-use.mjs
# PreToolUse: gh pr create variant
echo '{"session_id":"dev","tool_input":{"command":"gh pr create --title foo"}}' \
| CLAUDE_PLUGIN_DATA="$DATA" node hooks/pre-tool-use.mjs
# PreCompact: preserve summary across compaction
echo '{"session_id":"dev"}' \
| CLAUDE_PLUGIN_DATA="$DATA" node hooks/pre-compact.mjs# Force a fresh GitHub release check (ignores cache TTL)
node scripts/check-latest-version.mjs --json --no-cache
# Inject the CLAUDE.md block into a sandbox file
node scripts/install-claude-md-block.mjs --target /tmp/test-CLAUDE.md
cat /tmp/test-CLAUDE.md
# Idempotent re-run
node scripts/install-claude-md-block.mjs --target /tmp/test-CLAUDE.md
# Remove the block
node scripts/install-claude-md-block.mjs --target /tmp/test-CLAUDE.md --remove
# Install the zeropath CLI to ~/.local/bin/ (writes outside the repo)
node scripts/install.mjs --force# First call downloads the CLI to the cache; subsequent calls reuse it
DATA=/tmp/zp-dev CLAUDE_PLUGIN_DATA="$DATA" ./bin/zeropath --help# Silent when no update-check cache exists
CLAUDE_PLUGIN_DATA=/tmp/empty ./bin/statusline-zeropath.sh
# Up-arrow indicator when cache says update available
cat > /tmp/zp-dev/update-check.json <<EOF
{"ok":true,"latest":"0.4.0","updateAvailable":true}
EOF
CLAUDE_PLUGIN_DATA=/tmp/zp-dev ./bin/statusline-zeropath.shThe MCP server is launched by Claude Code via the .mcp.json config.
For debugging, you can spawn it directly:
ZEROPATH_TOKEN_ID=... \
ZEROPATH_TOKEN_SECRET=... \
ZEROPATH_ORG_ID=... \
uvx --from git+https://github.com/ZeroPathAI/zeropath-mcp-server zeropath-mcp-serverThe server speaks JSON-RPC on stdin/stdout. For an interactive tool browser, use the MCP inspector:
npx @modelcontextprotocol/inspector \
uvx --from git+https://github.com/ZeroPathAI/zeropath-mcp-server zeropath-mcp-server| Path | What lives there |
|---|---|
~/.claude/plugins/cache/zeropath-ZeroPathAI/ |
Installed plugin copy |
${CLAUDE_PLUGIN_DATA}/cli/ |
Cached zeropath binary |
${CLAUDE_PLUGIN_DATA}/sessions/<id>.touched.log |
Files edited this session |
${CLAUDE_PLUGIN_DATA}/sessions/<id>.lastscan |
Unix-ms timestamp of last clean scan |
${CLAUDE_PLUGIN_DATA}/update-check.json |
24h-cached GitHub release check |
${CLAUDE_PLUGIN_DATA}/config.json |
Per-user config override |
<project>/.zeropath/plugin.json |
Per-repo config |
~/.config/zeropath/credentials.json |
CLI credentials |
CLAUDE_PLUGIN_DATA is set by Claude Code when the plugin is active.
Outside Claude Code (i.e. running hooks manually), our code falls back
to ${tmpdir()}/zeropath-agent-plugin/.
rm -rf ~/.claude/plugins/cache/zeropath-ZeroPathAI
rm -rf ~/.claude/plugins/data/zeropath-ZeroPathAI
rm -rf ~/.config/zeropath# 1. Tests pass
node --test tests/smoke.test.mjs
# 2. All .mjs parse
find . -name "*.mjs" -not -path "./node_modules/*" -print0 \
| xargs -0 -n1 node --check
# 3. JSON manifests valid
for f in .claude-plugin/plugin.json .claude-plugin/marketplace.json \
.claude-plugin/settings.json .mcp.json hooks/hooks.json; do
node -e "JSON.parse(require('fs').readFileSync('$f','utf8'))" || echo "INVALID: $f"
done
# 4. Version sync between plugin.json and marketplace.json
grep -h '"version":' .claude-plugin/plugin.json .claude-plugin/marketplace.json- First
zeropathcall inside Claude Code takes ~5-10s while the bin/ shim downloads the CLI. Pre-warm withCLAUDE_PLUGIN_DATA=... ./bin/zeropath --version. zeropath whoamidoesn't exist upstream yet. SessionStart handles the absence gracefully (just shows "ready" without identity).- MCP env vars must be set in the shell that launched Claude Code.
Editing
~/.zshrcafter launch has no effect until you restart. - Symlinked plugin cache breaks
/plugin uninstall. Restore a real directory (or delete the symlink manually) before uninstalling. - The PreToolUse regex is intentionally narrow to avoid false
positives on commands like
echo "git push". It matches the common invocation forms only; CI scripts that do creative things withgitmay bypass the warning. - Update check writes to
${CLAUDE_PLUGIN_DATA}even when running manually. Use a sandboxCLAUDE_PLUGIN_DATA=/tmp/...when poking at scripts to avoid polluting your real plugin state.