@inetafrica/open-claudia 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +98 -23
- package/bot-agent.js +39 -5
- package/bot.js +39 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.11.0
|
|
4
|
+
- Backend-aware plan mode: /plan passes `--mode plan` to Cursor Agent, `--permission-mode plan` to Claude
|
|
5
|
+
- New /ask command: read-only Q&A mode (Cursor Agent only, `--mode ask`)
|
|
6
|
+
- /effort and /budget now warn when on Cursor backend (unsupported flags)
|
|
7
|
+
- Worktree flag (`--worktree`) wired into Cursor Agent args
|
|
8
|
+
- buildCursorArgs now forwards plan/ask/worktree settings
|
|
9
|
+
|
|
3
10
|
## v1.10.0
|
|
4
11
|
- Cursor Agent backend: switch between Claude Code and Cursor Agent CLI
|
|
5
12
|
- New commands: /cursor, /claude, /backend with inline keyboard
|
package/README.md
CHANGED
|
@@ -1,41 +1,69 @@
|
|
|
1
1
|
# Open Claudia
|
|
2
2
|
|
|
3
|
-
Your always-on AI coding assistant — Claude Code via Telegram.
|
|
3
|
+
Your always-on AI coding assistant — Claude Code and Cursor Agent via Telegram.
|
|
4
4
|
|
|
5
|
-
Send text, voice notes, screenshots, and files from your phone.
|
|
5
|
+
Send text, voice notes, screenshots, and files from your phone. Your chosen AI agent works on your projects and reports back.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
+
- **Multi-backend** — switch between Claude Code and Cursor Agent on the fly (`/cursor`, `/claude`)
|
|
9
10
|
- **Multi-project sessions** — switch between workspace projects
|
|
10
11
|
- **Per-project conversation history** — auto-resumes last conversation, switch with `/sessions`
|
|
12
|
+
- **Separate session persistence** — Claude and Cursor each maintain their own conversation state
|
|
11
13
|
- **Voice notes** — speak instructions, transcribed locally via Whisper
|
|
12
14
|
- **Screenshots & images** — send UI mockups, errors, or code screenshots
|
|
13
|
-
- **File sharing** — send PDFs, code files, documents — saved and read by
|
|
15
|
+
- **File sharing** — send PDFs, code files, documents — saved and read by the agent
|
|
14
16
|
- **Reply context** — reply to any message (including files) for follow-up
|
|
15
|
-
- **Streaming output** — see
|
|
17
|
+
- **Streaming output** — see the agent working in real-time
|
|
18
|
+
- **Agent mode** — non-blocking side conversations while heavy tasks run in the background
|
|
16
19
|
- **Cron jobs** — scheduled tasks (standups, git digests, health checks)
|
|
17
20
|
- **Encrypted vault** — store API keys and credentials securely
|
|
18
21
|
- **Customizable soul** — define your assistant's personality and knowledge
|
|
19
|
-
- **Model switching** — toggle between
|
|
20
|
-
- **Plan mode, effort levels, budgets** — full
|
|
22
|
+
- **Model switching** — toggle between models on either backend
|
|
23
|
+
- **Plan mode, effort levels, budgets** — full control from Telegram
|
|
21
24
|
- **Auto-updates** — checks for new versions every 5 minutes, upgrade with `/upgrade`
|
|
22
25
|
- **Multi-user auth** — authorize additional users with code verification
|
|
23
26
|
- **Cross-platform** — works on macOS, Linux, and Windows
|
|
24
27
|
|
|
25
28
|
## Prerequisites
|
|
26
29
|
|
|
27
|
-
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
28
30
|
- [Node.js](https://nodejs.org/) 18+
|
|
29
31
|
- A Telegram bot token (from [@BotFather](https://t.me/BotFather))
|
|
32
|
+
- At least one authenticated CLI backend on the host machine (see below)
|
|
30
33
|
- (Optional) [whisper.cpp](https://github.com/ggerganov/whisper.cpp) + ffmpeg for voice notes
|
|
31
34
|
|
|
32
|
-
##
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### 1. Install and authenticate the CLI backends
|
|
38
|
+
|
|
39
|
+
You need **at least one** of these authenticated on the machine where Open Claudia will run.
|
|
40
|
+
|
|
41
|
+
**Claude Code** (required):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @anthropic-ai/claude-code
|
|
45
|
+
claude # Opens browser to log in
|
|
46
|
+
claude --version # Verify it works
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Cursor Agent** (optional — enables `/cursor` backend):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Install from Cursor IDE: Settings > General > Agent CLI
|
|
53
|
+
# Or download from https://docs.cursor.com/agent
|
|
54
|
+
agent login # Opens browser to authenticate
|
|
55
|
+
agent status # Verify: should show your email and plan
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Important**: Both CLIs store authentication locally on the machine. Open Claudia doesn't handle auth itself — it shells out to whichever CLI you've authenticated. If you see auth errors in Telegram, SSH into the machine and re-authenticate the relevant CLI.
|
|
59
|
+
|
|
60
|
+
### 2. Install Open Claudia
|
|
33
61
|
|
|
34
62
|
```bash
|
|
35
63
|
npm install -g @inetafrica/open-claudia
|
|
36
64
|
```
|
|
37
65
|
|
|
38
|
-
|
|
66
|
+
### 3. Run setup
|
|
39
67
|
|
|
40
68
|
```bash
|
|
41
69
|
open-claudia setup
|
|
@@ -43,7 +71,7 @@ open-claudia setup
|
|
|
43
71
|
|
|
44
72
|
The setup wizard will:
|
|
45
73
|
|
|
46
|
-
1. Detect Claude CLI, ffmpeg, and whisper on your system
|
|
74
|
+
1. Detect Claude CLI, Cursor Agent CLI, ffmpeg, and whisper on your system
|
|
47
75
|
2. Verify Claude is authenticated
|
|
48
76
|
3. Ask for your Telegram bot token and verify it
|
|
49
77
|
4. Generate a verification code — send it to your bot to prove your identity
|
|
@@ -69,6 +97,16 @@ If installed as a background service, the bot starts automatically on login and
|
|
|
69
97
|
|
|
70
98
|
## Telegram Commands
|
|
71
99
|
|
|
100
|
+
### Backend Switching
|
|
101
|
+
|
|
102
|
+
| Command | Description |
|
|
103
|
+
|---------|-------------|
|
|
104
|
+
| `/cursor` | Switch to Cursor Agent backend |
|
|
105
|
+
| `/claude` | Switch to Claude Code backend |
|
|
106
|
+
| `/backend` | Show current backend with picker |
|
|
107
|
+
|
|
108
|
+
Each backend keeps its own persistent session. Switching doesn't lose your place — you can go back and forth freely.
|
|
109
|
+
|
|
72
110
|
### Session Management
|
|
73
111
|
|
|
74
112
|
| Command | Description |
|
|
@@ -85,13 +123,15 @@ When you select a project, the last conversation is automatically resumed. Tap "
|
|
|
85
123
|
|
|
86
124
|
| Command | Description |
|
|
87
125
|
|---------|-------------|
|
|
88
|
-
| `/model` | Switch model (opus / sonnet / haiku) |
|
|
126
|
+
| `/model` | Switch model (opus / sonnet / haiku for Claude; any model flag for Cursor) |
|
|
89
127
|
| `/effort` | Set effort level (low / medium / high / max) |
|
|
90
|
-
| `/budget` | Set max spend for next task (e.g. `/budget 0.50`) |
|
|
91
|
-
| `/plan` | Toggle plan mode |
|
|
128
|
+
| `/budget` | Set max spend for next task (e.g. `/budget 0.50`) — Claude only |
|
|
129
|
+
| `/plan` | Toggle plan mode — `--permission-mode plan` (Claude) / `--mode plan` (Cursor) |
|
|
130
|
+
| `/ask` | Toggle ask mode — read-only Q&A, no edits (Cursor Agent only) |
|
|
92
131
|
| `/compact` | Summarize conversation context |
|
|
93
|
-
| `/worktree` | Toggle isolated git branch |
|
|
94
|
-
| `/
|
|
132
|
+
| `/worktree` | Toggle isolated git branch — Claude only |
|
|
133
|
+
| `/mode` | Switch between direct and agent bot modes |
|
|
134
|
+
| `/status` | Show current session, backend, and settings |
|
|
95
135
|
|
|
96
136
|
### Automation
|
|
97
137
|
|
|
@@ -111,11 +151,28 @@ When you select a project, the last conversation is automatically resumed. Tap "
|
|
|
111
151
|
| `/stop` | Cancel a running task |
|
|
112
152
|
| `/help` | Show all commands |
|
|
113
153
|
|
|
154
|
+
## Backend Comparison
|
|
155
|
+
|
|
156
|
+
| | Claude Code | Cursor Agent |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| Binary | `claude` | `agent` |
|
|
159
|
+
| Session flag | `--resume <id>` | `--resume <id>` |
|
|
160
|
+
| Auth | `claude auth` | `agent login` |
|
|
161
|
+
| Plan mode | Yes (`--permission-mode plan`) | Yes (`--mode plan`) |
|
|
162
|
+
| Ask mode | No | Yes (`--mode ask`) |
|
|
163
|
+
| Budget control | Yes (`--max-budget-usd`) | No |
|
|
164
|
+
| Effort levels | Yes | No |
|
|
165
|
+
| Worktree | Yes (`--worktree`) | Yes (`--worktree`) |
|
|
166
|
+
| Model switching | Yes | Yes |
|
|
167
|
+
| Dangerously skip permissions | Yes | Yes (`--trust`) |
|
|
168
|
+
|
|
169
|
+
Both backends output `stream-json` which Open Claudia parses for real-time progress updates.
|
|
170
|
+
|
|
114
171
|
## Sending Files
|
|
115
172
|
|
|
116
|
-
Send any file to the bot — PDFs, code files, documents, images. Files are saved to `~/.open-claudia/files/` with their original names.
|
|
173
|
+
Send any file to the bot — PDFs, code files, documents, images. Files are saved to `~/.open-claudia/files/` with their original names. The agent reads the file and responds based on content.
|
|
117
174
|
|
|
118
|
-
Add a caption to your file to give
|
|
175
|
+
Add a caption to your file to give the agent specific instructions:
|
|
119
176
|
- Send a PDF with caption "summarize the key findings"
|
|
120
177
|
- Send a code file with caption "find bugs in this"
|
|
121
178
|
- Send a screenshot with caption "implement this design"
|
|
@@ -152,11 +209,14 @@ This shows authorized chats, pending requests, and lets you approve/deny or add
|
|
|
152
209
|
## How It Works
|
|
153
210
|
|
|
154
211
|
```
|
|
155
|
-
Phone (Telegram) --> Bot (Node.js) --> Claude Code CLI
|
|
212
|
+
Phone (Telegram) --> Bot (Node.js) --> Claude Code CLI --> Your codebase
|
|
213
|
+
--> Cursor Agent CLI -->
|
|
156
214
|
<-- <-- <--
|
|
157
215
|
```
|
|
158
216
|
|
|
159
|
-
The bot spawns `
|
|
217
|
+
The bot spawns the active backend CLI in headless mode (`--print` / `-p`) for each message, streaming `stream-json` output back to Telegram. It maintains conversation context via `--resume` and passes a system prompt that gives the agent awareness of the Telegram environment, configuration files, and the ability to send files/images directly.
|
|
218
|
+
|
|
219
|
+
Use `/cursor` or `/claude` to switch which CLI handles your messages. Each maintains its own session ID, so switching doesn't lose context on either side.
|
|
160
220
|
|
|
161
221
|
## Configuration Files
|
|
162
222
|
|
|
@@ -164,17 +224,30 @@ All stored in `~/.open-claudia/`:
|
|
|
164
224
|
|
|
165
225
|
| File | Purpose |
|
|
166
226
|
|------|---------|
|
|
167
|
-
| `.env` | Telegram token, workspace path, binary paths |
|
|
227
|
+
| `.env` | Telegram token, workspace path, binary paths (`CLAUDE_PATH`, `CURSOR_PATH`) |
|
|
168
228
|
| `auth.json` | Authorized users and pending requests |
|
|
169
229
|
| `vault.enc` | Encrypted credential store |
|
|
170
230
|
| `soul.md` | Assistant identity and personality (editable via `/soul`) |
|
|
171
231
|
| `crons.json` | Scheduled tasks |
|
|
172
232
|
| `sessions.json` | Per-project conversation history |
|
|
173
|
-
| `state.json` | Current session state (survives restarts) |
|
|
233
|
+
| `state.json` | Current session state including active backend (survives restarts) |
|
|
174
234
|
| `bot.log` | Bot logs |
|
|
175
235
|
| `files/` | Files received from Telegram |
|
|
176
236
|
| `media/` | Temporary media (voice notes, photos) |
|
|
177
237
|
|
|
238
|
+
### Environment Variables (.env)
|
|
239
|
+
|
|
240
|
+
| Variable | Required | Description |
|
|
241
|
+
|----------|----------|-------------|
|
|
242
|
+
| `TELEGRAM_BOT_TOKEN` | Yes | Bot token from BotFather |
|
|
243
|
+
| `TELEGRAM_CHAT_ID` | Yes | Comma-separated authorized chat IDs |
|
|
244
|
+
| `WORKSPACE` | Yes | Path to your projects directory |
|
|
245
|
+
| `CLAUDE_PATH` | Yes | Path to Claude Code CLI binary |
|
|
246
|
+
| `CURSOR_PATH` | No | Path to Cursor Agent CLI binary (auto-detected if in PATH) |
|
|
247
|
+
| `WHISPER_CLI` | No | Path to whisper.cpp binary |
|
|
248
|
+
| `WHISPER_MODEL` | No | Whisper model to use |
|
|
249
|
+
| `FFMPEG` | No | Path to ffmpeg binary |
|
|
250
|
+
|
|
178
251
|
## Background Service
|
|
179
252
|
|
|
180
253
|
### macOS (launchd)
|
|
@@ -188,6 +261,8 @@ launchctl load ~/Library/LaunchAgents/com.open-claudia.plist
|
|
|
188
261
|
launchctl unload ~/Library/LaunchAgents/com.open-claudia.plist
|
|
189
262
|
```
|
|
190
263
|
|
|
264
|
+
**Important**: If `agent` is installed in a non-standard location (e.g. `~/.local/bin`), make sure that path is included in the launchd plist's `PATH` environment variable. Otherwise the bot won't detect it at startup.
|
|
265
|
+
|
|
191
266
|
### Linux (systemd)
|
|
192
267
|
|
|
193
268
|
```bash
|
|
@@ -202,7 +277,7 @@ sudo systemctl status claude-telegram-bot
|
|
|
202
277
|
|
|
203
278
|
The bot checks npm for new versions every 5 minutes. When an update is available, you get a Telegram notification:
|
|
204
279
|
|
|
205
|
-
> "Hey! A new version is available (v1.
|
|
280
|
+
> "Hey! A new version is available (v1.10.0). You're on v1.9.2."
|
|
206
281
|
|
|
207
282
|
Send `/upgrade` to update. The bot will:
|
|
208
283
|
1. Download and install the new version
|
|
@@ -232,7 +307,7 @@ Store sensitive credentials encrypted:
|
|
|
232
307
|
/vault lock # Lock vault
|
|
233
308
|
```
|
|
234
309
|
|
|
235
|
-
|
|
310
|
+
The agent can read vault credentials when unlocked — useful for deployment scripts and API calls.
|
|
236
311
|
|
|
237
312
|
## License
|
|
238
313
|
|
package/bot-agent.js
CHANGED
|
@@ -178,6 +178,7 @@ bot.setMyCommands([
|
|
|
178
178
|
{ command: "effort", description: "Set effort level" },
|
|
179
179
|
{ command: "budget", description: "Set max spend for next task" },
|
|
180
180
|
{ command: "plan", description: "Toggle plan mode" },
|
|
181
|
+
{ command: "ask", description: "Toggle ask mode (Cursor only)" },
|
|
181
182
|
{ command: "sessions", description: "List conversations for this project" },
|
|
182
183
|
{ command: "compact", description: "Summarize conversation context" },
|
|
183
184
|
{ command: "continue", description: "Resume last conversation" },
|
|
@@ -696,6 +697,9 @@ function buildCursorArgs(prompt, opts = {}) {
|
|
|
696
697
|
if (opts.continueSession) args.push("--continue");
|
|
697
698
|
else if (cursorSessionId && !opts.fresh) args.push("--resume", cursorSessionId);
|
|
698
699
|
if (settings.model) args.push("--model", settings.model);
|
|
700
|
+
if (settings.permissionMode === "plan") args.push("--mode", "plan");
|
|
701
|
+
else if (settings.permissionMode === "ask") args.push("--mode", "ask");
|
|
702
|
+
if (settings.worktree) args.push("--worktree");
|
|
699
703
|
args.push(prompt);
|
|
700
704
|
return args;
|
|
701
705
|
}
|
|
@@ -1110,32 +1114,62 @@ bot.onText(/\/sessions$/, (msg) => {
|
|
|
1110
1114
|
|
|
1111
1115
|
bot.onText(/\/model$/, (msg) => {
|
|
1112
1116
|
if (!isAuthorized(msg)) return;
|
|
1113
|
-
|
|
1117
|
+
const keyboard = settings.backend === "cursor" ? [
|
|
1118
|
+
[{ text: "Composer 2", callback_data: "m:composer-2" }, { text: "Composer 2 Fast", callback_data: "m:composer-2-fast" }],
|
|
1119
|
+
[{ text: "Opus 4.6 Thinking", callback_data: "m:claude-4.6-opus-high-thinking" }, { text: "Sonnet 4.6", callback_data: "m:claude-4.6-sonnet-medium" }],
|
|
1120
|
+
[{ text: "GPT-5.4", callback_data: "m:gpt-5.4-medium" }, { text: "GPT-5.4 High", callback_data: "m:gpt-5.4-high" }],
|
|
1121
|
+
[{ text: "Auto", callback_data: "m:auto" }, { text: "Default", callback_data: "m:default" }],
|
|
1122
|
+
] : [
|
|
1114
1123
|
[{ text: "Opus", callback_data: "m:opus" }, { text: "Sonnet", callback_data: "m:sonnet" }, { text: "Haiku", callback_data: "m:haiku" }],
|
|
1115
1124
|
[{ text: "Default", callback_data: "m:default" }],
|
|
1116
|
-
]
|
|
1125
|
+
];
|
|
1126
|
+
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1127
|
+
send(`${label} model: ${settings.model || "default"}\n\nOr type /model <name> for any model.`, { keyboard: { inline_keyboard: keyboard } });
|
|
1117
1128
|
});
|
|
1118
1129
|
bot.onText(/\/model (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; settings.model = match[1].trim().toLowerCase(); if (settings.model === "default") settings.model = null; send(`Model: ${settings.model || "default"}`); });
|
|
1119
1130
|
|
|
1120
1131
|
bot.onText(/\/effort$/, (msg) => {
|
|
1121
1132
|
if (!isAuthorized(msg)) return;
|
|
1133
|
+
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1122
1134
|
send(`Effort: ${settings.effort || "default"}`, { keyboard: { inline_keyboard: [
|
|
1123
1135
|
[{ text: "Low", callback_data: "e:low" }, { text: "Med", callback_data: "e:medium" }, { text: "High", callback_data: "e:high" }, { text: "Max", callback_data: "e:max" }],
|
|
1124
1136
|
[{ text: "Default", callback_data: "e:default" }],
|
|
1125
1137
|
] } });
|
|
1126
1138
|
});
|
|
1127
|
-
bot.onText(/\/effort (.+)/, (msg, match) => {
|
|
1139
|
+
bot.onText(/\/effort (.+)/, (msg, match) => {
|
|
1140
|
+
if (!isAuthorized(msg)) return;
|
|
1141
|
+
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.");
|
|
1142
|
+
const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`);
|
|
1143
|
+
});
|
|
1128
1144
|
|
|
1129
1145
|
bot.onText(/\/budget$/, (msg) => {
|
|
1130
1146
|
if (!isAuthorized(msg)) return;
|
|
1147
|
+
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1131
1148
|
send(`Budget: ${settings.budget ? "$" + settings.budget : "none"}`, { keyboard: { inline_keyboard: [
|
|
1132
1149
|
[{ text: "$1", callback_data: "b:1" }, { text: "$5", callback_data: "b:5" }, { text: "$10", callback_data: "b:10" }, { text: "$25", callback_data: "b:25" }],
|
|
1133
1150
|
[{ text: "No limit", callback_data: "b:none" }],
|
|
1134
1151
|
] } });
|
|
1135
1152
|
});
|
|
1136
|
-
bot.onText(/\/budget (.+)/, (msg, match) => {
|
|
1153
|
+
bot.onText(/\/budget (.+)/, (msg, match) => {
|
|
1154
|
+
if (!isAuthorized(msg)) return;
|
|
1155
|
+
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.");
|
|
1156
|
+
const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`);
|
|
1157
|
+
});
|
|
1137
1158
|
|
|
1138
|
-
bot.onText(/\/plan$/, (msg) => {
|
|
1159
|
+
bot.onText(/\/plan$/, (msg) => {
|
|
1160
|
+
if (!isAuthorized(msg)) return;
|
|
1161
|
+
const p = settings.permissionMode === "plan";
|
|
1162
|
+
settings.permissionMode = p ? null : "plan";
|
|
1163
|
+
const label = settings.backend === "cursor" ? "read-only planning, no edits" : "plan permission mode";
|
|
1164
|
+
send(p ? "Plan mode off." : `Plan mode on (${label}).`);
|
|
1165
|
+
});
|
|
1166
|
+
bot.onText(/\/ask$/, (msg) => {
|
|
1167
|
+
if (!isAuthorized(msg)) return;
|
|
1168
|
+
if (settings.backend !== "cursor") return send("Ask mode is only available on Cursor Agent.\nUse /cursor to switch.");
|
|
1169
|
+
const a = settings.permissionMode === "ask";
|
|
1170
|
+
settings.permissionMode = a ? null : "ask";
|
|
1171
|
+
send(a ? "Ask mode off." : "Ask mode on (read-only Q&A, no edits).");
|
|
1172
|
+
});
|
|
1139
1173
|
bot.onText(/\/compact/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; if (!getActiveSessionId()) return send("No conversation."); await runClaude("Summarize: key decisions, code state, next steps.", currentSession.dir, msg.message_id); });
|
|
1140
1174
|
bot.onText(/\/continue$/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; await runClaude("continue where we left off", currentSession.dir, msg.message_id, { continueSession: true }); });
|
|
1141
1175
|
bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
|
package/bot.js
CHANGED
|
@@ -236,6 +236,7 @@ bot.setMyCommands([
|
|
|
236
236
|
{ command: "effort", description: "Set effort level" },
|
|
237
237
|
{ command: "budget", description: "Set max spend for next task" },
|
|
238
238
|
{ command: "plan", description: "Toggle plan mode" },
|
|
239
|
+
{ command: "ask", description: "Toggle ask mode (Cursor only)" },
|
|
239
240
|
{ command: "sessions", description: "List conversations for this project" },
|
|
240
241
|
{ command: "compact", description: "Summarize conversation context" },
|
|
241
242
|
{ command: "continue", description: "Resume last conversation" },
|
|
@@ -757,6 +758,9 @@ function buildCursorArgs(prompt, opts = {}) {
|
|
|
757
758
|
if (opts.continueSession) args.push("--continue");
|
|
758
759
|
else if (cursorSessionId && !opts.fresh) args.push("--resume", cursorSessionId);
|
|
759
760
|
if (settings.model) args.push("--model", settings.model);
|
|
761
|
+
if (settings.permissionMode === "plan") args.push("--mode", "plan");
|
|
762
|
+
else if (settings.permissionMode === "ask") args.push("--mode", "ask");
|
|
763
|
+
if (settings.worktree) args.push("--worktree");
|
|
760
764
|
args.push(prompt);
|
|
761
765
|
return args;
|
|
762
766
|
}
|
|
@@ -1153,32 +1157,62 @@ bot.onText(/\/sessions$/, (msg) => {
|
|
|
1153
1157
|
|
|
1154
1158
|
bot.onText(/\/model$/, (msg) => {
|
|
1155
1159
|
if (!isAuthorized(msg)) return;
|
|
1156
|
-
|
|
1160
|
+
const keyboard = settings.backend === "cursor" ? [
|
|
1161
|
+
[{ text: "Composer 2", callback_data: "m:composer-2" }, { text: "Composer 2 Fast", callback_data: "m:composer-2-fast" }],
|
|
1162
|
+
[{ text: "Opus 4.6 Thinking", callback_data: "m:claude-4.6-opus-high-thinking" }, { text: "Sonnet 4.6", callback_data: "m:claude-4.6-sonnet-medium" }],
|
|
1163
|
+
[{ text: "GPT-5.4", callback_data: "m:gpt-5.4-medium" }, { text: "GPT-5.4 High", callback_data: "m:gpt-5.4-high" }],
|
|
1164
|
+
[{ text: "Auto", callback_data: "m:auto" }, { text: "Default", callback_data: "m:default" }],
|
|
1165
|
+
] : [
|
|
1157
1166
|
[{ text: "Opus", callback_data: "m:opus" }, { text: "Sonnet", callback_data: "m:sonnet" }, { text: "Haiku", callback_data: "m:haiku" }],
|
|
1158
1167
|
[{ text: "Default", callback_data: "m:default" }],
|
|
1159
|
-
]
|
|
1168
|
+
];
|
|
1169
|
+
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1170
|
+
send(`${label} model: ${settings.model || "default"}\n\nOr type /model <name> for any model.`, { keyboard: { inline_keyboard: keyboard } });
|
|
1160
1171
|
});
|
|
1161
1172
|
bot.onText(/\/model (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; settings.model = match[1].trim().toLowerCase(); if (settings.model === "default") settings.model = null; send(`Model: ${settings.model || "default"}`); });
|
|
1162
1173
|
|
|
1163
1174
|
bot.onText(/\/effort$/, (msg) => {
|
|
1164
1175
|
if (!isAuthorized(msg)) return;
|
|
1176
|
+
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1165
1177
|
send(`Effort: ${settings.effort || "default"}`, { keyboard: { inline_keyboard: [
|
|
1166
1178
|
[{ text: "Low", callback_data: "e:low" }, { text: "Med", callback_data: "e:medium" }, { text: "High", callback_data: "e:high" }, { text: "Max", callback_data: "e:max" }],
|
|
1167
1179
|
[{ text: "Default", callback_data: "e:default" }],
|
|
1168
1180
|
] } });
|
|
1169
1181
|
});
|
|
1170
|
-
bot.onText(/\/effort (.+)/, (msg, match) => {
|
|
1182
|
+
bot.onText(/\/effort (.+)/, (msg, match) => {
|
|
1183
|
+
if (!isAuthorized(msg)) return;
|
|
1184
|
+
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.");
|
|
1185
|
+
const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`);
|
|
1186
|
+
});
|
|
1171
1187
|
|
|
1172
1188
|
bot.onText(/\/budget$/, (msg) => {
|
|
1173
1189
|
if (!isAuthorized(msg)) return;
|
|
1190
|
+
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1174
1191
|
send(`Budget: ${settings.budget ? "$" + settings.budget : "none"}`, { keyboard: { inline_keyboard: [
|
|
1175
1192
|
[{ text: "$1", callback_data: "b:1" }, { text: "$5", callback_data: "b:5" }, { text: "$10", callback_data: "b:10" }, { text: "$25", callback_data: "b:25" }],
|
|
1176
1193
|
[{ text: "No limit", callback_data: "b:none" }],
|
|
1177
1194
|
] } });
|
|
1178
1195
|
});
|
|
1179
|
-
bot.onText(/\/budget (.+)/, (msg, match) => {
|
|
1196
|
+
bot.onText(/\/budget (.+)/, (msg, match) => {
|
|
1197
|
+
if (!isAuthorized(msg)) return;
|
|
1198
|
+
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.");
|
|
1199
|
+
const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`);
|
|
1200
|
+
});
|
|
1180
1201
|
|
|
1181
|
-
bot.onText(/\/plan$/, (msg) => {
|
|
1202
|
+
bot.onText(/\/plan$/, (msg) => {
|
|
1203
|
+
if (!isAuthorized(msg)) return;
|
|
1204
|
+
const p = settings.permissionMode === "plan";
|
|
1205
|
+
settings.permissionMode = p ? null : "plan";
|
|
1206
|
+
const label = settings.backend === "cursor" ? "read-only planning, no edits" : "plan permission mode";
|
|
1207
|
+
send(p ? "Plan mode off." : `Plan mode on (${label}).`);
|
|
1208
|
+
});
|
|
1209
|
+
bot.onText(/\/ask$/, (msg) => {
|
|
1210
|
+
if (!isAuthorized(msg)) return;
|
|
1211
|
+
if (settings.backend !== "cursor") return send("Ask mode is only available on Cursor Agent.\nUse /cursor to switch.");
|
|
1212
|
+
const a = settings.permissionMode === "ask";
|
|
1213
|
+
settings.permissionMode = a ? null : "ask";
|
|
1214
|
+
send(a ? "Ask mode off." : "Ask mode on (read-only Q&A, no edits).");
|
|
1215
|
+
});
|
|
1182
1216
|
bot.onText(/\/compact/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; if (!getActiveSessionId()) return send("No conversation."); await runClaude("Summarize: key decisions, code state, next steps.", currentSession.dir, msg.message_id); });
|
|
1183
1217
|
bot.onText(/\/continue$/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; await runClaude("continue where we left off", currentSession.dir, msg.message_id, { continueSession: true }); });
|
|
1184
1218
|
bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
|