Skip to main content

Permission policy mediator for Claude Code, Codex, OpenCode, and Gemini CLI.

Project description

agentperm

PyPI License: MIT Python 3.12+

One allow / ask / deny policy for Claude Code, Codex CLI, OpenCode, and Gemini CLI — with a shell parser that reads compound commands the way bash does.

What it does

Coding agents ask before running a shell command. Each ships its own permission config, and none of them actually parse the command — to the native matcher, cat foo | head -60 is one opaque string, so it can't tell that it's two harmless read-only commands and asks you anyway. Every time.

agentperm replaces those four separate configs with a single file every agent consults, and decides on the whole parsed command. Here is a policy and exactly what your agents do with it:

// ~/.agent-permissions.jsonc
{
  "version": 1,
  "permissions": {
    "allow": ["Bash(cat:*)", "Bash(head:*)", "Bash(sed:*)", "Bash(git status:*)", "Read", "Grep"],
    "ask":   [{ "tool": "Bash", "command": ["sed"], "when": { "hasOption": ["-i", "--in-place"] },
                "reason": "sed -i edits files in place" }],
    "deny":  ["Bash(sudo:*)", "Bash(rm -rf:*)"]
  }
}
Command the agent wants to run Verdict Why
cat foo 2>&1 | head -60 allow both segments allowed; 2>&1 is a safe redirect
git status && cat README.md allow && sequence, both sides allowed
sed -i s/old/new/ x.txt ask sed is allowed, but the ask rule on -i wins
cat notes | ./deploy.sh ask compound includes unrecognized segment: no rule matched './deploy.sh'
rm -rf /tmp/* deny deny by rule 'Bash(rm -rf:*)'

These are the real verdicts agentperm check returns — the same answers every wired-up agent receives (the ask/deny rows quote the exact rationale string). The first two run with no prompt; the native matchers ask about both, because a single pipe or && defeats literal string matching.

Install

uv tool install agentperm   # or: pipx install agentperm

Set up (once)

agentperm import    # copy your agents' existing rules into ~/.agent-permissions.jsonc
agentperm install   # wire agentperm into each agent's hooks
agentperm edit      # open the policy in your editor ($VISUAL/$EDITOR; writes a default if none exists)

After install, each agent calls agentperm before running a tool. Your native settings keep working underneath as a fast path — nothing is taken away.

install writes to each agent's hook config (~/.claude/settings.json, ~/.codex/, ~/.gemini/settings.json, plus an OpenCode plugin), or merges into ~/.rulesync/hooks.json if you use Rulesync. Preview with --dry-run; force a path with --mode rulesync|direct. See the CLI reference.

Writing rules

Rules go in allow, ask, or deny. Three forms:

  • "Bash(git status:*)" — match a shell command by prefix (git status and anything after it). Drop the :* for an exact match, or use glob tokens: * matches one argument, ** matches zero or more (e.g. Bash(pnpm --dir * build:*)).
  • "Read", "WebFetch(domain:github.com)" — match a named, non-shell tool, optionally scoped.
  • Object form — match on flags: { "tool": "Bash", "command": ["sed"], "when": { "hasOption": ["-i"] }, "reason": "..." }.

In a compound command the strictest segment decides — one unrecognized command turns the whole line into an ask, and deny always wins. Full grammar: policy reference.

Global + per-project

agentperm merges two policy files and applies deny over everything: broad defaults in your home directory, overrides per repo. Both apply at the same time.

// ~/.agent-permissions.jsonc — defaults, everywhere
{ "version": 1, "permissions": {
  "allow": ["Bash(cat:*)", "Bash(git push:*)", "Read", "Grep"],
  "deny":  ["Bash(sudo:*)", "Bash(rm -rf:*)"]
}}
// ~/work/payments/.agent-permissions.jsonc — only inside this repo
{ "version": 1, "permissions": {
  "allow": [
    "Bash(pytest:*)",               // this repo's own tools
    "Bash(pnpm --dir * build:*)"    // * = one arg token; ** = zero or more
  ],
  "deny": [
    { "tool": "Bash", "command": ["git"],
      "when": { "hasOption": ["--force", "-f", "--force-with-lease"] },
      "reason": "force-push rewrites shared history" }
  ]
}}

Working inside ~/work/payments, an agent sees both at once:

Command Verdict Source
cat notes.md allow global default
pytest -q allow project adds it
pnpm --dir packages/web build allow project glob rule
git push allow global — a normal push is still fine
git push --force deny project flag rule overrides the global allow

So you allow a tool broadly once, and a single repo can both add its own commands and clamp down on the dangerous variants — without touching the global file.

Create or edit the project file with agentperm edit --local (it writes to your git repo root). import and install always act on the global file and your agents' global hooks.

Commands

Command What it does
agentperm import Copy existing native rules into the policy file (Claude, Codex, OpenCode)
agentperm install [--mode auto|rulesync|direct] [--dry-run] Wire agentperm into agent hooks
agentperm edit [--global|--local] Open the policy in your editor ($VISUAL/$EDITOR), creating a default if missing. --global (default) is ~/.agent-permissions.jsonc; --local is this repo's root file
agentperm check --agent <claude|codex|opencode|gemini> --event <name> Runtime decision: reads a hook payload on stdin, writes a verdict on stdout. The agent runs this, not you.
agentperm --version Print the installed version

Skip prompts in one pane (zellij)

Running agents inside zellij? The bundled WASM plugin adds a per-pane toggle: while it's on, both ask and unmatched (no-opinion) commands become allow in that pane only — deny rules still bite. See zellij-plugin/. (Under Claude Code's own --dangerously-skip-permissions, agentperm steps aside completely.)

What it doesn't do

  • It doesn't sandbox. agentperm decides allow / ask / deny; it doesn't contain what runs. An approved command runs with your normal privileges.
  • It doesn't replace native settings. Those keep working as a fast path; agentperm layers on top.
  • It doesn't manage MCP servers. Use Rulesync or native config for that.

More

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentperm-0.2.0.tar.gz (51.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentperm-0.2.0-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

Details for the file agentperm-0.2.0.tar.gz.

File metadata

  • Download URL: agentperm-0.2.0.tar.gz
  • Upload date:
  • Size: 51.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.18

File hashes

Hashes for agentperm-0.2.0.tar.gz
Algorithm Hash digest
SHA256 acaaab24b8d2b8c996fbe69f833e35d856f6f50c23d503a1c69d3ca4e858e456
MD5 6106aa2e5e7e9da332a34ea033401d23
BLAKE2b-256 9a8ae3d39cfbadd7762405931e053d8242c70a0bce0ce83601bae35d3af2dce8

See more details on using hashes here.

File details

Details for the file agentperm-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: agentperm-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 31.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.18

File hashes

Hashes for agentperm-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 05a40299f8763fcaa6c7b78a7d41c0642bf1c06f42764a73ccb8d632ecfeaa4c
MD5 72e277b2b5c1f0f2f35aa1834dc39250
BLAKE2b-256 19f50125bc39f387a51f81b0c9c94e3c33042cd1fb30435ebfd035c454b81a5f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page