Control Cursor IDE from anywhere — Telegram, Slack, Discord, AI
Send prompts, get screenshots, manage your AI coding sessions from your phone or any device.
┌─────────────────────────────────────────────────────────┐
│ INTERFACES │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Telegram │ │ Slack │ │ Discord │ │ AI │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬──────┴─────────────┘ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ CORE (WebSocket) │ │
│ │ ws://localhost:9876 │ │
│ └────────────┬────────────┘ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ CursorController │ │
│ │ (AppleScript) │ │
│ └────────────┬────────────┘ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Cursor IDE │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
git clone https://github.com/ivanpasichnyk/cursor-remote.git
cd cursor-remote
pip install websockets python-telegram-bot- Open Telegram, message @BotFather
- Send
/newbot, follow instructions - Copy the bot token (looks like
123456789:ABCdefGHI...)
- Message @userinfobot on Telegram
- Copy your numeric ID (looks like
394755411)
cp env.example .envEdit .env:
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
ALLOWED_USER_ID=394755411The bot uses AppleScript to control Cursor. macOS requires permission:
- Open System Settings → Privacy & Security → Accessibility
- Click + and add Terminal (or your terminal app)
⚠️ Without this, the bot will connect but can't control Cursor!
# Make sure Cursor IDE is open!
python examples/run_telegram.pyOpen your bot in Telegram:
/help— see all commands/screen— take screenshot/mode— toggle Agent/Ask mode- Send any text — it goes to Cursor AI
| Command | Description |
|---|---|
/screen |
Take screenshot |
/up / /down |
Scroll up/down + screenshot |
/accept |
Accept (Cmd+Enter) |
/reject |
Reject (Cmd+Backspace) |
/stop |
Stop (Escape) |
/enter |
Press Enter |
/mode |
Toggle mode (Cmd+.) |
/lang |
Switch keyboard layout (Cmd+Space x2) |
/ai |
AI model selector |
/file <path> |
Open file |
/run <cmd> |
Run shell command |
/terminal <cmd> |
Run in IDE terminal |
<any text> |
Send as prompt |
The bot uses AppleScript to simulate keyboard input. Key codes used:
| Key | Code | Notes |
|---|---|---|
| Arrow Up | 126 | Used for scrolling (10 presses = 1 page) |
| Arrow Down | 125 | Used for scrolling |
| Enter | 36 | Submit prompts |
| Escape | 53 | Stop AI generation |
| Backspace | 51 | Reject with Cmd |
| Space | 49 | Used with Cmd for language switch |
| Period | 47 | Mode switch (Cmd+.) |
| Slash | 44 | AI selector (Cmd+/) |
The /lang command uses Cmd+Space twice:
- First press opens the language selector menu
- Second press (after 0.5s delay) switches to next language
This is required because macOS Cmd+Space first shows a menu, then cycles through layouts.
Scrolling uses Arrow Keys (not Page Up/Down) because:
- Arrow keys work reliably in all contexts (editor, chat, panels)
- Page Up/Down may not work in focused text areas
- 10 arrow key presses ≈ 1 page of content
cursor-remote/
├── cursor_core/ # Core module (WebSocket server)
│ ├── controller.py # AppleScript commands
│ ├── server.py # WebSocket server
│ ├── protocol.py # JSON message format
│ └── keybindings.json # Cursor/VSCode keyboard shortcuts
├── interfaces/ # External interfaces
│ └── telegram/ # Telegram bot
│ └── bot.py
├── examples/
│ └── run_telegram.py # Launcher script
├── README.md
├── requirements.txt
└── env.example
cursor_core/keybindings.json contains Cursor IDE keyboard shortcuts.
Use cases:
- Voice commands → translate to keyboard actions
- AI reasoning → plan complex actions from natural language
- Future: LLM-based controller that maps "open settings" →
Cmd+,
Example:
{ "key": "cmd+p", "command": "workbench.action.quickOpen" }
{ "key": "cmd+i", "command": "composer.startComposerPrompt" }
{ "key": "cmd+.", "command": "composer.cycleMode" }Interfaces communicate with Core via JSON over WebSocket:
Request:
{"action": "screenshot"}
{"action": "send_prompt", "params": {"text": "Hello"}}
{"action": "switch_mode"}Response:
{"success": true, "message": "Screenshot taken", "data": {"path": "/tmp/screen.png"}}
{"success": false, "message": "Error message"}- Create
interfaces/your_interface/bot.py - Connect to
ws://localhost:9876 - Send JSON commands, receive JSON responses
- Handle responses in your interface
Example (Python):
import websockets
import json
async with websockets.connect("ws://localhost:9876") as ws:
# Take screenshot
await ws.send(json.dumps({"action": "screenshot"}))
response = json.loads(await ws.recv())
print(response) # {"success": true, "data": {"path": "/tmp/..."}}- macOS only — Uses AppleScript for IDE control
- Accessibility permissions — Grant to Terminal/Python
- Cursor or VSCode — Must be running
- Python 3.9+
websockets>=12.0
python-telegram-bot>=20.0
cd cursor-remote
TELEGRAM_BOT_TOKEN=your_token ALLOWED_USER_ID=your_id python3 examples/run_telegram.pyOr with .env file:
export $(grep -v '^#' .env | xargs) && python3 examples/run_telegram.pyCreate ~/Library/LaunchAgents/com.cursor-remote.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.cursor-remote</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/path/to/cursor-remote/examples/run_telegram.py</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>TELEGRAM_BOT_TOKEN</key>
<string>your_bot_token</string>
<key>ALLOWED_USER_ID</key>
<string>your_telegram_id</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/cursor_remote.out</string>
<key>StandardErrorPath</key>
<string>/tmp/cursor_remote.err</string>
<key>WorkingDirectory</key>
<string>/path/to/cursor-remote</string>
</dict>
</plist>Load and start:
launchctl load ~/Library/LaunchAgents/com.cursor-remote.plistStop:
launchctl unload ~/Library/LaunchAgents/com.cursor-remote.plistCheck logs:
tail -f /tmp/cursor_remote.errBot conflict error (terminated by other getUpdates):
pkill -f run_telegram.py
# Then restartCheck if running:
ps aux | grep run_telegramCommands not working (no response from Cursor):
- Check Accessibility permissions:
- System Settings → Privacy & Security → Accessibility
- Ensure Terminal/Python is in the list
- Check Cursor is in foreground:
- The bot activates Cursor before each command
- If another app takes focus during the command, it may fail
Scroll buttons not working:
- Scroll uses Arrow Keys (key codes 126/125), not Page Up/Down
- If still not working, check if Cursor is focused on the correct panel
- Try clicking inside the Cursor window first
Language switch (/lang) not working:
- Uses Cmd+Space twice with 0.5s delay
- First press opens language menu, second press switches
- If your Mac uses Ctrl+Space or another shortcut, modify
controller.py
Timeout errors:
- Increase
timeoutparameter in_run_applescript()(default: 30s) - Check if Cursor is frozen or very slow
- WebSocket connection may have dropped — restart the bot
- Whitelist users — Only specified user IDs can control the bot
- Command blacklist — Dangerous commands like
rm -rf /are blocked - Local only — Core runs on localhost, no external access
MIT — use it however you want.
Built by Ivan Pasichnyk
⭐ Star this repo if you find it useful!