intervene-cli
v1.0.2
Published
Intervene routes approvals, decisions, and notifications from coding agents to the messaging provider on your phone
Readme
Intervene
An escalation daemon and MCP server that bridges coding agents and messaging providers, letting you monitor and control autonomous coding sessions from your phone.
Inspired by SebastienMelki/escalate.
Why Intervene?
Without Intervene, running an autonomous coding agent often means one of two things:
Sitting at your computer watching. The agent stops every few minutes to ask for permission — "Can I run this Bash command?", "Can I write this file?" You're glued to your screen, clicking Approve over and over. You can't step away for coffee, take a walk, or do anything else. You're basically a human rubber stamp.
Using remote sessions. Remote agent sessions let you check back later. But there's a catch: when the agent hits a decision point, it just waits. Silently. You have no idea it's blocked until you manually check in. If it needs permission at minute 3 of a 30-minute task, the remaining 27 minutes are wasted. There's no notification, no ping, no way to know.
With Intervene, you get a third option. Start the agent on a big task, put your phone in your pocket, and walk away. When it actually needs you — a permission approval, a decision about approach, a failure that needs attention — your phone buzzes with a message. You glance at it, tap Approve, and the session keeps going. The rest of the time: silence.
What it looks like
- You kick off a task:
intervene codex - The agent works autonomously — reading files, planning, writing code
- It needs to run
rm -rf old-auth/→ your phone buzzes - You see: "PreToolUse: Bash" with the command and context
- You tap Approve → the agent continues instantly
- Later, the run finishes and you get a session summary
That's it. You were making lunch the whole time.
How It Works
Agent Adapter / MCP Client ──> Intervene Daemon / MCP Server ──> Provider API ──> Your Phone
│ │
SQLite store <──────── link tap/reply ─────────┘
│
Agent Adapter / MCP Client <───────────┘- Agent adapters send escalation requests into Intervene.
- Intervene stores state locally and routes the request to the configured provider.
- Provider adapters send Block Kit messages, bot messages, or signed approval links.
- You respond by tapping a button, replying in-chat, or opening a signed link.
- Intervene hands the response back to the agent adapter or MCP client.
Claude still works through its plugin hooks. Codex has two paths:
intervene codexfor native Codex approval mirroring- plain MCP tool usage for generic
request_human_approval
Prerequisites
- Node.js >= 22
- A messaging provider account such as Slack, Telegram, Teams, Email, or WhatsApp
- Claude Code, Codex, or another MCP-capable agent runtime
Providers
Intervene currently supports:
- Slack via Incoming Webhooks and signed approval links
- Telegram via bot messages and signed approval links
- Microsoft Teams via Adaptive Cards with signed approval links
- Email via SMTP with signed approval links
- WhatsApp Cloud API via signed approval links
Use the CLI to scaffold provider-specific config:
intervene init slack
intervene init telegram
intervene init --non-interactive teams
intervene init --list-providersThe command writes intervene.config.json plus a matching .env template for the selected provider.
It also writes project-scoped MCP config for Codex in .codex/config.toml and a generic .mcp.json for agents that read standard MCP config files.
Agent Runtimes
Intervene currently supports three integration styles:
- Claude Code via plugin hooks
- Codex via
intervene codex, which mirrors Codex's native approval prompts into your provider - Generic MCP-capable agents via the
request_human_approvalMCP tool
The core service stays the same in both cases. Agent-specific logic is just the adapter on the front side.
Slack Setup
Create or reuse a Slack Incoming Webhook for the target channel. Intervene posts messages to that webhook and uses signed approval links that route back through Intervene's own HTTP bridge.
Installation
Install globally from npm:
npm install -g intervene-cliOr clone and build locally:
git clone https://github.com/PeterHdd/intervene.git
cd intervene
pnpm install
pnpm buildRunning Intervene
Run Intervene as a local daemon if you want the shared core without attaching a client:
intervene daemonThis starts the local HTTP bridge, provider adapter, and SQLite state store without attaching stdio MCP transport.
For Claude Code, load the plugin:
claude --plugin-dir /path/to/interveneFor Codex, the recommended path is:
intervene codexThat command starts Intervene, starts a Codex app-server proxy, and mirrors native Codex approval prompts into your configured provider.
If you only want generic MCP tool access, intervene init also writes project-scoped MCP configuration automatically. In that mode, you can still run:
codexCodex will auto-start Intervene for that project as an MCP server. Intervene exposes request_human_approval, which sends the request through Slack/Telegram/Teams/Email/WhatsApp and waits for a human response. That MCP path is useful for generic human-in-the-loop questions, but it does not replace Codex's native approval system. intervene codex is the path that mirrors native approvals like command execution prompts.
Configuration
Environment Variables
Set these in your shell or .env (never commit secrets):
export INTERVENE_RESPONSE_SIGNING_SECRET="replace-with-a-random-secret"Optional (only needed for voice note transcription):
export INTERVENE_OPENAI_API_KEY="sk-..." # OpenAI API key for WhisperConfig File
The fastest path is:
intervene init slackIf you want to create the file manually, use:
Create intervene.config.json in your project root:
{
"provider": "slack",
"server": {
"publicBaseUrl": "https://intervene.example.com"
},
"slack": {
"webhookUrl": "https://hooks.slack.com/services/T00000000/B00000000/replace-me"
}
}webhookUrl is the Slack Incoming Webhook URL for the channel that should receive escalation messages. server.publicBaseUrl must be a public HTTPS URL that can receive approval link traffic from your phone.
Other providers use the same root file with a different provider field and provider-specific block:
{
"provider": "telegram",
"telegram": {
"chatId": "123456789"
}
}{
"provider": "teams",
"server": {
"publicBaseUrl": "https://intervene.example.com"
},
"teams": {
"webhookUrl": "https://outlook.office.com/webhook/..."
}
}Register as a Claude Code Plugin
claude --plugin-dir /path/to/interveneOr add to your project's .claude/plugins.json.
Connect from Codex
Codex stores MCP config in .codex/config.toml for trusted projects. intervene init updates that file automatically with an intervene MCP server entry pointing at Intervene's local server/main.js. It also writes .mcp.json so other MCP-capable agents can reuse the same project configuration.
For native Codex approval mirroring, use:
intervene codexFor plain MCP tool access, use:
codexInside Codex, Intervene should appear as an active MCP server. The agent can then call request_human_approval whenever it needs a real human decision. For native command or permission prompts, prefer intervene codex.
Releases
GitHub Actions now handles:
ci.ymlfor typecheck, test, and build on pushes and pull requestsrelease.ymlfor npm publishing on GitHub release publication
To publish successfully, set the NPM_TOKEN repository secret in GitHub and publish a GitHub release.
Smoke Tests
Use these before trusting a provider in day-to-day work.
Local provider smoke test
- Run
intervene init <provider>in a throwaway project. - Confirm
intervene.config.json,.env,.codex/config.toml, and.mcp.jsonwere written. - Start the agent path you actually plan to use:
- Claude:
claude --plugin-dir /path/to/intervene - Codex native approvals:
intervene codex - Generic MCP agent: run the agent normally and confirm
interveneappears in its MCP list
- Claude:
- Trigger an approval request:
- Claude: ask for a dangerous file or shell action
- Codex native: ask Codex to open a file in Finder or run a gated shell command
- Generic MCP: ask the agent to call
request_human_approval
- Verify the provider receives the message.
- Approve or deny from the provider.
- Verify the agent resumes with the correct decision.
Same-laptop signed-link smoke test
If you are testing signed approval links locally on one machine:
- Set
server.portto a fixed value, for example8787. - Set
server.publicBaseUrltohttp://127.0.0.1:8787. - Set
INTERVENE_RESPONSE_SIGNING_SECRETin.env. - Start the agent session.
- Confirm the bridge is up with:
curl http://127.0.0.1:8787/health- Click the approval link from the same laptop.
For phone-based testing, publicBaseUrl must be a public HTTPS URL that resolves back to the laptop.
Testing Strategy
Intervene needs three layers of testing:
Unit tests
- message formatting
- signature generation and verification
- Claude hook output translation
- Codex native approval mapping
Local integration tests
- HTTP bridge routes
- store resolution
- provider adapter payload generation
- signed-link approval roundtrips against a local bridge
Real-provider smoke tests
- Slack webhook channel
- Telegram bot chat
- Teams webhook
- email inbox
- WhatsApp test recipient
The practical way to automate the third layer is to use dedicated test destinations and a retryable smoke runner that:
- starts Intervene in a temp project
- triggers a known approval event
- polls the provider-side test destination or callback path
- resolves the approval
- verifies the agent resumes
For most teams, that should run in CI only for sandbox providers or on a scheduled staging machine with real credentials, not on every local test run.
What Gets Intervened
| Event | What Happens | Your Options | |-------|-------------|--------------| | PermissionRequest | The agent needs permission for an action | Approve / Deny | | PreToolUse | Dangerous tool (Bash, Write, Edit) about to run | Approve / Deny | | Stop | The agent wants to end the session | Stop / Continue | | PostToolUseFailure | A tool failed (notification only) | Acknowledged |
Architecture
intervene/
├── src/adapters/
│ ├── claude/ # Claude hook adapter + output translation
│ └── codex/ # Codex native approval bridge + app-server proxy
├── .claude-plugin/plugin.json # Claude plugin manifest
├── hooks/hooks.json # Claude hook registration
├── scripts/ # Thin hook dispatchers (<50 lines each)
│ ├── on-permission-request.ts
│ ├── on-pre-tool-use.ts
│ ├── on-stop.ts
│ ├── on-post-tool-failure.ts
│ └── lib/
│ ├── bridge-client.ts # Shared HTTP client for hook scripts
├── src/
│ ├── config/ # Zod-validated config loading
│ ├── state/ # SQLite escalation store
│ ├── server/
│ │ ├── mcp-server.ts # MCP server for generic MCP agents
│ │ ├── http-bridge.ts # Shared HTTP bridge for hooks and signed replies
│ │ ├── runtime.ts # Shared escalation runtime helpers
│ │ └── index.ts # Daemon / server entrypoint
│ ├── slack/
│ │ ├── adapter.ts # SlackAdapter (Incoming Webhook + signed links)
│ │ └── blocks.ts # Block Kit message builder
│ ├── providers/ # Telegram/Teams/Email/WhatsApp adapters
│ └── types/ # Shared TypeScript types
└── test/ # Vitest test suiteThe important split is:
src/adapters/claude: translates Claude hook events into Intervene escalations and back into Claude hook JSON.src/adapters/codex: intercepts Codex native approval requests and resolves them through Intervene.src/server+src/providers: shared core used by every agent adapter.
Development
# Run tests
pnpm test
# Watch mode
pnpm test:watch
# Type check
pnpm typecheck
# Lint
pnpm lint
# Format
pnpm formatLicense
MIT -- see LICENSE for details.
Contributing
See CONTRIBUTING.md for guidelines.
