Metadata-Version: 2.4
Name: bund
Version: 1.5.0
Summary: Telegram pings for coding agents. Approve from your phone. Free. Zero dependencies.
License: MIT
Keywords: telegram,claude,coding-agent,hooks,notifications,approval
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

<h1 align="center">bund</h1>

<p align="center">
  <a href="https://pypi.org/project/bund/"><img src="https://img.shields.io/pypi/v/bund" alt="PyPI"></a>
  <a href="https://pypi.org/project/bund/"><img src="https://img.shields.io/pypi/pyversions/bund" alt="Python"></a>
  <a href="https://pypi.org/project/bund/"><img src="https://img.shields.io/pypi/dm/bund" alt="Downloads"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
</p>

<p align="center"><b>Your AI agent is waiting for you. Again.</b></p>

<p align="center">
  <img src="https://api.memegen.link/images/fine/claude_has_been_waiting_for_2_hours/this_is_fine.png?width=450" width="450" alt="this is fine">
</p>

<p align="center">
  Telegram pings for coding agents.<br>
  Approve from your phone. Free. Zero dependencies.
</p>

---

## The problem

<p align="center">
  <img src="https://api.memegen.link/images/db/you/agent_needing_approval/just_one_more_meeting.png?width=450" width="450" alt="distracted boyfriend">
</p>

## How it works

<p align="center">
  <img src="https://api.memegen.link/images/gru/claude_asks_permission/bund_pings_your_telegram/you_tap_approve_from_anywhere/claude_keeps_working.png?width=450" width="450" alt="gru plan">
</p>

## Setup (2 minutes)

```bash
pip install bund

bund set --token <botfather-token>
bund set --chat auto
bund install        # Claude Code hooks
bund setup opencode # …or claudecode, codex, gemini — project prompt
bund test
```

`bund install` wires Claude Code's `PermissionRequest` and `Stop` hooks.
`bund setup <provider>` drops a prompt into your project so any other agent
uses `bund ask` for every question. On scripts, call `bund ask` directly —
no install needed.

<p align="center">
  <img src="https://api.memegen.link/images/success/set_up_in_two_minutes/phone_buzzed_immediately.png?width=400" width="400" alt="success kid">
</p>

## What you get

| Button | Does |
|---|---|
| ✅ Approve | Lets it run, once |
| ❌ Deny | Nope |
| ♾ Approve all similar | Never asks again for that kind of command |

And a ping when the task is done:

<p align="center">
  <img src="https://api.memegen.link/images/fry/not_sure_if_agent_finished/or_waiting_for_my_approval.png?width=450" width="450" alt="fry">
</p>

Now you know which one.

## Sample message

```
🙄 Your AI intern wants to run Bash. Please supervise.

📁 my-project
npm run build

[ ✅ Approve ] [ ❌ Deny ]
[ ♾ Approve all similar: Bash(npm run *) ]
```

The joke is on top. The real command is always shown in full below it.

## Is my PC still alive?

Send `/status` to your bot:

```
🟢 PC: ON — uptime 3h 12m (bund 1.5.0)
🤖 Agent: RUNNING — waiting for your approval
⏳ Bash in my-project — waiting 2m
🕑 last activity: 1m ago
🔍 processes: opencode
```

Answers all three questions: is the PC on, is an agent running, and is
something **waiting on you right now** (that one comes first).

Any running bund command answers `/status` — a hook waiting for approval,
`bund ask`, or the always-on listener:

```bash
bund listen     # stay online, answer /status, Ctrl+C to stop
```

Only your chat gets answers. Everyone else gets silence.

## Works with any agent

`bund ask` is the universal protocol: any CLI, script, or agent asks a
question, your phone answers.

```bash
bund ask "deploy to prod?" && echo let's go
# stdout: allow | deny | timeout
# exit:   0      1      2
```

```python
import subprocess
r = subprocess.run(["bund", "ask", "run the migration?"], capture_output=True, text=True)
if r.stdout.strip() == "allow":
    ...
```

No config files to wire. If bund isn't configured, `bund ask` fails fast with
exit code 2 instead of hanging your agent.

## One command per project

Tell any agent to use bund for **every** question and approval, scoped to the
current project:

```bash
bund setup opencode      # writes the prompt into ./AGENTS.md
bund setup claudecode    # writes the prompt into ./CLAUDE.md
bund setup codex         # AGENTS.md
bund setup gemini        # GEMINI.md
bund setup generic       # just prints it — paste anywhere
bund -setup opencode     # same thing, if you like dashes

bund setup opencode --print    # only print, don't write a file
bund setup opencode --dir ~/other-project   # write elsewhere
```

You get a ready-made instruction block (also printed to stdout):

```markdown
### When to use it
Any moment you would otherwise block waiting for me:
- you need permission for an action (risky or unusual command, …)
- you need a decision or answer from me to continue
- you are done with the task and need confirmation before the next step

### Contract (strict)
- stdout is exactly one word: `allow`, `deny`, or `timeout`
- exit codes: `0` = allow, `1` = deny, `2` = timeout / bund missing
- `timeout` … → fall back to asking in the terminal. Never treat it as
  approval. Never hang forever.
```

Running it twice updates the block in place instead of duplicating it.

## Safety

<p align="center">
  <img src="https://api.memegen.link/images/drake/approve_all_for_rm_rf/no_button_for_dangerous_commands.png?width=400" width="400" alt="drake">
</p>

- Only **your** Telegram chat can approve anything
- Every button works once (random request ids, no replaying old taps)
- Dangerous commands (`rm -rf`, `sudo`, force pushes, `curl | sh`, …) get a
  serious warning and **no** "approve all" button
- No answer? The agent falls back to the normal terminal prompt
- bund can never block your work — any error means silent fallback
- Token and chat id live in `~/.config/bund/config.json` with `chmod 600`

## Dependencies

<p align="center">
  <img src="https://api.memegen.link/images/buzz/zero_dependencies/zero_dependencies_everywhere.png?width=400" width="400" alt="buzz">
</p>

Python 3.9+. Standard library only. That's it.

## Cost

<p align="center">
  <img src="https://api.memegen.link/images/oprah/you_get_a_free_bot/and_you_get_free_approvals.gif?width=400" width="400" alt="oprah">
</p>

## Tone

```bash
bund set --tone sarcasm   # default
bund set --tone plain     # for people who have no joy
```

Add your own lines in `~/.config/bund/templates.json`.

## Also

```bash
bund -m "build finished, come back"
```

Works with any script or agent.

<p align="center">
  <img src="https://api.memegen.link/images/pigeon/claude/_/is_this_a_permission_prompt~q.png?width=400" width="400" alt="is this a pigeon">
</p>

---

**Commands**

```
bund set --token <token>        # botfather token
bund set --chat auto|<id>       # pick your chat
bund set --tone sarcasm|plain   # message tone
bund install                    # add Claude Code hooks
bund uninstall                  # remove only bund's hooks
bund setup <provider>           # project-scope prompt: use bund for every question
                               #   providers: opencode, claudecode, codex, gemini, cursor, generic
                               #   flags: --print (stdout only), --dir <path>
bund test                       # send a test message
bund ask "<question>"           # ask anything (allow|deny|timeout)
bund listen                     # always-on: answer /status from Telegram
bund -m "<text>"                # send any message
bund hook                       # internal, called by hooks
```

<p align="center">
  <sub>Memes by <a href="https://memegen.link">memegen.link</a>. If they don't load, that's on them, not us.</sub><br>
  <sub>MIT License</sub>
</p>
