A living mascot that floats above your windows, guards your Claude Code sessions, and lets you approve or deny actions without leaving your flow.
v2.2 — Always Allow button · keyboard shortcuts · hide/show mode · stop notifications
Each terminal session gets its own mascot — every active session spawns an independent mascot on screen, handling its own permission requests. Click the menubar icon to see live usage stats, costs, token breakdowns, and trends.
flowchart LR
A["Claude Code\nwants to run a tool"] --> B{"Config check"}
B -->|"Safe tool\n(Read, Glob...)"| C["Auto-approved"]
B -->|"Blocked tool"| D["Auto-denied"]
B -->|"Needs approval"| E["Guardian App\nshows overlay"]
E --> F{"You decide"}
F -->|"Allow / Y / Enter"| G["Tool runs"]
F -->|"Always ★"| J["Tool runs +\nadded to auto_approve"]
F -->|"Deny / N / Esc"| H["Tool blocked\n+ message to Claude"]
F -->|"No response"| I["Auto-denied\nafter timeout"]
style J fill:#d97706,color:#fff
style A fill:#f4845f,color:#fff
style C fill:#4ade80,color:#000
style D fill:#f87171,color:#fff
style E fill:#fbbf24,color:#000
style G fill:#4ade80,color:#000
style H fill:#f87171,color:#fff
style I fill:#f87171,color:#fff
brew tap anshaneja5/tap
brew install --cask claudeguardianThis installs the app to /Applications/, sets up Claude Code hooks, copies the default config to ~/.config/claude-guardian/, and launches Guardian automatically.
macOS Gatekeeper note: Since the app isn't notarized, macOS may show "app is damaged" or block it on first launch. Run this once to fix it:
xattr -cr /Applications/ClaudeGuardian.appThen open the app normally. You only need to do this once.
brew update
brew upgrade --cask claudeguardiangit clone https://github.com/anshaneja5/Claude-Guardian.git
cd Claude-Guardian
./setup.shThis will:
- Build the
.appbundle - Install
PreToolUse,SessionStart,SessionEnd,PermissionRequest, andNotificationhooks into~/.claude/settings.json - Copy the default config to
~/.config/claude-guardian/guardian.config.json - Create a LaunchAgent so Guardian starts on login
- Launch the app
# Build
cd app/ClaudeGuardian
swiftc -o ClaudeGuardian Sources/main.swift Sources/sprites.swift \
-framework Cocoa -framework SwiftUI -framework Network
# Run
./ClaudeGuardian &Click the menubar icon → Stats tab to see a full analytics dashboard powered by your local ~/.claude/ data:
- Today — cost, sessions, messages, tokens, token breakdown by type, rate limits (5-hour & 7-day), recent sessions
- All Time — lifetime totals, average daily cost, active days, model breakdown (Opus/Sonnet/Haiku)
- Projects — cost ranked by project with visual progress bars
- Trends — 7-day cost chart + 30-day daily history table
- Auto-syncs on file changes, 100% local — no data leaves your machine
- When Claude finishes a response, the mascot shows "Claude finished coding! ✓" as a speech bubble
- If Guardian isn't running when Claude stops, it auto-launches the app to show the notification
- Works even in bypass mode — a temporary mascot pops up, shows the message, then disappears
- When running
claude --dangerously-skip-permissions, Guardian steps aside completely — no mascot, no overlay, total silence - Set
"notify_only": truein~/.config/claude-guardian/guardian.config.jsonto always run in this mode - Tools already allowed in Claude Code's own
settings.json/settings.local.jsonare also auto-approved silently
- Each Claude Code session gets its own mascot widget on screen
- Mascots appear when a session starts, disappear when it ends
- Each widget is independently draggable — place them wherever you want
- Each widget shows the project folder name so you know which session is which
- Permission requests are routed to the correct session's mascot
- No sessions running = no mascots on screen (just the menubar icon)
- Click a mascot to jump to that session's terminal — instantly focuses the right window
- Long press (hold 0.5s) to cycle through mascot styles — each session can have a different mascot
- Resize with the
+/-buttons in the bottom-right corner — scales the entire widget (mascot, text, box) - The
mascotfield in config sets the default for new sessions
- Animations change based on state:
- Idle: breathing + blinking cycle
- Permission pending: waving / ear wiggle
- Approved: happy expression (^_^)
- Denied: sad expression with droopy ears
- Status label below each mascot: IDLE, WORKING, NEEDS YOU, APPROVED!, DENIED
- Permission needed → submarine alert sound so you never miss it
- Approved → pop sound
- Denied → basso sound
- Timeout → sosumi sound
- Notification → blow sound
- All using macOS built-in system sounds — no extra files needed
- Claude's notifications appear as speech bubbles above the mascot
- Shows messages like "Task completed", errors, or status updates
- Auto-dismisses after 5 seconds, or tap to dismiss immediately
- Widget expands to fit the message text
- Displays the running cost (USD) of each session right on the mascot widget
- Updates automatically every time Claude uses a tool
- Keeps you aware of spend without checking the terminal
- Expands below the mascot when Claude needs approval
- Shows the tool type (Shell Command, Write File, Edit File, etc.)
- Shows the exact content — the command, file path, code changes, etc.
- Three buttons:
- ✓ Allow (or press
Y/Return) — approve this action - ★ Always — approve this action and permanently add the tool to
auto_approve(no config editing needed) - ✕ Deny (or press
N/Escape) — first press reveals a text field to type a message back to Claude, second press sends and rejects
- ✓ Allow (or press
- Keyboard shortcuts work automatically — no need to click the mascot first
- Countdown timer — auto-denies after timeout (default 300 seconds)
- Panel collapses back to just the mascot after you respond
- Status icon in the macOS menu bar: 🟢 no sessions, 🟠 active, 🔴 needs attention, ✅ just approved, ❌ just denied
- Click the icon to see:
- Active sessions with Hide/Show buttons
- Session cost and project name at a glance
- Approve/deny stats
- Searchable action history log (last 50 actions)
- Filter bar to search by tool name or content
- Quit button
- Click the menubar icon
- Find the session → click Hide
- The mascot disappears — permission requests fall through to Claude Code's own terminal prompt (nothing is auto-approved)
- Click Show anytime to hand control back to Guardian
- If the Guardian app isn't running, the hook exits silently and Claude Code falls back to its own built-in permission prompts
- No action is ever silently approved — if something goes wrong, it fails safe
Edit ~/.config/claude-guardian/guardian.config.json (created during setup):
{
"port": 9001,
"timeout_seconds": 300,
"mascot": "cat",
"auto_approve": ["Read", "Glob", "Grep", "LS"],
"always_block": []
}| Field | Description |
|---|---|
port |
HTTP port for hook-to-app communication (default 9001) |
timeout_seconds |
Auto-deny after this many seconds of no response (default 300) |
mascot |
Default mascot for new sessions (can be changed per-session by clicking) |
auto_approve |
Tools that pass through without asking. You can also click ★ Always on any prompt to add a tool here automatically. |
always_block |
Tools that are always denied without prompting. Note: Claude Code's own permissions.deny in ~/.claude/settings.json does the same thing at the system level — use whichever you prefer. |
Set "mascot" in config for the default, or click any mascot on screen to cycle through them live:
"claude" |
"cat" |
"owl" |
"skull" |
"dog" |
"dragon" |
|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| Coral Claude | Dark Gray Cat | Brown Owl | Pixel Skull | Golden Puppy | Green Dragon |
| Hook | Script | Purpose |
|---|---|---|
PreToolUse |
hook/pre_tool_use.py |
Intercepts tool calls already in the allow list; handles cost tracking |
PermissionRequest |
hook/permission_request.py |
Intercepts built-in "Yes/No" permission prompts for all other tools |
SessionStart |
hook/session_lifecycle.py |
Notifies Guardian to spawn a mascot |
SessionEnd |
hook/session_lifecycle.py |
Notifies Guardian to remove the mascot |
Notification |
hook/notification.py |
Forwards mid-task notifications as speech bubbles on mascot |
Stop |
hook/stop.py |
Shows "Claude finished coding! ✓" when Claude stops; launches app if needed |
main.swift: App delegate with per-session window management, HTTP server (NWListener), SwiftUI views, menubarsprites.swift: All pixel art mascot sprites (16x16 grids) with animation frames and color palettes- Runs as a menubar-only app (no Dock icon)
- HTTP server handles
/health,/request,/session, and/decision/{id}endpoints - Each session window uses
.screenSaverlevel to appear above fullscreen apps
claude-guardian/
├── setup.sh # One-command install (build + post-install)
├── build-app.sh # Builds ClaudeGuardian.app bundle + zip
├── post-install.sh # Installs hooks, config, launch agent
├── guardian.config.json # Default config (port, timeout, mascot, rules)
├── hook/
│ ├── pre_tool_use.py # PreToolUse hook (allowed tools + cost tracking)
│ ├── permission_request.py # PermissionRequest hook (built-in Yes/No prompts)
│ ├── session_lifecycle.py # SessionStart/SessionEnd hook (fire-and-forget)
│ ├── notification.py # Notification hook (speech bubbles)
│ └── stop.py # Stop hook ("Claude finished coding!" notification)
├── app/
│ └── ClaudeGuardian/
│ ├── Info.plist # macOS app bundle metadata
│ └── Sources/
│ ├── main.swift # App, HTTP server, per-session windows, UI
│ └── sprites.swift # Pixel art mascot sprite data
├── homebrew/
│ └── claudeguardian.rb # Homebrew cask formula
├── .github/
│ └── workflows/
│ └── release.yml # CI: build + GitHub release on tag push
├── assets/ # Generated mascot preview images
│ ├── claude.png
│ ├── cat.png
│ ├── owl.png
│ ├── skull.png
│ ├── dog.png
│ └── dragon.png
├── generate_pngs.py # Script to regenerate mascot PNGs from sprites
└── README.md
- macOS 13+ (Ventura or later)
- Swift 5.9+ (included with Xcode or Xcode Command Line Tools)
- Python 3 (pre-installed on macOS)
- Claude Code CLI with hooks support
brew uninstall --cask claudeguardianThis automatically stops the app, removes hooks from ~/.claude/settings.json, and cleans up the launch agent.
./uninstall.shOr manually:
# 1. Stop the running app
pkill -f ClaudeGuardian
# 2. Remove the launch agent
launchctl unload ~/Library/LaunchAgents/com.claudeguardian.app.plist
rm ~/Library/LaunchAgents/com.claudeguardian.app.plist
# 3. Remove hooks from Claude Code settings
# Edit ~/.claude/settings.json and delete PreToolUse, SessionStart, SessionEnd,
# PermissionRequest, and Notification hook entries
# 4. Remove config and project folder
rm -rf ~/.config/claude-guardian
rm -rf /path/to/claude-guardian| Key | Action |
|---|---|
⌘Y |
Allow the pending action |
⌘N |
Deny (first press reveals message field, second press sends) |
| Click mascot | Jump to that session's terminal |
| Long press mascot | Cycle to next mascot style |
Click + / - |
Resize the widget (scales everything) |
| Menubar → Hide | Dismiss mascot — Claude Code's own prompt handles permissions |
| Menubar → Show | Hand control back to Guardian |
Keyboard shortcuts use ⌘ to avoid accidental triggers while typing.
Hook error about spaces in path: If your project folder path contains spaces, make sure the hook command in ~/.claude/settings.json wraps the script path in single quotes:
"command": "python3 '/path/with spaces/hook/pre_tool_use.py'"Overlay doesn't appear: Check that the Guardian app is running (curl http://localhost:9001/health should return {"status":"ok"}). If not, launch it manually.
Port conflict: If port 9001 is taken, change "port" in both guardian.config.json and the hook scripts' GUARDIAN_PORT variable.
Mascot doesn't appear for a session: Make sure SessionStart and SessionEnd hooks are installed in ~/.claude/settings.json. Run ./setup.sh again to reinstall all hooks.
- Cat pixel art sprites based on "Cats - Pixel Art" by peony (OpenGameArt, CC-BY 4.0)





