Your personal agent in Telegram, using your real browser and logins.
Hometabs is a personal AI agent you talk to in Telegram. It works in the browser on your Mac at home, with your logins, cookies, bookmarks and password manager, so it can do things on any website you can use yourself, including the ones no app or integration covers: your local bank, the utility company, a government portal. Ask it from your phone to check an order, look something up in an account or fill in a form; it shows its progress step by step, sends screenshots, and asks before doing anything irreversible.
- Your real browser. Works with Aside Browser: the agent uses your tabs and sessions instead of a fresh cloud browser.
- Any website. If you can do it in your browser, you can ask for it.
- Lives in Telegram. No new app to install on your phone.
- Runs on your own Mac. Open source; nothing in between except Telegram and the Claude API.
Under the hood, Hometabs hands your messages to a Claude Agent SDK agent, which drives the Aside Browser through Aside's MCP server.
Hometabs is an unofficial, community project. It is not affiliated with, endorsed by or supported by Anthropic, Aside or Telegram. Aside, Claude and Telegram are trademarks of their respective owners.
The agent acts in your real browser, which may be signed in to your accounts. AI agents make mistakes and can be manipulated by content on the pages they read. Use it at your own risk and read SECURITY.md first.
- Real browser control. The agent can use every tool the Aside MCP server
exposes, now and in future Aside versions:
repl(Playwright-style JavaScript in your Aside Browser, so it sees your tabs, cookies and logins),memory_search(Aside's notes about you, your accounts and sites) andexec(hands a task to Aside's own agent). - Allowlist. Only the Telegram user IDs you list can use it; it won't start without the list.
- Locked down. No shell or file tools, no
~/.claudesettings, CLAUDE.md, plugins or other MCP servers: just the aside MCP tools and an allowlisted set of skills. - Asks first. Instructed to confirm in chat before purchases, sending messages, posting, submitting forms, deleting, changing settings or accepting terms.
- Password manager sign-in. Can sign in with the browser's 1Password autofill when a task needs it; never types secrets from chat.
- Send images. Photos, screenshots and image files (PNG, JPEG, WebP, GIF, HEIC, ...) go to the agent with your caption as the instruction; an album becomes one request. See Sending images.
- Persistent conversations. One conversation per chat, resumed after restarts.
- Live progress. A typing indicator, a status line with the current browser
step,
/stopto interrupt, and a queue for messages sent mid-task. - Model and effort switching.
/modeland/effortwith the model list discovered from Anthropic's API, so new models show up on their own. - Phone-friendly replies. Markdown converted to Telegram HTML, long replies split, screenshots forwarded as photos.
- One credential, nothing else. Uses
ANTHROPIC_API_KEY(recommended),CLAUDE_CODE_OAUTH_TOKENor your local Claude Code login; every other credential and provider variable is removed from the agent's environment.
- macOS with the Aside Browser and its CLI
installed (
aside mcpmust work). - Claude access: an Anthropic API key (recommended), or a Claude Code login (see Authentication). The Agent SDK bundles the Claude Code CLI it runs.
- A Telegram bot token from @BotFather.
- uv (installs Python 3.10+ for you).
- Optional: the 1Password browser extension in Aside, for sign-ins.
The agent works in the Aside Browser on the machine where Hometabs runs, so that Mac needs to be on, awake and logged in for the bot to answer.
- Best: an always-on Mac, such as a Mac mini on your desk or a spare MacBook kept plugged in. Your agent is then reachable from Telegram any time, wherever you are.
- Fine for trying it out: your everyday laptop. The bot answers while the laptop is awake; when it sleeps, the bot goes quiet. Telegram holds on to messages you send in the meantime, and the bot works through them when the laptop wakes up, so keep that in mind before sending something like "pay this bill" to a sleeping Mac. Messages sent while Hometabs itself wasn't running are different: they are skipped at startup, on purpose, so a stale request doesn't fire hours later.
Tip: to keep a dedicated Mac awake, open System Settings → Energy and turn on "Prevent automatic sleeping when the display is off" (on a MacBook: Battery → Options → "Prevent automatic sleeping on power adapter when the display is off").
-
Get the code
git clone https://github.com/emanuilo/hometabs.git cd hometabs uv sync cp .env.example .env -
Claude credential: create an API key in the Claude Console and put it in
ANTHROPIC_API_KEY. See Authentication for the alternatives. -
Telegram bot: message @BotFather, send
/newbot, and put the token inTELEGRAM_BOT_TOKEN. -
Your Telegram user ID: ask @userinfobot, or start the bot and message it: rejected users are logged with their ID and told it in a private chat. Put it in
ALLOWED_USER_IDS. -
Run
uv run hometabs # or: uv run python -m hometabsThen send your bot something like "Open example.com and tell me the heading".
Hometabs picks exactly one Claude credential, in this order:
| Order | Setting | Mode logged at startup |
|---|---|---|
| 1 | ANTHROPIC_API_KEY |
api_key |
| 2 | CLAUDE_CODE_OAUTH_TOKEN |
oauth_token |
| 3 | neither: your local Claude Code login | local_login |
- API key (recommended). Create one in the Claude Console; usage is billed to that Console account. Anthropic asks developers building with the Agent SDK to use API key authentication. A dedicated key with a spend limit is a good idea.
- Subscription token (optional, personal use).
claude setup-tokenprints a long-lived token for your Claude plan; put it inCLAUDE_CODE_OAUTH_TOKEN. Anthropic restricts how Claude.ai plan credentials may be used outside its own apps, so read Anthropic's authentication and credential use policy and check the terms for your plan before you use this. - Local login. With neither set, the Claude Code CLI uses whatever login
it finds on the machine (
claude, then/login). The same terms apply.
If both are set, the API key is used and the startup log says so. Every other
credential or provider variable (ANTHROPIC_AUTH_TOKEN,
CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX,
CLAUDE_CODE_USE_FOUNDRY, and whichever of the two above wasn't chosen) is
removed before the Claude CLI starts. Credential values are never logged.
Settings are read from the environment or .env (see
.env.example).
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
yes | Bot token from @BotFather | |
ALLOWED_USER_IDS |
yes | Comma-separated Telegram user IDs allowed to use the bot | |
ANTHROPIC_API_KEY |
no | Anthropic API key (recommended; takes precedence) | |
CLAUDE_CODE_OAUTH_TOKEN |
no | local Claude Code login | Token from claude setup-token (see Authentication) |
ASIDE_COMMAND |
no | aside on PATH, else ~/.local/bin/aside |
Aside CLI, started as aside mcp |
ASIDE_SKILLS_DIR |
no | ~/.aside/u/0/skills/builtin |
Where Aside's builtin skills are copied from |
HOMETABS_MODEL |
no | claude-sonnet-5-5 |
Default model (overridden by /model) |
HOMETABS_EFFORT |
no | medium |
Default effort: low, medium, high, xhigh, max (overridden by /effort) |
STATE_FILE |
no | .state/sessions.json |
Session store; its directory is the state dir |
LOG_LEVEL |
no | INFO |
DEBUG, INFO, WARNING or ERROR |
The state dir (.state/ by default) holds:
| File | Contents |
|---|---|
sessions.json |
Chat ID → Claude session ID, plus a fingerprint of the agent's instructions |
settings.json |
The model and effort chosen with /model and /effort |
models_cache.json |
The model list from /v1/models, cached for 24h |
plugin/ |
The skills plugin, rebuilt at every start |
The conversations themselves are stored by Claude Code as session transcripts
under ~/.claude/projects/.
| Command | Effect |
|---|---|
/start |
Help (also /help) |
/new |
Forget the conversation and start fresh |
/stop |
Interrupt the running task and drop any queued messages |
/model |
Show the current model with buttons to switch; /model <id> switches directly |
/effort |
Show the reasoning effort with buttons for the levels the model supports; /effort <level> sets it |
Messages sent while a task runs are queued and handled in order (one agent turn per chat at a time). Messages sent while the bot wasn't running are dropped on startup, so stale requests don't run in your browser.
Send a photo, a screenshot or an image file (sent "as file", uncompressed) and the caption is the instruction, e.g. a screenshot of an error with "what does this mean?" or a photo of a bill with "pay this on the utility's site". Without a caption, the agent describes the image and asks what to do with it.
- Albums (several photos sent together) become one request with all the images and the album's caption.
- Up to 10 images per request. Telegram lets bots download files up to 20 MB; larger files get a reply saying so.
- JPEG, PNG, GIF and WebP are sent as they are when small enough; other formats (HEIC, BMP, TIFF, ...) are converted to JPEG, and large images are scaled down to 1568 px on the long edge (Claude's own working size) and kept under Claude's per-image size limit.
- Images are queued like text messages, and
/stopand/newdrop them too. - The agent sees the images but can't upload them to websites: they are passed to the model, not saved as files the browser could attach.
- Voice messages, videos, stickers and other files get a short reply that only text and images are supported.
Images are sent to Anthropic along with the conversation and are stored in the Claude Code session transcripts. See SECURITY.md.
The model list isn't hardcoded: /model fetches it from Anthropic's
GET /v1/models with your credential (the API key as x-api-key, or the
OAuth token as a bearer token; sent only to api.anthropic.com). With the local
Claude Code login there is no credential to send, so no list is fetched. Each
model's supported effort levels come from the same response, limited to the
levels the installed Agent SDK accepts. If the list can't be fetched, the last
cached list is used; with no list at all, /model <id> still accepts any
claude-... ID.
The choice is bot-wide and persisted. A change applies from the next message and keeps the conversation: the chat's agent reconnects, resuming the same session with the new model and effort. A task that is already running finishes with the old setting. If the new model doesn't support the chosen effort, the nearest lower supported level is used (or effort is omitted for models without effort support), and the bot tells you.
flowchart LR
phone["Telegram app<br/>(your phone)"] <--> tg["Telegram Bot API"]
subgraph mac["Your Mac"]
bot["bot.py<br/>allowlist, per-chat queue,<br/>progress, formatting"]
agent["agent.py<br/>BrowsingAgent<br/>(ClaudeSDKClient per chat)"]
state[(".state/<br/>sessions, settings,<br/>model cache, plugin")]
cli["Claude Code CLI<br/>(bundled with the SDK)"]
mcp["aside mcp<br/>(repl, memory_search,<br/>exec, ...)"]
browser["Aside Browser<br/>your tabs and logins"]
bot --> agent
bot <--> state
agent -->|spawns| cli
cli -->|stdio| mcp
mcp --> browser
end
tg <-->|long polling| bot
cli <-->|"HTTPS, API key or token"| anthropic["Anthropic API"]
When you send a message:
- The bot long-polls Telegram (no open ports), checks the sender against the allowlist and takes the chat's lock; if a task is running, the message is queued.
- The chat's
BrowsingAgentis created on first use: it spawns the Claude Code CLI with a fixed system prompt, the aside MCP server, the skills plugin and an environment holding only the chosen Claude credential, resuming the chat's saved session if there is one. - Claude works in a loop, calling the aside MCP tools: mostly
replto read and act on pages,memory_searchwhen Aside's notes about you would help, andexecwhen handing the whole task to Aside's own agent is clearly better. Anexectask runs on your Aside plan and may wait for a confirmation that can only be answered in the Aside desktop app; the agent is told to say so. Each tool call updates the "Working… step n" status message (at most every 2 seconds). - The final reply is converted to Telegram HTML, split if needed and sent with up to five screenshots. The session ID is saved so the conversation survives restarts.
Sessions started under different instructions (system prompt or skills) are not resumed, because Claude Code keeps a session's original system prompt; you get a note that a fresh conversation started.
See docs/architecture.html for detailed diagrams of the architecture, the message flow and how history is kept.
The agent loads skills only from a local Claude Code plugin named hometabs
that the bot builds at startup in .state/plugin/ (passed to the CLI with
--plugin-dir). Your ~/.claude settings, CLAUDE.md and personal skills and
plugins are never loaded, and only the plugin's skills are allowlisted.
hometabs:sign-in(shipped with this project, MIT): tells the agent to sign in with the browser's password manager autofill whenever a task needs a login, and how to click 1Password's autofill menu reliably.- Aside's builtin skills listed in
ASIDE_SKILLSinsrc/hometabs/skills.py(currently1password, loaded ashometabs:1password) are copied unmodified from your local Aside install (ASIDE_SKILLS_DIR). They belong to Aside and are not distributed with this project. If one isn't found, the bot logs a warning and runs without it.
Because the plugin is rebuilt on every start, an Aside update is picked up on
the next restart (and starts fresh conversations, since the instructions
changed). To use another Aside skill, add its directory name to
ASIDE_SKILLS. The startup log lists the skills that were loaded.
The bot gives a Telegram chat control of a logged-in browser. Prompt injection from web pages is the main residual risk, and the "ask before irreversible actions" rule is an instruction to the model, not an enforced control. Use a dedicated browser profile, keep the allowlist tight and keep your tokens and keys secret. See SECURITY.md for the threat model and how to report vulnerabilities.
To keep the bot running in the background and start it at login, use a launchd
LaunchAgent (it must run in your login session, where the Aside Browser runs).
docs/hometabs.plist.example is a template:
# Edit the paths in the template first, then:
cp docs/hometabs.plist.example ~/Library/LaunchAgents/com.hometabs.bot.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.hometabs.bot.plist
# Restart after an update / stop it:
launchctl kickstart -k gui/$(id -u)/com.hometabs.bot
launchctl bootout gui/$(id -u)/com.hometabs.botLogs go to ~/Library/Logs/hometabs.log. Run only one instance per bot token:
Telegram allows a single long-polling client.
uv sync
uv run pytest # tests (no network, keys, Aside or Telegram needed)
uv run ruff check # lint
uv run ruff format # formatsrc/hometabs/
__init__.py # entry point: logging, config, env scrubbing, polling
agent.py # agent core (no Telegram): ClaudeSDKClient + aside MCP
auth.py # credential choice and the scrubbed env for the Claude CLI
bot.py # Telegram handlers, per-chat locks, progress, /model, /effort
config.py # .env loading and validation
formatting.py # message splitting, Markdown -> Telegram HTML
images.py # incoming images: size limits, conversion, downscaling
models.py # /v1/models discovery (model list, effort levels, cache)
skills.py # builds the skills plugin (our skills + Aside's)
plugin/ # plugin template: manifest + skills we ship
tests/ # pytest suite
docs/ # architecture diagrams, launchd example
See CONTRIBUTING.md for guidelines.
MIT. Aside's skills loaded at runtime are not part of this project and remain under Aside's terms.