Metadata-Version: 2.5
Name: awai
Version: 0.1.1
Summary: Run Claude Code in tmux and drive it from your phone (Telegram) or anywhere, through one small local API.
Project-URL: Homepage, https://github.com/zeocax/awai
Project-URL: Repository, https://github.com/zeocax/awai
Project-URL: Issues, https://github.com/zeocax/awai/issues
Project-URL: Documentation, https://github.com/zeocax/awai/blob/master/docs/api.md
Author-email: zeocax <zeocax@zeocax.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agent,claude,claude-code,coding-agent,remote,telegram,tmux
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# awai

Run AI coding agents in tmux, then watch and drive them remotely through one small local API.
The name comes from away + AI: you step away from the computer, the agent keeps working.

```
 Telegram adapter          ─┐
 Web (planned)             ─┼─ HTTP API + event stream (SSE) ──▶ awai core ──▶ tmux ──▶ claude
 awai CLI                  ─┘                                        ◀── reads the transcript
```

## Design principles

- **Logic lives in one place.** Everything a session does is in the core (`SessionManager`). Telegram, the web and the CLI only call the public API; none of them touch tmux.
- **Code you can read in one sitting.** The core uses only the Python standard library; there are no third-party runtime dependencies.
- **Leaves your configuration alone.** awai never writes `~/.claude/settings.json`. It starts claude with `--session-id` to fix the session ID and `--settings` to pass hooks that belong to that one session.
- **Only touches its own tmux.** awai runs a separate tmux server (`tmux -L awai`), so it can neither see nor change your other tmux sessions.

## Security model

- The API listens on `127.0.0.1:8787` by default. Binding to anything else requires an explicit `[server] allow_remote = true`, and you should put TLS in front of it.
- Every request needs `Authorization: Bearer <token>`. The token is random and stored in `~/.awai/token` (0600).
- The `Host` header is checked, so a malicious web page cannot reach the local service through DNS rebinding. Request bodies must be `application/json`, and no CORS headers are sent.
- Configuration is read only from `~/.awai/config.toml`; **a `.env` in the current directory is never read**.
- The agent's argument list is handed to tmux directly, never through a shell. Text input may not contain control characters, and special keys go through an allow-list.
- Agents cannot be started in `/` or in your home directory itself. `[sessions] allowed_roots` narrows this further.
- The CLI client ignores proxy settings, so the token never goes through `HTTP_PROXY`.

## Install

