All checks were successful
Reindex Knowledge Base / reindex (push) Successful in 5s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
4.7 KiB
Markdown
139 lines
4.7 KiB
Markdown
---
|
|
title: "Writing Classifiers for permission-manager (agent-toolkit)"
|
|
description: "Guide to adding new command classifiers to the St0nefish/agent-toolkit permission-manager plugin for Claude Code, covering project structure, conventions, testing, and PR workflow."
|
|
type: guide
|
|
domain: development
|
|
tags: [claude-code, permissions, agent-toolkit, bash, classifier]
|
|
---
|
|
|
|
# Writing Classifiers for permission-manager
|
|
|
|
## Overview
|
|
|
|
The `permission-manager@agent-toolkit` plugin (St0nefish/agent-toolkit) provides a `cmd-gate` PreToolUse hook that classifies Bash commands before execution. Classifiers are bash scripts in `plugins-claude/permission-manager/scripts/classifiers/` that decide whether commands should be auto-allowed, require user approval, or be denied.
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
plugins-claude/permission-manager/
|
|
scripts/
|
|
cmd-gate.sh # Hook entry point
|
|
lib-classify.sh # Classification framework + dispatcher
|
|
classifiers/
|
|
cargo.sh # cargo/rust
|
|
docker.sh # docker/compose
|
|
find.sh # find (deny -delete/-exec rm)
|
|
git.sh # git (with protected branch logic)
|
|
gh.sh # GitHub CLI
|
|
gradle.sh # gradle/gradlew
|
|
jvm-tools.sh # java/mvn
|
|
npm.sh # npm/node/pnpm/yarn/npx
|
|
pip.sh # pip/python/poetry/pyenv
|
|
read-only-tools.sh # cat, ls, grep, jq, ps, etc.
|
|
tea.sh # Gitea CLI
|
|
uv.sh # uv/uvx (added 2026-03-18)
|
|
tests/permission-manager/
|
|
test-classify.sh # Main test harness (618+ tests)
|
|
```
|
|
|
|
## Classification Decisions
|
|
|
|
Three possible outcomes:
|
|
- **`allow`** — auto-approve, no prompt (read-only or safe local operations)
|
|
- **`ask`** — prompt user for approval (destructive, remote, or elevated operations)
|
|
- **`deny`** — block the command (e.g., `find -delete`)
|
|
- **passthrough** — `return 0` without calling allow/ask/deny; defers to Claude Code's built-in permission system
|
|
|
|
## Classifier Conventions
|
|
|
|
### File Header
|
|
```bash
|
|
# shellcheck shell=bash
|
|
# shellcheck source=../lib-classify.sh
|
|
```
|
|
|
|
### Function Naming
|
|
- Entry point: `check_<tool>()` (e.g., `check_uv`, `check_docker`)
|
|
- Subcommand extractor: `extract_<tool>_subcommand()` (for tools with global flags)
|
|
|
|
### Entry Guard Pattern
|
|
|
|
Simple tools (cargo, npm, pip) use `awk` + `case`:
|
|
```bash
|
|
local first_token
|
|
first_token=$(echo "$command" | awk '{print $1}')
|
|
case "$first_token" in
|
|
uv) ;;
|
|
*) return 0 ;;
|
|
esac
|
|
```
|
|
|
|
Complex tools (docker, git) use `perl` guard:
|
|
```bash
|
|
echo "$command" | perl -ne '$f=1,last if /^\s*docker(\s|$)/; END{exit !$f}' || return 0
|
|
```
|
|
|
|
### Reason Message Style
|
|
- Read-only: `"<tool> <subcmd> is read-only"`
|
|
- Local ops: `"<tool> <subcmd> is a local build/dev operation"`
|
|
- Ask: `"<tool> <subcmd> modifies <what>"`
|
|
- Deny: `"<tool> <subcmd> is not allowed"`
|
|
|
|
### Classification Philosophy (from existing classifiers)
|
|
| Category | Decision | Examples |
|
|
|----------|----------|---------|
|
|
| Read-only inspection | allow | `git status`, `docker ps`, `pip list` |
|
|
| Local build/install | allow | `npm install`, `pip install`, `cargo build` |
|
|
| Global tool install | ask | `cargo install`, `uv tool install` |
|
|
| Package uninstall | ask | `pip uninstall`, `uv pip uninstall` |
|
|
| Publish/deploy | ask | `npm publish`, `cargo publish`, `uv publish` |
|
|
| Execute arbitrary packages | passthrough | `npx`, `uvx`, `uv run --with` |
|
|
| Destructive operations | deny | `find -delete` |
|
|
|
|
## Wiring a New Classifier
|
|
|
|
In `lib-classify.sh`, add to `classify_single_command()`:
|
|
```bash
|
|
check_<tool>
|
|
[[ "$CLASSIFY_MATCHED" -eq 1 ]] && return 0
|
|
```
|
|
|
|
Place it logically near related classifiers (e.g., `check_uv` after `check_pip`).
|
|
|
|
## Testing
|
|
|
|
### Test Format
|
|
```bash
|
|
run_test_both <expected> "<command>" ["label"]
|
|
```
|
|
`run_test_both` runs the test in both Claude and Copilot modes. Expected values: `allow`, `ask`, `deny`, `none` (passthrough).
|
|
|
|
### Test Section Structure
|
|
```bash
|
|
# ===== ALLOW: <tool> read-only =====
|
|
echo "── <tool> read-only ──"
|
|
run_test_both allow "<command>"
|
|
|
|
# ===== ALLOW: <tool> local build/dev =====
|
|
echo "── <tool> local build/dev ──"
|
|
...
|
|
```
|
|
|
|
### Running Tests
|
|
```bash
|
|
bash tests/permission-manager/test-classify.sh # Full suite
|
|
bash tests/permission-manager/test-classify.sh uv # Filter by keyword
|
|
```
|
|
|
|
## PR Workflow
|
|
|
|
1. Fork `St0nefish/agent-toolkit` on GitHub
|
|
2. Branch from `master` (not `main`)
|
|
3. Add classifier, wire it, update tests
|
|
4. Run full test suite — zero regressions required
|
|
5. PR via `gh pr create --repo St0nefish/agent-toolkit --base master`
|
|
|
|
## Reference PR
|
|
|
|
- [PR #38: feat: add uv/uvx classifier](https://github.com/St0nefish/agent-toolkit/pull/38) — full uv/uvx classifier with 102 tests, submitted 2026-03-18
|