@inetafrica/open-claudia 1.15.0 → 1.16.1
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 +16 -0
- package/README.md +26 -16
- package/bot.js +175 -23
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.16.1
|
|
4
|
+
- `/model` is now a single unified picker — all Claude, Cursor, and Codex models in one keyboard, with section dividers per backend
|
|
5
|
+
- Tapping any model auto-switches the active backend if needed (e.g. picking gpt-5 from Claude flips you to Codex). No more `/codex` + `/model` two-step
|
|
6
|
+
- Only backends with a detected CLI binary appear in the picker (Cursor/Codex rows hidden if not installed)
|
|
7
|
+
|
|
8
|
+
## v1.16.0
|
|
9
|
+
- OpenAI Codex backend support: `/codex` to switch, `/backend` picker now has 3 options
|
|
10
|
+
- Auto-detects `codex` binary via `which codex` at startup; opt-in via `CODEX_PATH` in .env
|
|
11
|
+
- Codex sessions persist (`codexSessionId`) across restarts and resume via `codex exec resume <id>`
|
|
12
|
+
- Per-backend session IDs maintained independently — switch freely without losing context
|
|
13
|
+
- Stream parser handles Codex JSONL events: `thread.started`, `item.started`, `item.completed`, `turn.completed` (with usage)
|
|
14
|
+
- Plan mode on Codex maps to `--sandbox read-only`; outside plan mode uses `--dangerously-bypass-approvals-and-sandbox` (bot is the sandbox)
|
|
15
|
+
- `/model` picker shows Codex models (gpt-5, gpt-5-codex, o3, o4-mini) when on Codex
|
|
16
|
+
- Effort/budget gracefully reject on Codex (not exposed as CLI flags); Claude auth preflight scoped strictly to the Claude backend
|
|
17
|
+
- Codex auth errors in stderr produce an actionable "run `codex login`" recovery message
|
|
18
|
+
|
|
3
19
|
## v1.14.9
|
|
4
20
|
- Fix `/upgrade`: `latest` was const-scoped to the version-check try
|
|
5
21
|
block, so the install step ran with `latest` undefined and threw
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Open Claudia
|
|
2
2
|
|
|
3
|
-
Your always-on AI coding assistant — Claude Code
|
|
3
|
+
Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram.
|
|
4
4
|
|
|
5
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
|
|
9
|
+
- **Multi-backend** — switch between Claude Code, Cursor Agent, and OpenAI Codex on the fly (`/claude`, `/cursor`, `/codex`)
|
|
10
10
|
- **Multi-project sessions** — switch between workspace projects
|
|
11
11
|
- **Per-project conversation history** — auto-resumes last conversation, switch with `/sessions`
|
|
12
12
|
- **Separate session persistence** — Claude and Cursor each maintain their own conversation state
|
|
@@ -55,6 +55,14 @@ agent login # Opens browser to authenticate
|
|
|
55
55
|
agent status # Verify: should show your email and plan
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
**OpenAI Codex** (optional — enables `/codex` backend):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install -g @openai/codex
|
|
62
|
+
codex login # Opens browser to authenticate
|
|
63
|
+
codex --version # Verify it works
|
|
64
|
+
```
|
|
65
|
+
|
|
58
66
|
> **Important**: Claude Code can use macOS Keychain when you log in interactively, but a launchd/background bot may not be able to read that Keychain session. Open Claudia v1.14.0 adds Telegram auth helpers and supports `CLAUDE_CODE_OAUTH_TOKEN` for non-interactive Claude runs. Prefer `/setup_token` then `/use_oauth_token` if Telegram shows Claude auth/keychain errors.
|
|
59
67
|
|
|
60
68
|
### 2. Install Open Claudia
|
|
@@ -103,6 +111,7 @@ If installed as a background service, the bot starts automatically on login and
|
|
|
103
111
|
|---------|-------------|
|
|
104
112
|
| `/cursor` | Switch to Cursor Agent backend |
|
|
105
113
|
| `/claude` | Switch to Claude Code backend |
|
|
114
|
+
| `/codex` | Switch to OpenAI Codex backend |
|
|
106
115
|
| `/backend` | Show current backend with picker |
|
|
107
116
|
|
|
108
117
|
Each backend keeps its own persistent session. Switching doesn't lose your place — you can go back and forth freely.
|
|
@@ -167,20 +176,20 @@ Tokens are redacted from Telegram output and logs. If the encrypted vault is unl
|
|
|
167
176
|
|
|
168
177
|
## Backend Comparison
|
|
169
178
|
|
|
170
|
-
| | Claude Code | Cursor Agent |
|
|
171
|
-
|
|
172
|
-
| Binary | `claude` | `agent` |
|
|
173
|
-
| Session flag | `--resume <id>` | `--resume <id>` |
|
|
174
|
-
| Auth | `claude auth` | `agent login` |
|
|
175
|
-
| Plan mode | Yes (`--permission-mode plan`) | Yes (`--mode plan`) |
|
|
176
|
-
| Ask mode | No | Yes (`--mode ask`) |
|
|
177
|
-
| Budget control | Yes (`--max-budget-usd`) | No |
|
|
178
|
-
| Effort levels | Yes | No |
|
|
179
|
-
| Worktree | Yes (`--worktree`) | Yes (`--worktree`) |
|
|
180
|
-
| Model switching | Yes | Yes |
|
|
181
|
-
| Dangerously skip permissions | Yes | Yes (`--trust`) |
|
|
182
|
-
|
|
183
|
-
|
|
179
|
+
| | Claude Code | Cursor Agent | OpenAI Codex |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| Binary | `claude` | `agent` | `codex` |
|
|
182
|
+
| Session flag | `--resume <id>` | `--resume <id>` | `exec resume <id>` |
|
|
183
|
+
| Auth | `claude auth` | `agent login` | `codex login` |
|
|
184
|
+
| Plan mode | Yes (`--permission-mode plan`) | Yes (`--mode plan`) | Yes (`--sandbox read-only`) |
|
|
185
|
+
| Ask mode | No | Yes (`--mode ask`) | No |
|
|
186
|
+
| Budget control | Yes (`--max-budget-usd`) | No | No |
|
|
187
|
+
| Effort levels | Yes | No | Via config.toml |
|
|
188
|
+
| Worktree | Yes (`--worktree`) | Yes (`--worktree`) | No |
|
|
189
|
+
| Model switching | Yes | Yes | Yes (`--model`) |
|
|
190
|
+
| Dangerously skip permissions | Yes | Yes (`--trust`) | Yes (`--dangerously-bypass-approvals-and-sandbox`) |
|
|
191
|
+
|
|
192
|
+
All three backends output structured JSON which Open Claudia parses for real-time progress updates (Claude/Cursor: `stream-json`; Codex: JSONL events).
|
|
184
193
|
|
|
185
194
|
## Sending Files
|
|
186
195
|
|
|
@@ -258,6 +267,7 @@ All stored in `~/.open-claudia/`:
|
|
|
258
267
|
| `WORKSPACE` | Yes | Path to your projects directory |
|
|
259
268
|
| `CLAUDE_PATH` | Yes | Path to Claude Code CLI binary |
|
|
260
269
|
| `CURSOR_PATH` | No | Path to Cursor Agent CLI binary (auto-detected if in PATH) |
|
|
270
|
+
| `CODEX_PATH` | No | Path to OpenAI Codex CLI binary (auto-detected if in PATH) |
|
|
261
271
|
| `WHISPER_CLI` | No | Path to whisper.cpp binary |
|
|
262
272
|
| `WHISPER_MODEL` | No | Whisper model to use |
|
|
263
273
|
| `FFMPEG` | No | Path to ffmpeg binary |
|
package/bot.js
CHANGED
|
@@ -143,6 +143,7 @@ const CHAT_ID = CHAT_IDS[0]; // Primary owner chat for crons/notifications
|
|
|
143
143
|
const WORKSPACE = config.WORKSPACE;
|
|
144
144
|
const CLAUDE_PATH = config.CLAUDE_PATH;
|
|
145
145
|
const CURSOR_PATH = config.CURSOR_PATH || null;
|
|
146
|
+
const CODEX_PATH = config.CODEX_PATH || null;
|
|
146
147
|
|
|
147
148
|
// Validate critical config at startup
|
|
148
149
|
if (!TOKEN) { console.error("TELEGRAM_BOT_TOKEN not set"); process.exit(1); }
|
|
@@ -180,6 +181,15 @@ if (!resolvedCursorPath) {
|
|
|
180
181
|
} catch (e) { resolvedCursorPath = null; }
|
|
181
182
|
}
|
|
182
183
|
if (resolvedCursorPath) console.log(`Cursor Agent CLI: ${resolvedCursorPath}`);
|
|
184
|
+
|
|
185
|
+
// Resolve Codex CLI (optional — discovered at startup)
|
|
186
|
+
let resolvedCodexPath = CODEX_PATH;
|
|
187
|
+
if (!resolvedCodexPath) {
|
|
188
|
+
try {
|
|
189
|
+
resolvedCodexPath = execSync("which codex 2>/dev/null", { encoding: "utf-8" }).trim() || null;
|
|
190
|
+
} catch (e) { resolvedCodexPath = null; }
|
|
191
|
+
}
|
|
192
|
+
if (resolvedCodexPath) console.log(`Codex CLI: ${resolvedCodexPath}`);
|
|
183
193
|
const WHISPER_CLI = config.WHISPER_CLI || "";
|
|
184
194
|
const WHISPER_MODEL = config.WHISPER_MODEL || "";
|
|
185
195
|
const FFMPEG = config.FFMPEG || "";
|
|
@@ -193,6 +203,7 @@ const BOT_DIR = __dirname;
|
|
|
193
203
|
const FULL_PATH = [
|
|
194
204
|
path.dirname(CLAUDE_PATH),
|
|
195
205
|
resolvedCursorPath ? path.dirname(resolvedCursorPath) : null,
|
|
206
|
+
resolvedCodexPath ? path.dirname(resolvedCodexPath) : null,
|
|
196
207
|
path.dirname(process.execPath),
|
|
197
208
|
FFMPEG ? path.dirname(FFMPEG) : null,
|
|
198
209
|
WHISPER_CLI ? path.dirname(WHISPER_CLI) : null,
|
|
@@ -283,6 +294,7 @@ bot.setMyCommands([
|
|
|
283
294
|
{ command: "status", description: "Session & settings info" },
|
|
284
295
|
{ command: "cursor", description: "Switch to Cursor Agent backend" },
|
|
285
296
|
{ command: "claude", description: "Switch to Claude Code backend" },
|
|
297
|
+
{ command: "codex", description: "Switch to OpenAI Codex backend" },
|
|
286
298
|
{ command: "backend", description: "Show/switch active backend" },
|
|
287
299
|
{ command: "auth_status", description: "Check Claude Code auth" },
|
|
288
300
|
{ command: "login", description: "Start Claude Code login" },
|
|
@@ -325,6 +337,7 @@ function saveState() {
|
|
|
325
337
|
currentSession,
|
|
326
338
|
lastSessionId,
|
|
327
339
|
cursorSessionId,
|
|
340
|
+
codexSessionId,
|
|
328
341
|
settings,
|
|
329
342
|
sessionUsage,
|
|
330
343
|
};
|
|
@@ -395,6 +408,7 @@ let streamBuffer = "";
|
|
|
395
408
|
let streamInterval = null;
|
|
396
409
|
let lastSessionId = savedState.lastSessionId || null;
|
|
397
410
|
let cursorSessionId = savedState.cursorSessionId || null;
|
|
411
|
+
let codexSessionId = savedState.codexSessionId || null;
|
|
398
412
|
let messageQueue = [];
|
|
399
413
|
let activeCrons = new Map();
|
|
400
414
|
let pendingVaultUnlock = false; // Waiting for password
|
|
@@ -965,7 +979,7 @@ function claudeAuthRecoveryMessage(reason = "Claude Code authentication failed")
|
|
|
965
979
|
}
|
|
966
980
|
|
|
967
981
|
function preflightClaudeAuthMessage() {
|
|
968
|
-
if (settings.backend
|
|
982
|
+
if (settings.backend !== "claude") return null;
|
|
969
983
|
if (getClaudeOAuthToken().value) return null;
|
|
970
984
|
try {
|
|
971
985
|
const output = execSync(`"${CLAUDE_PATH}" auth status`, {
|
|
@@ -1160,6 +1174,7 @@ function parseStreamEvents(data) {
|
|
|
1160
1174
|
|
|
1161
1175
|
function buildClaudeArgs(prompt, opts = {}) {
|
|
1162
1176
|
if (settings.backend === "cursor") return buildCursorArgs(prompt, opts);
|
|
1177
|
+
if (settings.backend === "codex") return buildCodexArgs(prompt, opts);
|
|
1163
1178
|
const args = ["-p", "--verbose", "--output-format", "stream-json",
|
|
1164
1179
|
"--append-system-prompt", buildSystemPrompt()];
|
|
1165
1180
|
if (opts.continueSession) args.push("--continue");
|
|
@@ -1186,13 +1201,40 @@ function buildCursorArgs(prompt, opts = {}) {
|
|
|
1186
1201
|
return args;
|
|
1187
1202
|
}
|
|
1188
1203
|
|
|
1204
|
+
function buildCodexArgs(prompt, opts = {}) {
|
|
1205
|
+
// Codex uses `exec [SESSION] [PROMPT]` or `exec resume <SESSION> [PROMPT]`.
|
|
1206
|
+
// Sandbox: bot runs headless, so bypass approvals (mirrors --dangerously-skip-permissions).
|
|
1207
|
+
// Plan mode: drop to read-only sandbox so the model can't write.
|
|
1208
|
+
const args = [];
|
|
1209
|
+
const resumeId = (!opts.fresh && !opts.continueSession) ? codexSessionId : null;
|
|
1210
|
+
if (opts.continueSession && codexSessionId) {
|
|
1211
|
+
args.push("exec", "resume", codexSessionId);
|
|
1212
|
+
} else if (resumeId) {
|
|
1213
|
+
args.push("exec", "resume", resumeId);
|
|
1214
|
+
} else {
|
|
1215
|
+
args.push("exec");
|
|
1216
|
+
}
|
|
1217
|
+
args.push("--json", "--skip-git-repo-check");
|
|
1218
|
+
if (settings.permissionMode === "plan") {
|
|
1219
|
+
args.push("--sandbox", "read-only");
|
|
1220
|
+
} else {
|
|
1221
|
+
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
1222
|
+
}
|
|
1223
|
+
if (settings.model) args.push("--model", settings.model);
|
|
1224
|
+
args.push(prompt);
|
|
1225
|
+
return args;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1189
1228
|
function getActiveBinary() {
|
|
1190
1229
|
if (settings.backend === "cursor") return resolvedCursorPath;
|
|
1230
|
+
if (settings.backend === "codex") return resolvedCodexPath;
|
|
1191
1231
|
return CLAUDE_PATH;
|
|
1192
1232
|
}
|
|
1193
1233
|
|
|
1194
1234
|
function getActiveSessionId() {
|
|
1195
|
-
|
|
1235
|
+
if (settings.backend === "cursor") return cursorSessionId;
|
|
1236
|
+
if (settings.backend === "codex") return codexSessionId;
|
|
1237
|
+
return lastSessionId;
|
|
1196
1238
|
}
|
|
1197
1239
|
|
|
1198
1240
|
async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
@@ -1337,6 +1379,45 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1337
1379
|
currentToolDetail = "";
|
|
1338
1380
|
}
|
|
1339
1381
|
}
|
|
1382
|
+
// Codex stream events
|
|
1383
|
+
if (evt.type === "thread.started" && evt.thread_id) {
|
|
1384
|
+
codexSessionId = evt.thread_id;
|
|
1385
|
+
saveState();
|
|
1386
|
+
}
|
|
1387
|
+
if (evt.type === "item.started" && evt.item) {
|
|
1388
|
+
const it = evt.item;
|
|
1389
|
+
if (it.type === "command_execution" && it.command) {
|
|
1390
|
+
currentTool = "Shell"; toolUses.push("Shell");
|
|
1391
|
+
currentToolDetail = String(it.command).slice(0, 80);
|
|
1392
|
+
} else if (it.type === "file_change" && (it.path || it.file_path)) {
|
|
1393
|
+
currentTool = "Edit"; toolUses.push("Edit");
|
|
1394
|
+
const p = it.path || it.file_path;
|
|
1395
|
+
currentToolDetail = String(p).split("/").slice(-2).join("/");
|
|
1396
|
+
} else if (it.type === "web_search" && it.query) {
|
|
1397
|
+
currentTool = "Web"; toolUses.push("Web");
|
|
1398
|
+
currentToolDetail = String(it.query).slice(0, 60);
|
|
1399
|
+
} else if (it.type) {
|
|
1400
|
+
currentTool = it.type; toolUses.push(it.type);
|
|
1401
|
+
currentToolDetail = "";
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (evt.type === "item.completed" && evt.item) {
|
|
1405
|
+
const it = evt.item;
|
|
1406
|
+
if (it.type === "agent_message" && typeof it.text === "string") {
|
|
1407
|
+
assistantText += (assistantText ? "\n" : "") + it.text;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (evt.type === "turn.completed" && evt.usage && settings.backend === "codex") {
|
|
1411
|
+
sessionUsage.turns += 1;
|
|
1412
|
+
const input = evt.usage.input_tokens || 0;
|
|
1413
|
+
const cached = evt.usage.cached_input_tokens || 0;
|
|
1414
|
+
const output = evt.usage.output_tokens || 0;
|
|
1415
|
+
sessionUsage.inputTokens += input;
|
|
1416
|
+
sessionUsage.outputTokens += output;
|
|
1417
|
+
sessionUsage.cacheReadTokens += cached;
|
|
1418
|
+
sessionUsage.lastInputTokens = input + cached;
|
|
1419
|
+
saveState();
|
|
1420
|
+
}
|
|
1340
1421
|
if (evt.type === "result" && evt.session_id) {
|
|
1341
1422
|
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
1342
1423
|
else { lastSessionId = evt.session_id; }
|
|
@@ -1370,7 +1451,7 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1370
1451
|
clearTimeout(processTimeout);
|
|
1371
1452
|
|
|
1372
1453
|
// Check for auth errors in stderr and give actionable Telegram recovery steps.
|
|
1373
|
-
if (settings.backend
|
|
1454
|
+
if (settings.backend === "claude" && isClaudeAuthErrorText(stderrBuffer)) {
|
|
1374
1455
|
await send(claudeAuthRecoveryMessage(stderrBuffer.trim().slice(-800)), { replyTo: replyToMsgId });
|
|
1375
1456
|
return;
|
|
1376
1457
|
}
|
|
@@ -1378,6 +1459,10 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
1378
1459
|
await send("Cursor authentication error. Run `agent login` on this machine, then retry.", { replyTo: replyToMsgId });
|
|
1379
1460
|
return;
|
|
1380
1461
|
}
|
|
1462
|
+
if (settings.backend === "codex" && /not (?:logged in|authenticated)|please (?:log|sign) in|401 unauthorized|invalid api key/i.test(stderrBuffer)) {
|
|
1463
|
+
await send("Codex authentication error. Run `codex login` on this machine, then retry.", { replyTo: replyToMsgId });
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1381
1466
|
|
|
1382
1467
|
try {
|
|
1383
1468
|
if (code !== 0 && code !== null && !assistantText.trim()) {
|
|
@@ -1665,23 +1750,50 @@ bot.onText(/\/sessions$/, (msg) => {
|
|
|
1665
1750
|
|
|
1666
1751
|
bot.onText(/\/model$/, (msg) => {
|
|
1667
1752
|
if (!isAuthorized(msg)) return;
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1753
|
+
// Unified picker: every model from every available backend in one keyboard.
|
|
1754
|
+
// Tapping a button switches backend (if needed) and sets the model.
|
|
1755
|
+
// Callback format: `mb:<backend>:<model>` (model may itself contain `-`).
|
|
1756
|
+
const rows = [];
|
|
1757
|
+
rows.push([{ text: "── Claude ──", callback_data: "noop" }]);
|
|
1758
|
+
rows.push([
|
|
1759
|
+
{ text: "Opus 4.7", callback_data: "mb:claude:claude-opus-4-7" },
|
|
1760
|
+
{ text: "Opus 4.6", callback_data: "mb:claude:claude-opus-4-6" },
|
|
1761
|
+
{ text: "Sonnet 4.6", callback_data: "mb:claude:claude-sonnet-4-6" },
|
|
1762
|
+
{ text: "Haiku", callback_data: "mb:claude:claude-haiku-4-5-20251001" },
|
|
1763
|
+
]);
|
|
1764
|
+
if (resolvedCursorPath) {
|
|
1765
|
+
rows.push([{ text: "── Cursor ──", callback_data: "noop" }]);
|
|
1766
|
+
rows.push([
|
|
1767
|
+
{ text: "Composer 2", callback_data: "mb:cursor:composer-2" },
|
|
1768
|
+
{ text: "Composer 2 Fast", callback_data: "mb:cursor:composer-2-fast" },
|
|
1769
|
+
{ text: "Auto", callback_data: "mb:cursor:auto" },
|
|
1770
|
+
]);
|
|
1771
|
+
rows.push([
|
|
1772
|
+
{ text: "Opus 4.6 Thinking", callback_data: "mb:cursor:claude-4.6-opus-high-thinking" },
|
|
1773
|
+
{ text: "GPT-5.4", callback_data: "mb:cursor:gpt-5.4-medium" },
|
|
1774
|
+
]);
|
|
1775
|
+
}
|
|
1776
|
+
if (resolvedCodexPath) {
|
|
1777
|
+
rows.push([{ text: "── Codex ──", callback_data: "noop" }]);
|
|
1778
|
+
rows.push([
|
|
1779
|
+
{ text: "gpt-5", callback_data: "mb:codex:gpt-5" },
|
|
1780
|
+
{ text: "gpt-5-codex", callback_data: "mb:codex:gpt-5-codex" },
|
|
1781
|
+
]);
|
|
1782
|
+
rows.push([
|
|
1783
|
+
{ text: "o3", callback_data: "mb:codex:o3" },
|
|
1784
|
+
{ text: "o4-mini", callback_data: "mb:codex:o4-mini" },
|
|
1785
|
+
]);
|
|
1786
|
+
}
|
|
1787
|
+
rows.push([{ text: "Default (current backend)", callback_data: "m:default" }]);
|
|
1788
|
+
const beLabel = settings.backend === "cursor" ? "Cursor" : settings.backend === "codex" ? "Codex" : "Claude";
|
|
1789
|
+
send(`Current: ${beLabel} · ${settings.model || "default"}\n\nPick a model — backend switches automatically.\nOr type /model <name>.`, { keyboard: { inline_keyboard: rows } });
|
|
1679
1790
|
});
|
|
1680
1791
|
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"}`); });
|
|
1681
1792
|
|
|
1682
1793
|
bot.onText(/\/effort$/, (msg) => {
|
|
1683
1794
|
if (!isAuthorized(msg)) return;
|
|
1684
1795
|
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1796
|
+
if (settings.backend === "codex") return send("Effort levels are not exposed as a flag on Codex.\nSet `model_reasoning_effort` in ~/.codex/config.toml, or switch to /claude.");
|
|
1685
1797
|
send(`Effort: ${settings.effort || "default"}`, { keyboard: { inline_keyboard: [
|
|
1686
1798
|
[{ text: "Low", callback_data: "e:low" }, { text: "Med", callback_data: "e:medium" }, { text: "High", callback_data: "e:high" }, { text: "Max", callback_data: "e:max" }],
|
|
1687
1799
|
[{ text: "Default", callback_data: "e:default" }],
|
|
@@ -1690,12 +1802,14 @@ bot.onText(/\/effort$/, (msg) => {
|
|
|
1690
1802
|
bot.onText(/\/effort (.+)/, (msg, match) => {
|
|
1691
1803
|
if (!isAuthorized(msg)) return;
|
|
1692
1804
|
if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.");
|
|
1805
|
+
if (settings.backend === "codex") return send("Effort levels are not exposed as a flag on Codex.");
|
|
1693
1806
|
const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`);
|
|
1694
1807
|
});
|
|
1695
1808
|
|
|
1696
1809
|
bot.onText(/\/budget$/, (msg) => {
|
|
1697
1810
|
if (!isAuthorized(msg)) return;
|
|
1698
1811
|
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
|
|
1812
|
+
if (settings.backend === "codex") return send("Budget limits are not supported on Codex.\nSwitch to Claude with /claude to use this.");
|
|
1699
1813
|
send(`Budget: ${settings.budget ? "$" + settings.budget : "none"}`, { keyboard: { inline_keyboard: [
|
|
1700
1814
|
[{ text: "$1", callback_data: "b:1" }, { text: "$5", callback_data: "b:5" }, { text: "$10", callback_data: "b:10" }, { text: "$25", callback_data: "b:25" }],
|
|
1701
1815
|
[{ text: "No limit", callback_data: "b:none" }],
|
|
@@ -1704,6 +1818,7 @@ bot.onText(/\/budget$/, (msg) => {
|
|
|
1704
1818
|
bot.onText(/\/budget (.+)/, (msg, match) => {
|
|
1705
1819
|
if (!isAuthorized(msg)) return;
|
|
1706
1820
|
if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.");
|
|
1821
|
+
if (settings.backend === "codex") return send("Budget limits are not supported on Codex.");
|
|
1707
1822
|
const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`);
|
|
1708
1823
|
});
|
|
1709
1824
|
|
|
@@ -1711,8 +1826,14 @@ bot.onText(/\/plan$/, (msg) => {
|
|
|
1711
1826
|
if (!isAuthorized(msg)) return;
|
|
1712
1827
|
const p = settings.permissionMode === "plan";
|
|
1713
1828
|
settings.permissionMode = p ? null : "plan";
|
|
1714
|
-
const label = settings.backend === "cursor" ? "read-only planning, no edits"
|
|
1715
|
-
|
|
1829
|
+
const label = settings.backend === "cursor" ? "read-only planning, no edits"
|
|
1830
|
+
: settings.backend === "codex" ? "read-only sandbox, no edits"
|
|
1831
|
+
: "plan permission mode";
|
|
1832
|
+
if (p) {
|
|
1833
|
+
// Reset session so next message doesn't resume in plan-mode for the active backend.
|
|
1834
|
+
if (settings.backend === "cursor") cursorSessionId = null;
|
|
1835
|
+
else if (settings.backend === "codex") codexSessionId = null;
|
|
1836
|
+
}
|
|
1716
1837
|
send(p ? "Plan mode off (session reset)." : `Plan mode on (${label}).`);
|
|
1717
1838
|
});
|
|
1718
1839
|
bot.onText(/\/ask$/, (msg) => {
|
|
@@ -1733,7 +1854,7 @@ bot.onText(/\/cursor$/, async (msg) => {
|
|
|
1733
1854
|
settings.model = null;
|
|
1734
1855
|
saveState();
|
|
1735
1856
|
const sid = cursorSessionId ? `\nSession: ${cursorSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1736
|
-
send(`Switched to Cursor Agent.${sid}\n\n/claude — switch back`);
|
|
1857
|
+
send(`Switched to Cursor Agent.${sid}\n\n/claude — switch back · /codex — Codex`);
|
|
1737
1858
|
});
|
|
1738
1859
|
|
|
1739
1860
|
bot.onText(/\/claude$/, async (msg) => {
|
|
@@ -1742,15 +1863,26 @@ bot.onText(/\/claude$/, async (msg) => {
|
|
|
1742
1863
|
settings.model = null;
|
|
1743
1864
|
saveState();
|
|
1744
1865
|
const sid = lastSessionId ? `\nSession: ${lastSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1745
|
-
send(`Switched to Claude Code.${sid}\n\n/cursor —
|
|
1866
|
+
send(`Switched to Claude Code.${sid}\n\n/cursor — Cursor · /codex — Codex`);
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
bot.onText(/\/codex$/, async (msg) => {
|
|
1870
|
+
if (!isAuthorized(msg)) return;
|
|
1871
|
+
if (!resolvedCodexPath) return send("Codex CLI not found.\nInstall: npm install -g @openai/codex\nThen run `codex login` to authenticate.");
|
|
1872
|
+
settings.backend = "codex";
|
|
1873
|
+
settings.model = null;
|
|
1874
|
+
saveState();
|
|
1875
|
+
const sid = codexSessionId ? `\nSession: ${codexSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1876
|
+
send(`Switched to Codex.${sid}\n\n/claude — Claude · /cursor — Cursor`);
|
|
1746
1877
|
});
|
|
1747
1878
|
|
|
1748
1879
|
bot.onText(/\/backend$/, async (msg) => {
|
|
1749
1880
|
if (!isAuthorized(msg)) return;
|
|
1750
|
-
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1881
|
+
const label = settings.backend === "cursor" ? "Cursor Agent" : settings.backend === "codex" ? "Codex" : "Claude Code";
|
|
1751
1882
|
const cursorAvail = resolvedCursorPath ? "available" : "not found";
|
|
1752
|
-
|
|
1753
|
-
|
|
1883
|
+
const codexAvail = resolvedCodexPath ? "available" : "not found";
|
|
1884
|
+
send(`Backend: ${label}\n\nClaude Code: available\nCursor Agent: ${cursorAvail}\nCodex: ${codexAvail}`, { keyboard: { inline_keyboard: [
|
|
1885
|
+
[{ text: "Claude Code", callback_data: "be:claude" }, { text: "Cursor Agent", callback_data: "be:cursor" }, { text: "Codex", callback_data: "be:codex" }],
|
|
1754
1886
|
] } });
|
|
1755
1887
|
});
|
|
1756
1888
|
|
|
@@ -1783,7 +1915,7 @@ bot.onText(/\/stop/, async (msg) => {
|
|
|
1783
1915
|
bot.onText(/\/status/, (msg) => {
|
|
1784
1916
|
if (!isAuthorized(msg)) return;
|
|
1785
1917
|
if (!currentSession) return send("No session.", { keyboard: { inline_keyboard: [[{ text: "Pick", callback_data: "show:projects" }]] } });
|
|
1786
|
-
const backendLabel = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1918
|
+
const backendLabel = settings.backend === "cursor" ? "Cursor Agent" : settings.backend === "codex" ? "Codex" : "Claude Code";
|
|
1787
1919
|
send([
|
|
1788
1920
|
`Project: ${currentSession.name}`,
|
|
1789
1921
|
`Backend: ${backendLabel}`,
|
|
@@ -2024,16 +2156,36 @@ bot.on("callback_query", async (q) => {
|
|
|
2024
2156
|
}
|
|
2025
2157
|
if (d === "a:continue") { if (currentSession) await runClaude("continue", currentSession.dir); else send("No session."); return; }
|
|
2026
2158
|
if (d === "a:end") { if (currentSession) { const n = currentSession.name; currentSession = null; lastSessionId = null; messageQueue = []; resetSettings(); resetSessionUsage(); saveState(); await send(`Ended: ${n}`, { keyboard: { inline_keyboard: [[{ text: "New", callback_data: "show:projects" }]] } }); } return; }
|
|
2159
|
+
if (d === "noop") { return; }
|
|
2160
|
+
if (d.startsWith("mb:")) {
|
|
2161
|
+
// Unified picker: switch backend + set model in one tap.
|
|
2162
|
+
// Format: mb:<backend>:<model>. Model may contain `:` itself (unlikely), so split on first two.
|
|
2163
|
+
const rest = d.slice(3);
|
|
2164
|
+
const colon = rest.indexOf(":");
|
|
2165
|
+
if (colon < 0) return;
|
|
2166
|
+
const be = rest.slice(0, colon);
|
|
2167
|
+
const model = rest.slice(colon + 1);
|
|
2168
|
+
if (be === "cursor" && !resolvedCursorPath) { await send("Cursor Agent CLI not found."); return; }
|
|
2169
|
+
if (be === "codex" && !resolvedCodexPath) { await send("Codex CLI not found. Install: npm install -g @openai/codex"); return; }
|
|
2170
|
+
const switched = settings.backend !== be;
|
|
2171
|
+
settings.backend = be;
|
|
2172
|
+
settings.model = model;
|
|
2173
|
+
saveState();
|
|
2174
|
+
const beLabel = be === "cursor" ? "Cursor Agent" : be === "codex" ? "Codex" : "Claude Code";
|
|
2175
|
+
await send(switched ? `Switched to ${beLabel}.\nModel: ${model}` : `Model: ${model}`);
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2027
2178
|
if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
|
|
2028
2179
|
if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
|
|
2029
2180
|
if (d.startsWith("b:")) { const b = d.slice(2); settings.budget = b === "none" ? null : parseFloat(b); await send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); return; }
|
|
2030
2181
|
if (d.startsWith("be:")) {
|
|
2031
2182
|
const be = d.slice(3);
|
|
2032
2183
|
if (be === "cursor" && !resolvedCursorPath) { await send("Cursor Agent CLI not found."); return; }
|
|
2184
|
+
if (be === "codex" && !resolvedCodexPath) { await send("Codex CLI not found. Install: npm install -g @openai/codex"); return; }
|
|
2033
2185
|
settings.backend = be;
|
|
2034
2186
|
settings.model = null;
|
|
2035
2187
|
saveState();
|
|
2036
|
-
const label = be === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
2188
|
+
const label = be === "cursor" ? "Cursor Agent" : be === "codex" ? "Codex" : "Claude Code";
|
|
2037
2189
|
await send(`Switched to ${label}.`);
|
|
2038
2190
|
return;
|
|
2039
2191
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Your always-on AI coding assistant — Claude Code via Telegram",
|
|
3
|
+
"version": "1.16.1",
|
|
4
|
+
"description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"open-claudia": "./bin/cli.js"
|