Requires Python ≥ 3.11, [tmux](https://github.com/tmux/tmux) and [Claude Code](https://docs.claude.com/en/docs/claude-code). Developed and tested on macOS; Linux should work but is untested.

```bash
uv tool install awai      # or: pipx install awai
```

From source: `git clone https://github.com/zeocax/awai && cd awai && uv run awai …`.

## Quick start

```bash
awai serve                 # start awai: the core plus the adapters enabled in the config (e.g. Telegram); creates the token on first run

# in another terminal
awai new ~/code/myapp      # start claude in the project directory; prints the session ID
awai ls
awai send 3f2a "look at the failing tests"   # a prefix of the session ID is enough
awai events                # watch events live (JSON lines)
awai screen 3f2a           # show the terminal screen
awai keys 3f2a Down Enter  # answer prompts such as menus
awai attach 3f2a           # take over at the computer; Ctrl-b d to detach
awai restart 3f2a          # restart claude and continue the conversation (with the current hooks)
awai restart 3f2a --yolo   # same, and switch to YOLO; --default switches back, --mode manual etc.
awai kill 3f2a
```

The first time claude starts in a directory it asks whether you trust it, and **the preselected answer is "No, exit"**. Pressing Enter makes claude quit. To trust the directory:

```bash
awai keys 3f2a Down Enter
```

## Telegram

`awai serve` starts the Telegram adapter when the config enables it. Like the CLI, it only calls the awai API. Each session gets a topic in your private chat with the bot, no matter where the session was created.

1. Create a bot with `/newbot` in [@BotFather](https://t.me/BotFather) and note its token. Then open BotFather's **Mini App** (the Open button next to the message box, not the text menus of `/mybots`), pick your bot, and turn on **Threaded Mode** in its topic (Threads) settings. The Telegram adapter checks this setting on start and logs an error if it is off (the core keeps running).
2. Look up your numeric user ID with [@userinfobot](https://t.me/userinfobot).
3. Open your bot in Telegram and tap Start (or send `/start`).
4. Add this to `~/.awai/config.toml` (see `config.example.toml` for every option):

   ```toml
   [telegram]
   enabled = true
   bot_token = "123456:ABC..."
   user_id = 123456789
   ```

5. Run `awai serve`. If the log says `adapters: telegram`, the Telegram adapter is running too. It shares the process with the core but still talks to it only through the local API. If it crashes it is restarted; a configuration error (wrong token, Threaded Mode off) is logged once instead.

Usage: with topics on, the bot's private chat has no "main chat", only topics. **Create a topic and send `/new ~/code/your-project` in it**: claude starts in that directory, and the topic becomes its conversation (it is renamed after the project). For sessions created on the computer with `awai new`, the bot creates a topic itself.

| Where | Send | Effect |
|---|---|---|
| A topic without a session | `/new ~/code/app` | Start claude in that directory; the topic is now its conversation |
| A topic without a session | `/yolo ~/code/app` | Same, but in YOLO mode; the topic name starts with 🎲 |
| A session topic | plain text | Typed into claude and submitted; claude's own slash commands such as `/compact` are passed through as is |
| A session topic | `/screen` | Show the terminal screen, with Refresh / Esc / Enter / ⇧Tab / 1 2 3 / ↑ ↓ keys below it |
| A session topic | `/keys Down Enter` | Press any keys from the allow-list |
| A session topic | `/restart` | Restart claude and continue the conversation; brings older sessions up to date (hooks) |
| A session topic | `/restart yolo` | Same, and switch to YOLO mode (the topic name gets 🎲); `/restart default` switches back |
| A session topic | `/kill` | End the session |
| Any topic | `/delete` | Delete this topic (after a confirmation; its session is ended first). You cannot delete topics the bot created from the Telegram client, so this lets the bot do it |
| Any topic | `/ls`, `/help` | List sessions, show help |

Typing `/` opens the command menu.

Claude's replies are rendered as Markdown: code blocks, inline code, bold, italic, links, lists and quotes become Telegram formatting. Telegram has no tables, so tables become column-aligned monospace text. The conversion uses Telegram entities rather than `parse_mode`, so escaping can never make a message fail; if Telegram still rejects the formatting, the message is sent again as plain text.

Tool calls arrive as silent messages with a "📺 Screen" button. When Claude stops to wait for a permission answer, tap it to see the screen and answer with the keys below it. Text typed on the computer is shown with a leading 🖥️.

Security: the bot responds only to `user_id`, and only in that user's private chat with the bot. Other people, groups, channels and forwarded messages are ignored without a reply. Requests to Telegram use `HTTPS_PROXY` if you need a proxy; requests to the local awai API never go through a proxy.

## Remote approvals and questions

When Claude asks for permission (to run a command, edit a file, …), Telegram shows:

```
🔐 Claude wants to use Bash
rm -rf build
(Remove build directory)
[✅ Allow] [❌ Deny]
```

When Claude asks a question (say, "Which file should I create?"), each question arrives as its own message with the options as buttons. For multiple-choice questions, tick the options and tap "✅ Done"; you can also answer by replying with text. On the command line, `awai pending` lists open prompts, and `awai allow|deny <ID>` and `awai answer <ID> answer…` answer them.

**The terminal still shows these menus and questions, so you can answer at the computer too; whichever answers first wins.** awai learns about questions and options through hooks that apply only to the sessions it starts (`claude --settings`; your `~/.claude/settings.json` is left alone). The hooks return immediately and never decide anything for you. A remote answer is translated into key presses for the menu, and awai first checks that the menu really is on screen. Deny is the same as pressing Esc in the terminal: Claude stops and waits for you to say what to do next. Details in [docs/api.md](https://github.com/zeocax/awai/blob/master/docs/api.md#remote-approvals-and-questions).

If Claude is in auto mode (the status bar shows `auto mode on`), Claude decides most actions on its own and rarely asks. To approve every action yourself, switch to the normal mode with ⇧Tab, or start with `--mode manual`.

## YOLO mode

```bash
awai new ~/code/app --yolo        # CLI
/yolo ~/code/app                  # Telegram
```

A running session can switch too: `/restart yolo` (or `awai restart <ID> --yolo`) restarts it in YOLO mode and continues the conversation; `/restart default` switches back.

This starts Claude with `--dangerously-skip-permissions`: running commands and changing files no longer ask for confirmation, so no remote approvals appear either. The mode is chosen per session; in Telegram such topics start with 🎲, and a warning is posted on start. In the API, the session's `mode` is `"yolo"`.

It gives nobody who can drive awai any new power: whoever can use the Telegram bot could already tap "Allow", or run shell commands directly with `!`. The real new risk is **prompt injection**: files in the repository or web content can talk Claude into running commands, and in YOLO mode nobody sees a confirmation. Use it only when you trust everything in the directory.

`--mode manual|auto|acceptEdits|plan` picks other permission modes; see [docs/api.md](https://github.com/zeocax/awai/blob/master/docs/api.md#permission-modes).

## Configuration

The configuration file is `~/.awai/config.toml` (set `AWAI_HOME` to use another directory than `~/.awai`). It is split into one section per component, and every section and key is optional. [`config.example.toml`](https://github.com/zeocax/awai/blob/master/config.example.toml) in the repository lists every option with its default:

| Section | Keys | Purpose |
|---|---|---|
| `[server]` | `host`, `port`, `allow_remote`, `allowed_hosts` | The local HTTP API |
| `[sessions]` | `allowed_roots` | Directories sessions may start in (empty: anywhere except `/` and your home directory itself) |
| `[claude]` | `command`, `config_dir` | How to start claude (an argument list; no permission flags) and where its transcripts are |
| `[tmux]` | `socket`, `session` | awai's own tmux server |
| `[telegram]` | `enabled`, `bot_token`, `user_id` | If this section exists (and does not say `enabled = false`), `awai serve` starts the Telegram adapter |

The old flat layout (e.g. a top-level `port = 8787`) is rejected with an error that says which section the key moved to.

## API

See [docs/api.md](https://github.com/zeocax/awai/blob/master/docs/api.md).

## Development

```bash
uv run pytest -q
```

`tests/test_tmux.py` and `tests/test_e2e.py` start throwaway tmux servers (removed when the tests finish). The end-to-end tests use `tests/fake_agent.py` instead of claude, so they need no account and spend no tokens.

## Roadmap

- [x] Telegram adapter (long polling, public API only)
- [ ] Web client (served same-origin by the core)
- [x] Remote permission approvals (session-scoped hooks)
- [x] Remote answers to questions (AskUserQuestion)
- [ ] Remote plan approval (ExitPlanMode)
- [ ] Status detection: working, idle
- [ ] Other agents (Codex, Gemini, …)

## Acknowledgements

awai was inspired by [ccgram](https://github.com/alexei-led/ccgram), which drives Claude Code in tmux from Telegram topics. awai is a separate, much smaller implementation written from scratch.
