Your AI infrastructure, in your pocket.
PAI Mobile is a Telegram gateway that connects your phone to Claude Code, giving you full AI assistant access from anywhere. Send a message on Telegram, get a Claude Code response back — including file access, terminal commands, and multi-turn conversations.
Status: v2.1.2 — Stable release with session memory, image handling, PAI memory integration, and skill command discovery. Running daily on the author's machine. Your feedback will shape what this becomes.
PAI is powerful, but it lives on your laptop. You have to be at your desk, in a terminal, to use it. That's a real limitation — ideas don't wait for you to sit down.
PAI Mobile was inspired by OpenClaw's vision of autonomous, always-accessible AI. OpenClaw showed what's possible when your AI can reach you proactively — checking in, running scheduled tasks, alerting you when something matters. PAI Mobile brings that same philosophy to the PAI ecosystem:
- Reactive — Ask questions, run tasks, manage files from your phone
- Proactive — Heartbeat monitoring and scheduled tasks that reach out to you
- Autonomous — Runs as a system service, survives reboots, self-recovers from crashes
This is a PAI plugin, not a replacement. It extends your existing PAI setup with a mobile interface. Your skills, memory, and configuration all work through it.
From your phone:
- Ask quick questions (weather, lookups, calculations) — routed to fast, stateless lite mode
- Run complex multi-turn tasks (coding, file management, research) — routed to full Claude Code with tool access
- Send images — photos and image documents are downloaded and forwarded to Claude for analysis
- Persistent context — conversations remember what you were talking about, even across session restarts
- Switch between multiple conversation sessions
- Cancel running tasks, manage your queue
Proactively (the bot reaches out to you):
- Heartbeat — Periodic check-ins that evaluate a checklist you define. Only alerts when something is actionable. Silence means everything is fine.
- Cron — Scheduled tasks with natural language ("daily 7am", "weekdays 9am", "every 2 hours"). One-shot or recurring.
Commands:
| Command | What it does |
|---|---|
/new [name] |
Start a new conversation session |
/sessions |
List all sessions |
/switch <id> |
Switch to a different session |
/lite |
Lock to lite mode (fast, no tools) |
/full |
Lock to full mode (Claude Code with tools) |
/auto |
Restore auto-routing |
/cancel |
Kill current task, clear queue |
/status |
Show session info |
/pause |
Pause all proactive behavior |
/resume |
Resume proactive behavior |
/heartbeat |
Check heartbeat status |
/cron list |
List scheduled tasks |
/cron add "name" "schedule" "prompt" |
Create a scheduled task |
/cron remove <id> |
Delete a scheduled task |
/cron toggle <id> |
Enable/disable a task |
/cron run <id> |
Manually trigger a task |
/help |
Show all commands |
/<skill> |
Run any installed PAI skill (see below) |
PAI Skill Commands:
The gateway automatically discovers skills installed at ~/.claude/skills/ and registers them as Telegram bot commands. Type / in your chat to see the full menu of available skills alongside built-in commands.
For example, if you have a Research skill installed, /research quantum computing will invoke it through Claude Code in full mode. Skills are discovered at startup — restart the gateway to pick up newly installed skills.
Before you start, you need:
- Claude Code CLI — installed and authenticated. Run
claude --versionto verify. If not installed: docs.anthropic.com/en/docs/claude-code - Claude Max subscription — PAI Mobile uses
claude -pwhich bills to your Max plan, not API credits. No API key needed. - Node.js 18+ — Run
node --versionto check. Install via nodejs.org orbrew install node - A Telegram bot token — You'll create one in the next section
- Your Telegram chat ID — You'll get this in the next section
- Open Telegram and search for @BotFather
- Send
/newbot - Choose a display name (e.g., "My PAI Mobile")
- Choose a username (must end in
bot, e.g.,my_pai_mobile_bot) - BotFather will reply with your bot token — save it, you'll need it next
- Open Telegram and search for @userinfobot
- Send
/start - It replies with your chat ID (a number like
123456789) — save it
git clone https://github.com/jdrolls/pai-mobile.git
cd pai-mobile
npm installcp .env.example .envOpen .env in your editor and fill in:
# Required — your bot identity
BOT_NAME=MyBot # Whatever you want to call your bot
USER_NAME=YourName # Your name (used in system prompts)
# Required — from Steps 1 and 2
TELEGRAM_BOT_TOKEN=your-bot-token-here
TELEGRAM_CHAT_ID=your-chat-id-hereThat's the minimum. The rest of .env has optional settings with sensible defaults:
| Variable | Default | What it controls |
|---|---|---|
BOT_NAME |
PAI Mobile |
Bot's name in prompts |
USER_NAME |
User |
Your name in prompts |
TELEGRAM_BOT_TOKEN |
— | Required. From @BotFather |
TELEGRAM_CHAT_ID |
— | Required. Comma-separated for multiple users |
TIMEZONE |
America/Denver |
For heartbeat active hours and cron schedules |
LITE_MODEL |
sonnet |
Model for lite mode |
FULL_MODEL |
sonnet |
Model for full mode |
PERMISSION_MODE |
default |
Claude Code permission mode (see Security) |
MAX_MESSAGES_PER_MINUTE |
20 |
Rate limit per chat |
MAX_CONCURRENT_CLAUDE |
3 |
Max simultaneous claude -p processes |
# Dev mode (auto-restarts on file changes)
npm run devOpen Telegram, find your bot, and send it a message. You should get a response within a few seconds.
If it works — you're done with basic setup.
To keep your bot running permanently on macOS (survives reboots, auto-restarts on crash):
# Install the launchd service
./manage.sh install
# Start it
./manage.sh start
# Verify it's running
./manage.sh statusOther service commands:
./manage.sh stop # Stop the service
./manage.sh restart # Restart
./manage.sh logs # Tail the log file
./manage.sh uninstall # Remove the serviceThe heartbeat system periodically checks a file you define and alerts you only when something needs attention.
-
Copy the example config:
cp data/heartbeat-config.example.json data/heartbeat-config.json
-
Edit
data/heartbeat-config.jsonand set"enabled": true -
Create your heartbeat checklist at
~/.claude/HEARTBEAT.md:# Heartbeat Checklist Check these and alert me only if something needs action: - [ ] Are any critical services down? - [ ] Any disk usage above 90%? - [ ] Any failed cron jobs in the last hour?
-
Restart the gateway:
./manage.sh restart
The heartbeat runs hourly during active hours (default 7am–10pm). If everything is fine, it stays silent. If something is actionable, you get a Telegram message.
Schedule recurring AI tasks. Add them via Telegram:
/cron add "morning-briefing" "daily 7am" "Give me a brief summary of today's calendar and priorities"
Or use standard cron expressions:
/cron add "weekly-review" "0 9 * * 1" "Review my project status and flag anything behind schedule"
Natural language schedules supported: daily 7am, weekdays 9am, every 2h, weekly monday 8am, monthly 1st 9am
Telegram → index.ts (polling) → classifier.ts (keyword routing)
├── lite.ts → claude -p (stateless, no tools)
├── full.ts → claude -p (session resume, full tools)
│ ↓
│ transcript.ts (JSONL safety net)
│ memory.ts (permanent memory + daily logs)
└── skills.ts (PAI skill discovery → command menu)
Proactive systems:
heartbeat.ts → claude -p (read-only) → outbound-queue.ts → Telegram
cron.ts → claude -p (isolated) → outbound-queue.ts → Telegram
- All inference goes through
claude -p— no direct API calls. Uses your Claude Max subscription. - Lite mode is stateless — every message gets a fresh context. Fast, cheap, no context poisoning.
- Full mode resumes sessions — multi-turn conversation continuity via
--resume. - Session memory — three-layer persistence: transcript JSONL (safety net), permanent memory (MEMORY.md), and daily interaction logs. See Session Memory below.
- Mode stickiness — sessions auto-lock to full mode for context continuity. Use
/liteto opt out. - Resume failure detection — if Claude prunes a session, the bot detects it and re-injects conversation context.
- Classifier uses keyword regex, not LLM — zero overhead routing.
- Outbound queue bundles proactive messages (5 cron results at 8am = 1 Telegram message, not 5).
- Image support — photos and image documents sent via Telegram are downloaded and forwarded to Claude.
Edit prompts/lite-system.md and prompts/full-system.md to customize your bot's personality and behavior. Use {{BOT_NAME}} and {{USER_NAME}} placeholders — they're replaced at runtime from your .env config.
Edit src/classifier.ts to tune which messages route to lite vs full mode. The default uses keyword patterns — add your own for your use case. Override anytime with /lite or /full.
The included skills/CreateCron/ skill lets Claude create cron jobs programmatically from natural language when running in full mode. It can read and write to data/cron/jobs.json directly.
PAI Mobile uses a three-layer memory system for conversation persistence:
- Location:
data/transcripts/{sessionId}.jsonl - Format: Append-only JSONL with
{role, content, ts}entries - Purpose: When Claude prunes a session and
--resumefails silently, the transcript provides fallback context - Injection: Only when
--resumeis unavailable — never alongside a successful resume - Simple truncation (last N entries within character budget), not LLM compaction
- Location:
~/.claude/MEMORY/TELEGRAM/MEMORY.md+ localdata/memory/MEMORY.md - Size cap: 8,000 characters
- Loaded: Injected into system prompt on new sessions
- Purpose: Cross-session knowledge that survives session boundaries
- Integrates with PAI's native memory system — desktop Claude Code sessions can discover Telegram context
- Location:
~/.claude/MEMORY/RELATIONSHIP/YYYY-MM/YYYY-MM-DD.md+ localdata/memory/daily/ - Format: Timestamped interaction bullets (e.g.,
[Telegram 14:32] Discussed X) - Purpose: PAI's
LoadContexthook automatically reads today + yesterday's relationship notes, so Telegram interactions surface in desktop sessions with zero configuration
- Detected by comparing the returned
session_idto the storedclaudeSessionId - Different IDs = session was pruned, resume failed silently
- Next call prepends transcript context to the user message
contextRecoveryflag on the session tracks this state
--resumeis the primary context mechanism; transcript is a fallback only- Transcript is never injected alongside a successful
--resume(avoids duplicate context) - Simple truncation over LLM compaction (deterministic, zero latency, no semantic drift)
- File-based only, no database
- Chat ID whitelist — Only Telegram accounts listed in
TELEGRAM_CHAT_IDcan interact with the bot. All other messages are silently dropped. - Default permission mode is
default— Claude asks for confirmation before risky actions (file edits, shell commands). You can change this toacceptEditsorbypassPermissionsin.env, but understand: a compromised Telegram account would gain whatever permissions you grant. - Self-modification protection — The full mode system prompt prevents Claude from modifying the bot's own source code or restarting its process.
- System session isolation — Heartbeat and cron processes run with a stripped environment (allowlist: HOME, PATH, USER, TMPDIR, TERM, LANG, SHELL, CLAUDE_CONFIG_DIR only). No secrets, no bot token.
- Rate limiting — Per-chat message limits and global concurrent process limits prevent resource exhaustion.
- Never commit your
.env— It's in.gitignore.
- macOS only for the launchd service management. The bot itself runs anywhere Node.js does, but
manage.sh installgenerates macOS-specific plists. Linux systemd support is planned. - One poller per bot token — If another process polls the same Telegram token, both break. The bot runs
deleteWebhook()at startup to clear conflicts. - Telegram message limit — Responses over 4096 characters get chunked. Complex output may look fragmented.
- Session pruning — If Claude Code prunes old sessions,
--resumefails silently. The transcript safety net detects this and recovers context automatically, but the first response after pruning may have slightly less context.
If you're a PAI community member testing this out, I'd love to hear:
- What works well, what doesn't
- What features you'd want next
- Any bugs or unexpected behavior
- How you're using it — what tasks, what schedules
Open an issue on this repo, or drop feedback in the PAI Discussions.
MIT