Releases: tabforgeai/telegram-claw
v0.3.0 : Self-Managed Trust: PIN Pairing, TTL Tokens & SMS Kill Switch
Before v0.3.0: adding a new authorized user meant editing an environment variable and restarting the relay server.
v0.3.0: the device owner taps "Generate PIN" on the phone, shares the 6-character code out-of-band, and the new user is authorized the moment they send it to the bot — no server restart, no config file edits. Access expires automatically after 30 days and survives relay restarts. If anything goes wrong, a single SMS containing "CLAW KILL" disables every tool and stops the service instantly, from any phone.

v0.2.0 — Full End-to-End Loop on a Real Android Device
Phase 2 Smoke Test — Day 17-19: Full End-to-End Loop with Natural Language Answer
Goal: Verify that a natural language question sent to a Telegram bot travels the complete
Telegram Claw loop — from Telegram, through Claude AI, via FCM to a real Android device,
back to the relay server via HTTP callback, through a second Claude pass, and returns as a
natural language answer in the same language the question was asked.
This is the first test that requires a real Android device with the Claw app installed.
All previous smoke tests could run without one.
What This Test Actually Proves
You type in Telegram: "Spava li on?"
↓
Relay receives message at /webhook endpoint
↓
Claude identifies intent → calls get_device_context tool
Relay stores original question in PendingCallbackStore (30s timeout starts)
↓
FCM push delivered to Android device
↓
Android executes DeviceContextTool:
reads battery, screen state, sound profile, ambient light, motion sensor
↓
Android POSTs result to relay /callback:
{ chatId: 8608523419, tool: "get_device_context",
result: "Battery: 85% (discharging). Screen: off. Sound: silent (vol 0/15).
Light: dark (3 lux). Motion: stationary." }
↓
CallbackReceiver claims original question, cancels timeout
↓
IntentParser.interpretResult() — second Claude call:
"The person asked: 'Spava li on?'
The device returned: Battery 85%, screen off, silent, dark, stationary.
Write a 2-3 sentence answer in Serbian with a conclusion."
↓
Person A sees in Telegram:
"Ekran je ugašen, zvuk je isključen i prostorija je tamna.
Uređaj nije pomeren u poslednjem očitavanju.
Na osnovu toga, verovatno spava."
Two things make this test different from everything before it:
- A real Android device participates — the tool executes on actual hardware,
reading real sensors. The result is not mocked or simulated. - Claude answers, not just dispatches — the response is not Claude's pre-execution
guess ("I'll check the device state...") but a post-execution interpretation of real data.
What Is NOT Tested Here
| Feature | Phase |
|---|---|
| media_control, location_fetcher, camera_capture tools | Phase 3 |
| RSA encryption of command payload | Phase 3, Day 23 |
| QR code pairing ceremony | Phase 3, Day 24 |
| SMS kill switch | Phase 3, Day 25 |
| TTL-limited access tokens | Phase 3, Day 26 |
Prerequisites
Relay server (Phase 1 complete):
| What | Why |
|---|---|
| Telegram bot token (from @Botfather) | Relay receives messages and sends replies |
| Anthropic API key | Intent parsing (first Claude call) + result interpretation (second Claude call) |
| Firebase project + service account JSON | FCM push delivery to Android |
| ngrok | Exposes local relay to Telegram webhook AND to Android /callback |
Android device:
| What | Why |
|---|---|
| Android 8.0+ device with internet | Runs the Claw app |
| Claw app installed (built from this repo) | Receives FCM, executes tools, POSTs callback |
| USB debugging not required | The app runs standalone once installed |
Note on the relay URL: Both Telegram (webhook) and the Android app (callback)
need to reach the relay server. A single ngrok tunnel covers both —
ngrok exposes port 8080 with one public URL, and both/webhookand/callback
are registered on that same port.
One-Time Setup — Android App
Build and install the Claw app from Android Studio, then complete the two-field setup
in the app's main screen before running this test.
1. Get the FCM Device Token
Open the Claw app on the Android device. The FCM Device Token section at the top
of the screen displays the registration token for this device. Copy it — you'll need
it as the FCM_DEVICE_TOKEN environment variable on the relay server.
The token is also printed to Android Studio Logcat (filter by
TelegramClaw):
FCM Token: dU8kXm2T...
2. Configure the Bot Token
In the Claw app, find the Bot Token field. Paste the Telegram bot token
(the same token used by the relay server — from @Botfather). Tap Save Settings.
This is what allows the Android app to fall back to direct Telegram messaging
if the relay callback URL is not set or unreachable.
3. Configure the Relay Callback URL
In the Claw app, find the Relay Callback URL field. Enter the base URL of the
running relay server — the ngrok URL without any path:
https://abc123.ngrok.io ✓ correct
https://abc123.ngrok.io/webhook ✗ wrong — do not include the path
https://abc123.ngrok.io/callback ✗ wrong — the app appends /callback automatically
Tap Save Settings.
If no relay URL is configured, the app falls back to Day 17 behavior:
Android sends the raw result directly to Telegram, bypassing Claude's interpretation.
4. Enable Tool Permissions
In the Claw app, enable the tools needed for this test:
- get_device_context — ON (required for "is he sleeping?" type questions)
- audio_manager — ON (optional, for the volume test below)
- notification_sender — ON (optional, for the notification test below)
All other tools can remain OFF.
Running the Test
Step 1 — Start ngrok
ngrok http 8080
Copy the https:// URL. You will use it for both WEBHOOK_URL and the Relay Callback URL in the app.
Step 2 — Build and start the relay server
cd D:\AndroidStudioProjects\TelegramClaw\telegram-claw-relay
mvn package -q
$env:TELEGRAM_BOT_TOKEN = "1234567890:ABCdef..."
$env:WEBHOOK_URL = "https://xxxx.ngrok.io/webhook"
$env:ANTHROPIC_API_KEY = "sk-ant-..."
$env:GOOGLE_APPLICATION_CREDENTIALS = "C:\Users\yourname\firebase-key.json"
$env:FCM_DEVICE_TOKEN = "dU8kXm2T...4Yv9q"
java -jar target\telegram-claw-relay-1.0-SNAPSHOT.jarExpected startup output — check these lines:
TELEGRAM_BOT_TOKEN: SET
WEBHOOK_URL: https://xxxx.ngrok.io/webhook
ANTHROPIC_API_KEY: SET
GOOGLE_APPLICATION_CREDENTIALS: C:\Users\yourname\firebase-key.json
FCM_DEVICE_TOKEN: SET
Webhook registered. Telegram confirmed: "Webhook was set."
IntentParser initialized | model: claude-haiku-4-5-20251001 | tools: 6
Server is up on port 8080. Waiting for Telegram updates...
Step 3 — Test 1: Device context query
Send a message to the bot asking about the device state. Try in Serbian or English:
| What you send | Expected tool |
|---|---|
Spava li on? |
get_device_context |
Is he sleeping? |
get_device_context |
Koliko mu baterije ima? |
get_device_context |
Is he awake? |
get_device_context |
Expected relay log (in order):
[MESSAGE] From: Cvele (id=8608523419) | Text: "Spava li on?"
[INTENT] Tool: get_device_context | Params: {}
[COMMAND] Tool: get_device_context | Params: {} | ChatId: 8608523419
[PENDING] Stored for chatId=8608523419 | timeout=30s | question="Spava li on?"
[FCM] Dispatched 'get_device_context' → device. Message ID: projects/.../messages/...
[CALLBACK] tool=get_device_context | chatId=8608523419 | result=Battery: 85%...
[PENDING] Claimed for chatId=8608523419
[INTERPRET] get_device_context → Ekran je ugašen, zvuk je isključen...
[REPLY] Sent to chatId=8608523419: "Ekran je ugašen..."
Expected Android Logcat (filter by TelegramClaw):
[FCM] Command received | tool=get_device_context | chatId=8608523419
[EXECUTE] tool=get_device_context | chatId=8608523419
[get_device_context] Battery: 85% (discharging). Screen: off. Sound: silent...
Callback posted to relay | chatId=8608523419 tool=get_device_context
Expected Telegram message received by Person A:
A single natural language message in the same language as the question, ending with
a clear conclusion. Example:
"Ekran je ugašen, zvuk je isključen i prostorija je tamna. Uređaj nije pomeren.
Na osnovu toga, verovatno spava."
"The screen is off, the device is on silent, and the ambient light is very low.
The device appears stationary. Based on this, they are most likely sleeping."
This single message — in the question's language, with a conclusion — is the smoke test passing.
Step 4 — Test 2: Action tool with callback confirmation
Send a volume command. Unlike query tools, action tool replies are sent immediately
by the relay without waiting for a callback interpretation:
Pojacaj mu zvono na 80%
Expected Telegram reply (immediate, from relay):
"Pojačavam zvono na 80%. Šaljem komandu na uređaj."
Then, a second message arrives from Android via callback:
"Volume set to index 12/15 (80%)"
Step 5 — Test 3: Offline device (timeout)
This test verifies the 30-second timeout behavior when the device is unreachable.
- Power off the Android device (or disable its internet connection)
- Send
Is he sleeping?to the bot - Wait approximately 30 seconds
Expected Telegram message:
"The device did not respond. It may be offline or out of battery."
Expected relay log:
[PENDING] Stored for chatId=8608523419 | timeout=30s
[FCM] Dispatched 'get_device_context' → device. Message ID: ...
[TIMEOUT] No callback from device for chatId=8608523419 — sending offline n...