How kashmeister/prompt-controller works
1. Repository purpose
- This repo is a small macOS-only control layer around two existing agent CLIs: Claude Code and Codex CLI.
- It does not implement an agent itself; it mediates between the human, the terminal app, and the underlying CLI binary to make approval prompts impossible to miss.
2. Top-level shape
- There are two parallel wrapper stacks:
- claude-cli → claude_cli_wrapper.py
- codex-cli → codex_cli_wrapper.py
- The duplication is intentional: each wrapper is tailored to the prompt style and state markers of its corresponding CLI, while keeping the same user-facing behavior and flags.
3. Execution boundary
- The wrapper launches the real claude or codex binary inside a pseudo-terminal (PTY).
- All terminal I/O is passed through unchanged, so the underlying CLI still behaves like it was launched directly.
- The wrapper sits “beside” the CLI stream, observing output but not replacing the agent.
4. Prompt detection subsystem
- The core logic is a stream watcher that looks for blocking approval menus.
- For Codex, it keys off the [!] Action Required title marker and its numbered approval menu.
- For Claude, it matches the interactive approval menu rendered in terminal output, using a whitespace-insensitive comparison because Claude’s UI often uses cursor movement escapes rather than literal spaces.
- It deliberately ignores normal chat output and completed turns; only blocking prompts trigger attention.
5. Focus and notification subsystem
- When a prompt appears, the wrapper can:
- write active prompt state to /tmp,
- post a macOS notification,
- and bring the agent’s terminal window to the front.
- The focus behavior is careful: it targets only the relevant terminal window rather than globally raising every terminal window.
- It also supports restoring the previously focused app afterward, depending on configuration.
6. State-file contract
- Active prompt state is exposed as JSON at /tmp/<cli>-cli-prompt-state.json.
- The file acts as the integration boundary for external tooling and for the wrapper’s own bookkeeping.
- The README shows it containing fields like active, terminalApp, savedFrontmostApp, excerpt, and pid.
- Stale state is cleared on startup, so the file is treated as ephemeral runtime state, not persistent config.
7. Reminder loop / lifecycle
- While a prompt remains unanswered and the terminal is not frontmost, the wrapper periodically re-focuses and re-notifies.
- The reminder cadence is separate from the initial detection event, so there is a continuous “nudging” loop until the agent resumes or the user responds.
- Once the prompt ends, the wrapper clears state and stops the reminder behavior.
8. Configuration model
- Both wrappers expose the same control flags:
- --no-notify
- --no-activate
- --no-restore
- --state-file
- --terminal-app
- --debug
- --debug-file
- Each wrapper also reads CLI-specific environment variables with distinct prefixes:
- CLAUDE_PROMPT_*
- CODEX_PROMPT_*
- This creates a shared architecture with per-CLI namespacing rather than a single global config.
9. Runtime and tooling constraints
- The project depends on macOS, Python 3, and the presence of the real claude / codex binaries on PATH.
- Debug output is intentionally written to a file rather than the terminal, because the wrappers share the screen with a live TUI and terminal printing would corrupt rendering.
- There is no server, database, or web frontend; the system is entirely local and process-driven.
10. Optional input-control integration
- The karabiner/prompt-controller-karabiner.json file is a separate optional input layer for Karabiner-Elements.
- It does not participate in the wrapper’s control flow; it only maps controller gestures to keystrokes for answering prompts faster.
- This file belongs in a separate “human input enhancement” box in a diagram, distinct from the wrapper/CLI/terminal path.
11. End-to-end data flow
- The key path is: user launches wrapper → wrapper spawns real CLI in PTY → CLI emits terminal output → wrapper detects approval prompt → wrapper updates /tmp state and notifies/focuses → user responds in the terminal → wrapper observes prompt clearance → wrapper clears state and optionally restores prior focus.
- That is the main architecture boundary: human ↔ wrapper ↔ terminal CLI, with macOS notification/focus actions as side effects and /tmp state as the observable integration point.
Main entry points: claude_cli_wrapper.py Claude wrapper · codex_cli_wrapper.py Codex wrapper · prompt-controller-karabiner.json Karabiner map
Components 15
Prompt wrappers
Parallel control layers for the two agent CLIs.
- Claude wrapper · MacOS control layer for Claude Code prompt detection and focus handling.
claude_cli_wrapper.py - Codex wrapper · MacOS control layer for Codex CLI prompt detection and focus handling.
codex_cli_wrapper.py - Claude watcher · Matches Claude approval menus using whitespace-insensitive stream comparison.
claude_cli_wrapper.py - Codex watcher · Detects Codex blocking prompts via the action-required marker and numbered menu.
codex_cli_wrapper.py
Runtime boundary
Local process and macOS integration around the wrappers.
- Terminal app · Hosts the interactive CLI session and shows prompt menus.
- PTY bridge · Pseudo-terminal boundary that preserves native terminal behavior.
- Claude Code · Real Claude binary launched by the wrapper inside a PTY.
- Codex CLI · Real Codex binary launched by the wrapper inside a PTY.
- /tmp state · Ephemeral JSON prompt state used by the wrapper and external tooling.
- Notify & focus · Posts notifications, activates the terminal, and can restore prior focus.
- Reminder loop · Repeats notifications and refocuses while a prompt remains unanswered.
- Flags & env · Shared control flags and CLI-specific environment variables.
- Debug file · File-based debug logging to avoid corrupting live terminal rendering.
Human input enhancement
Optional keyboard automation layer for faster prompt responses.
- Karabiner map · Optional key remapping layer for faster prompt responses.
karabiner/prompt-controller-karabiner.json
Other components
- Human · Launches wrappers, answers prompts, and resumes the agent.
Connections 21
- Human → Claude wrapper: launches · User starts the Claude wrapper directly.
- Human → Codex wrapper: launches · User starts the Codex wrapper directly.
- Claude wrapper → PTY bridge: spawns · Wrapper creates a PTY-backed session for the real CLI.
- Codex wrapper → PTY bridge: spawns · Wrapper creates a PTY-backed session for the real CLI.
- PTY bridge → Claude Code: runs · Claude Code executes inside the pseudo-terminal.
- PTY bridge → Codex CLI: runs · Codex CLI executes inside the pseudo-terminal.
- Claude Code → Terminal app: renders output · Interactive output passes through to the terminal unchanged.
- Codex CLI → Terminal app: renders output · Interactive output passes through to the terminal unchanged.
- Terminal app → Claude watcher: stream seen by · Claude wrapper inspects terminal output for approval prompts.
- Terminal app → Codex watcher: stream seen by · Codex wrapper inspects terminal output for approval prompts.
- Claude watcher → /tmp state: updates · Writes active prompt state and clears it when the prompt ends.
- Codex watcher → /tmp state: updates · Writes active prompt state and clears it when the prompt ends.
- Claude watcher → Notify & focus: triggers · On prompt detection, notifies and brings the terminal forward.
- Codex watcher → Notify & focus: triggers · On prompt detection, notifies and brings the terminal forward.
- Notify & focus → Reminder loop: feeds · Continues nudging while the prompt remains unanswered and the terminal is not frontmost.
- Reminder loop → Notify & focus: repeats · Re-fires notifications and focus changes until the prompt clears.
- Claude wrapper → Flags & env: reads · Shared flags and CLAUDE_PROMPT_* environment settings.
- Codex wrapper → Flags & env: reads · Shared flags and CODEX_PROMPT_* environment settings.
- Claude wrapper → Debug file: writes · Debug output goes to a file instead of the live terminal.
- Codex wrapper → Debug file: writes · Debug output goes to a file instead of the live terminal.
- Karabiner map → Human: assists · Optional key remapping helps the human answer prompts faster.