Skip to main content

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 and Claude Code. Developed and tested on macOS; Linux should work but is untested.

uv tool install awai      # or: pipx install awai

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

Quick start

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:

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 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.

  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):

    [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.

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

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.

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 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.

Development

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

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

Acknowledgements

awai was inspired by ccgram, which drives Claude Code in tmux from Telegram topics. awai is a separate, much smaller implementation written from scratch.

Metadata

Release files for awai 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for awai 0.1.1
File Size Uploaded
awai-0.1.1.tar.gz 83.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for awai 0.1.1
File Interpreter ABI Platform
awai-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 141.7 kB

Release files / awai-0.1.1.tar.gz

Download URL awai-0.1.1.tar.gz
Size 83.0 kB
Tags Source
SHA-256 checksum
How to use checksums
aa8638a220f582df2c77d50f99c98369ce17e3fe660964b1322969de5c83d0e5
BLAKE2b-256 checksum
How to use checksums
9114d176bfe288499d46ab66deb16c6ae26e85b0d84658d8a891dea8f576c9f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.12

Release files / awai-0.1.1-py3-none-any.whl

Download URL awai-0.1.1-py3-none-any.whl
Size 58.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2e82b4dd9c4f4d7bbca096a0fc43a38727b22c49eddc5619d2f3f4a75868fc9f
BLAKE2b-256 checksum
How to use checksums
f3d6b7769fd1f98a8ab8ea10fa4817f79349f0518d845f6702a8e16eee3f7ec6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.12

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page