@inetafrica/open-claudia 1.9.2 → 1.10.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/.env.example +1 -0
- package/CHANGELOG.md +7 -0
- package/bot-agent.js +87 -5
- package/bot.js +89 -7
- package/package.json +1 -1
package/.env.example
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.10.0
|
|
4
|
+
- Cursor Agent backend: switch between Claude Code and Cursor Agent CLI
|
|
5
|
+
- New commands: /cursor, /claude, /backend with inline keyboard
|
|
6
|
+
- Separate session persistence per backend (Claude and Cursor sessions don't clash)
|
|
7
|
+
- Auto-discovers `agent` CLI in PATH if CURSOR_PATH not set
|
|
8
|
+
- /status shows active backend
|
|
9
|
+
|
|
3
10
|
## v1.9.2
|
|
4
11
|
- Fix: show what's new after upgrade
|
|
5
12
|
- Startup message shows version
|
package/bot-agent.js
CHANGED
|
@@ -76,6 +76,17 @@ const CHAT_IDS = (config.TELEGRAM_CHAT_ID || "").split(",").map((s) => s.trim())
|
|
|
76
76
|
const CHAT_ID = CHAT_IDS[0]; // Primary owner chat for crons/notifications
|
|
77
77
|
const WORKSPACE = config.WORKSPACE;
|
|
78
78
|
const CLAUDE_PATH = config.CLAUDE_PATH;
|
|
79
|
+
const CURSOR_PATH = config.CURSOR_PATH || null;
|
|
80
|
+
|
|
81
|
+
// Resolve Cursor Agent CLI (optional)
|
|
82
|
+
let resolvedCursorPath = CURSOR_PATH;
|
|
83
|
+
if (!resolvedCursorPath) {
|
|
84
|
+
try {
|
|
85
|
+
resolvedCursorPath = execSync("which agent 2>/dev/null", { encoding: "utf-8" }).trim() || null;
|
|
86
|
+
} catch (e) { resolvedCursorPath = null; }
|
|
87
|
+
}
|
|
88
|
+
if (resolvedCursorPath) console.log(`Cursor Agent CLI: ${resolvedCursorPath}`);
|
|
89
|
+
|
|
79
90
|
const WHISPER_CLI = config.WHISPER_CLI || "";
|
|
80
91
|
const WHISPER_MODEL = config.WHISPER_MODEL || "";
|
|
81
92
|
const FFMPEG = config.FFMPEG || "";
|
|
@@ -88,6 +99,7 @@ const BOT_DIR = __dirname;
|
|
|
88
99
|
// Detect PATH for subprocess
|
|
89
100
|
const FULL_PATH = [
|
|
90
101
|
path.dirname(CLAUDE_PATH),
|
|
102
|
+
resolvedCursorPath ? path.dirname(resolvedCursorPath) : null,
|
|
91
103
|
path.dirname(process.execPath),
|
|
92
104
|
FFMPEG ? path.dirname(FFMPEG) : null,
|
|
93
105
|
WHISPER_CLI ? path.dirname(WHISPER_CLI) : null,
|
|
@@ -174,6 +186,9 @@ bot.setMyCommands([
|
|
|
174
186
|
{ command: "vault", description: "Manage credentials (password required)" },
|
|
175
187
|
{ command: "soul", description: "View/edit assistant identity" },
|
|
176
188
|
{ command: "status", description: "Session & settings info" },
|
|
189
|
+
{ command: "cursor", description: "Switch to Cursor Agent backend" },
|
|
190
|
+
{ command: "claude", description: "Switch to Claude Code backend" },
|
|
191
|
+
{ command: "backend", description: "Show/switch active backend" },
|
|
177
192
|
{ command: "stop", description: "Cancel running task" },
|
|
178
193
|
{ command: "end", description: "End current session" },
|
|
179
194
|
{ command: "version", description: "Show current version" },
|
|
@@ -204,6 +219,7 @@ function saveState() {
|
|
|
204
219
|
const data = {
|
|
205
220
|
currentSession,
|
|
206
221
|
lastSessionId,
|
|
222
|
+
cursorSessionId,
|
|
207
223
|
settings,
|
|
208
224
|
};
|
|
209
225
|
try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) {}
|
|
@@ -276,6 +292,7 @@ let statusMessageId = null;
|
|
|
276
292
|
let streamBuffer = "";
|
|
277
293
|
let streamInterval = null;
|
|
278
294
|
let lastSessionId = savedState.lastSessionId || null;
|
|
295
|
+
let cursorSessionId = savedState.cursorSessionId || null;
|
|
279
296
|
let messageQueue = []; // Fallback queue (only used if chat process also fails)
|
|
280
297
|
let activeCrons = new Map();
|
|
281
298
|
let pendingVaultUnlock = false;
|
|
@@ -283,11 +300,12 @@ let pendingVaultAction = null;
|
|
|
283
300
|
let isFirstMessage = !lastSessionId;
|
|
284
301
|
|
|
285
302
|
let settings = savedState.settings || {
|
|
286
|
-
model: null, effort: null, budget: null, permissionMode: null, worktree: false,
|
|
303
|
+
model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude",
|
|
287
304
|
};
|
|
305
|
+
if (!settings.backend) settings.backend = "claude";
|
|
288
306
|
|
|
289
307
|
function resetSettings() {
|
|
290
|
-
settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false };
|
|
308
|
+
settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude" };
|
|
291
309
|
}
|
|
292
310
|
|
|
293
311
|
function isAuthorized(msg) {
|
|
@@ -658,6 +676,7 @@ function parseStreamEvents(data) {
|
|
|
658
676
|
}
|
|
659
677
|
|
|
660
678
|
function buildClaudeArgs(prompt, opts = {}) {
|
|
679
|
+
if (settings.backend === "cursor") return buildCursorArgs(prompt, opts);
|
|
661
680
|
const args = ["-p", "--verbose", "--output-format", "stream-json",
|
|
662
681
|
"--append-system-prompt", buildSystemPrompt()];
|
|
663
682
|
if (opts.continueSession) args.push("--continue");
|
|
@@ -672,6 +691,24 @@ function buildClaudeArgs(prompt, opts = {}) {
|
|
|
672
691
|
return args;
|
|
673
692
|
}
|
|
674
693
|
|
|
694
|
+
function buildCursorArgs(prompt, opts = {}) {
|
|
695
|
+
const args = ["--print", "--trust", "--output-format", "stream-json"];
|
|
696
|
+
if (opts.continueSession) args.push("--continue");
|
|
697
|
+
else if (cursorSessionId && !opts.fresh) args.push("--resume", cursorSessionId);
|
|
698
|
+
if (settings.model) args.push("--model", settings.model);
|
|
699
|
+
args.push(prompt);
|
|
700
|
+
return args;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function getActiveBinary() {
|
|
704
|
+
if (settings.backend === "cursor") return resolvedCursorPath;
|
|
705
|
+
return CLAUDE_PATH;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function getActiveSessionId() {
|
|
709
|
+
return settings.backend === "cursor" ? cursorSessionId : lastSessionId;
|
|
710
|
+
}
|
|
711
|
+
|
|
675
712
|
/**
|
|
676
713
|
* Quick chat process — handles messages while a heavy task is running.
|
|
677
714
|
* Uses a fresh session (no --resume) with context about what's running.
|
|
@@ -747,7 +784,8 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
747
784
|
let currentToolDetail = "";
|
|
748
785
|
|
|
749
786
|
const args = buildClaudeArgs(prompt, opts);
|
|
750
|
-
const
|
|
787
|
+
const binaryPath = getActiveBinary();
|
|
788
|
+
const proc = spawn(binaryPath, args, {
|
|
751
789
|
cwd,
|
|
752
790
|
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME },
|
|
753
791
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -815,7 +853,11 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
815
853
|
}
|
|
816
854
|
}
|
|
817
855
|
}
|
|
818
|
-
if (evt.type === "result" && evt.session_id) {
|
|
856
|
+
if (evt.type === "result" && evt.session_id) {
|
|
857
|
+
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
858
|
+
else { lastSessionId = evt.session_id; }
|
|
859
|
+
saveState();
|
|
860
|
+
}
|
|
819
861
|
if (evt.type === "result" && evt.result) assistantText = evt.result;
|
|
820
862
|
}
|
|
821
863
|
});
|
|
@@ -1094,10 +1136,38 @@ bot.onText(/\/budget$/, (msg) => {
|
|
|
1094
1136
|
bot.onText(/\/budget (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); });
|
|
1095
1137
|
|
|
1096
1138
|
bot.onText(/\/plan$/, (msg) => { if (!isAuthorized(msg)) return; const p = settings.permissionMode === "plan"; settings.permissionMode = p ? null : "plan"; send(p ? "Plan mode off." : "Plan mode on."); });
|
|
1097
|
-
bot.onText(/\/compact/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; if (!
|
|
1139
|
+
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); });
|
|
1098
1140
|
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 }); });
|
|
1099
1141
|
bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
|
|
1100
1142
|
|
|
1143
|
+
bot.onText(/\/cursor$/, async (msg) => {
|
|
1144
|
+
if (!isAuthorized(msg)) return;
|
|
1145
|
+
if (!resolvedCursorPath) return send("Cursor Agent CLI not found.\nSet CURSOR_PATH in .env or install: https://docs.cursor.com/agent");
|
|
1146
|
+
settings.backend = "cursor";
|
|
1147
|
+
settings.model = null;
|
|
1148
|
+
saveState();
|
|
1149
|
+
const sid = cursorSessionId ? `\nSession: ${cursorSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1150
|
+
send(`Switched to Cursor Agent.${sid}\n\n/claude — switch back`);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
bot.onText(/\/claude$/, async (msg) => {
|
|
1154
|
+
if (!isAuthorized(msg)) return;
|
|
1155
|
+
settings.backend = "claude";
|
|
1156
|
+
settings.model = null;
|
|
1157
|
+
saveState();
|
|
1158
|
+
const sid = lastSessionId ? `\nSession: ${lastSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1159
|
+
send(`Switched to Claude Code.${sid}\n\n/cursor — switch to Cursor`);
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
bot.onText(/\/backend$/, async (msg) => {
|
|
1163
|
+
if (!isAuthorized(msg)) return;
|
|
1164
|
+
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1165
|
+
const cursorAvail = resolvedCursorPath ? "available" : "not found";
|
|
1166
|
+
send(`Backend: ${label}\n\nClaude Code: available\nCursor Agent: ${cursorAvail}`, { keyboard: { inline_keyboard: [
|
|
1167
|
+
[{ text: "Claude Code", callback_data: "be:claude" }, { text: "Cursor Agent", callback_data: "be:cursor" }],
|
|
1168
|
+
] } });
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1101
1171
|
bot.onText(/\/mode$/, async (msg) => {
|
|
1102
1172
|
if (!isAuthorized(msg)) return;
|
|
1103
1173
|
await send("Bot mode: *agent* (non-blocking)\n\nHeavy tasks run in the background. You can keep chatting while they work.\nSwitch to direct mode for serial execution with shared session context.", {
|
|
@@ -1133,8 +1203,10 @@ bot.onText(/\/stop/, async (msg) => {
|
|
|
1133
1203
|
bot.onText(/\/status/, (msg) => {
|
|
1134
1204
|
if (!isAuthorized(msg)) return;
|
|
1135
1205
|
if (!currentSession) return send("No session.", { keyboard: { inline_keyboard: [[{ text: "Pick", callback_data: "show:projects" }]] } });
|
|
1206
|
+
const backendLabel = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1136
1207
|
const lines = [
|
|
1137
1208
|
`Project: ${currentSession.name} (agent mode)`,
|
|
1209
|
+
`Backend: ${backendLabel}`,
|
|
1138
1210
|
`Model: ${settings.model || "default"} | Effort: ${settings.effort || "default"}`,
|
|
1139
1211
|
`Vault: ${vault.isUnlocked() ? "unlocked" : "locked"} | Crons: ${activeCrons.size}`,
|
|
1140
1212
|
];
|
|
@@ -1281,6 +1353,16 @@ bot.on("callback_query", async (q) => {
|
|
|
1281
1353
|
if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
|
|
1282
1354
|
if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
|
|
1283
1355
|
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; }
|
|
1356
|
+
if (d.startsWith("be:")) {
|
|
1357
|
+
const be = d.slice(3);
|
|
1358
|
+
if (be === "cursor" && !resolvedCursorPath) { await send("Cursor Agent CLI not found."); return; }
|
|
1359
|
+
settings.backend = be;
|
|
1360
|
+
settings.model = null;
|
|
1361
|
+
saveState();
|
|
1362
|
+
const label = be === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1363
|
+
await send(`Switched to ${label}.`);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1284
1366
|
|
|
1285
1367
|
// Mode switching
|
|
1286
1368
|
if (d.startsWith("mode:")) {
|
package/bot.js
CHANGED
|
@@ -107,6 +107,7 @@ const CHAT_IDS = (config.TELEGRAM_CHAT_ID || "").split(",").map((s) => s.trim())
|
|
|
107
107
|
const CHAT_ID = CHAT_IDS[0]; // Primary owner chat for crons/notifications
|
|
108
108
|
const WORKSPACE = config.WORKSPACE;
|
|
109
109
|
const CLAUDE_PATH = config.CLAUDE_PATH;
|
|
110
|
+
const CURSOR_PATH = config.CURSOR_PATH || null;
|
|
110
111
|
|
|
111
112
|
// Validate critical config at startup
|
|
112
113
|
if (!TOKEN) { console.error("TELEGRAM_BOT_TOKEN not set"); process.exit(1); }
|
|
@@ -135,6 +136,15 @@ if (!fs.existsSync(CLAUDE_PATH)) {
|
|
|
135
136
|
process.exit(1);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
|
|
140
|
+
// Resolve Cursor Agent CLI (optional — discovered at startup)
|
|
141
|
+
let resolvedCursorPath = CURSOR_PATH;
|
|
142
|
+
if (!resolvedCursorPath) {
|
|
143
|
+
try {
|
|
144
|
+
resolvedCursorPath = execSync("which agent 2>/dev/null", { encoding: "utf-8" }).trim() || null;
|
|
145
|
+
} catch (e) { resolvedCursorPath = null; }
|
|
146
|
+
}
|
|
147
|
+
if (resolvedCursorPath) console.log(`Cursor Agent CLI: ${resolvedCursorPath}`);
|
|
138
148
|
const WHISPER_CLI = config.WHISPER_CLI || "";
|
|
139
149
|
const WHISPER_MODEL = config.WHISPER_MODEL || "";
|
|
140
150
|
const FFMPEG = config.FFMPEG || "";
|
|
@@ -147,6 +157,7 @@ const BOT_DIR = __dirname;
|
|
|
147
157
|
// Detect PATH for subprocess
|
|
148
158
|
const FULL_PATH = [
|
|
149
159
|
path.dirname(CLAUDE_PATH),
|
|
160
|
+
resolvedCursorPath ? path.dirname(resolvedCursorPath) : null,
|
|
150
161
|
path.dirname(process.execPath),
|
|
151
162
|
FFMPEG ? path.dirname(FFMPEG) : null,
|
|
152
163
|
WHISPER_CLI ? path.dirname(WHISPER_CLI) : null,
|
|
@@ -233,6 +244,9 @@ bot.setMyCommands([
|
|
|
233
244
|
{ command: "vault", description: "Manage credentials (password required)" },
|
|
234
245
|
{ command: "soul", description: "View/edit assistant identity" },
|
|
235
246
|
{ command: "status", description: "Session & settings info" },
|
|
247
|
+
{ command: "cursor", description: "Switch to Cursor Agent backend" },
|
|
248
|
+
{ command: "claude", description: "Switch to Claude Code backend" },
|
|
249
|
+
{ command: "backend", description: "Show/switch active backend" },
|
|
236
250
|
{ command: "stop", description: "Cancel running task" },
|
|
237
251
|
{ command: "end", description: "End current session" },
|
|
238
252
|
{ command: "version", description: "Show current version" },
|
|
@@ -270,6 +284,7 @@ function saveState() {
|
|
|
270
284
|
const data = {
|
|
271
285
|
currentSession,
|
|
272
286
|
lastSessionId,
|
|
287
|
+
cursorSessionId,
|
|
273
288
|
settings,
|
|
274
289
|
};
|
|
275
290
|
try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) {}
|
|
@@ -338,6 +353,7 @@ let statusMessageId = null;
|
|
|
338
353
|
let streamBuffer = "";
|
|
339
354
|
let streamInterval = null;
|
|
340
355
|
let lastSessionId = savedState.lastSessionId || null;
|
|
356
|
+
let cursorSessionId = savedState.cursorSessionId || null;
|
|
341
357
|
let messageQueue = [];
|
|
342
358
|
let activeCrons = new Map();
|
|
343
359
|
let pendingVaultUnlock = false; // Waiting for password
|
|
@@ -345,11 +361,12 @@ let pendingVaultAction = null; // What to do after unlock
|
|
|
345
361
|
let isFirstMessage = !lastSessionId; // Track if this is first message in session
|
|
346
362
|
|
|
347
363
|
let settings = savedState.settings || {
|
|
348
|
-
model: null, effort: null, budget: null, permissionMode: null, worktree: false,
|
|
364
|
+
model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude",
|
|
349
365
|
};
|
|
366
|
+
if (!settings.backend) settings.backend = "claude";
|
|
350
367
|
|
|
351
368
|
function resetSettings() {
|
|
352
|
-
settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false };
|
|
369
|
+
settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false, backend: "claude" };
|
|
353
370
|
}
|
|
354
371
|
|
|
355
372
|
function isAuthorized(msg) {
|
|
@@ -720,6 +737,7 @@ function parseStreamEvents(data) {
|
|
|
720
737
|
}
|
|
721
738
|
|
|
722
739
|
function buildClaudeArgs(prompt, opts = {}) {
|
|
740
|
+
if (settings.backend === "cursor") return buildCursorArgs(prompt, opts);
|
|
723
741
|
const args = ["-p", "--verbose", "--output-format", "stream-json",
|
|
724
742
|
"--append-system-prompt", buildSystemPrompt()];
|
|
725
743
|
if (opts.continueSession) args.push("--continue");
|
|
@@ -734,6 +752,24 @@ function buildClaudeArgs(prompt, opts = {}) {
|
|
|
734
752
|
return args;
|
|
735
753
|
}
|
|
736
754
|
|
|
755
|
+
function buildCursorArgs(prompt, opts = {}) {
|
|
756
|
+
const args = ["--print", "--trust", "--output-format", "stream-json"];
|
|
757
|
+
if (opts.continueSession) args.push("--continue");
|
|
758
|
+
else if (cursorSessionId && !opts.fresh) args.push("--resume", cursorSessionId);
|
|
759
|
+
if (settings.model) args.push("--model", settings.model);
|
|
760
|
+
args.push(prompt);
|
|
761
|
+
return args;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function getActiveBinary() {
|
|
765
|
+
if (settings.backend === "cursor") return resolvedCursorPath;
|
|
766
|
+
return CLAUDE_PATH;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function getActiveSessionId() {
|
|
770
|
+
return settings.backend === "cursor" ? cursorSessionId : lastSessionId;
|
|
771
|
+
}
|
|
772
|
+
|
|
737
773
|
async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
738
774
|
if (runningProcess) {
|
|
739
775
|
messageQueue.push({ prompt, replyToMsgId, opts });
|
|
@@ -750,11 +786,12 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
750
786
|
let currentToolDetail = "";
|
|
751
787
|
|
|
752
788
|
const args = buildClaudeArgs(prompt, opts);
|
|
753
|
-
const
|
|
789
|
+
const binaryPath = getActiveBinary();
|
|
790
|
+
const proc = spawn(binaryPath, args, {
|
|
754
791
|
cwd,
|
|
755
792
|
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME },
|
|
756
793
|
stdio: ["ignore", "pipe", "pipe"],
|
|
757
|
-
detached: process.platform !== "win32",
|
|
794
|
+
detached: process.platform !== "win32",
|
|
758
795
|
});
|
|
759
796
|
|
|
760
797
|
runningProcess = proc;
|
|
@@ -832,7 +869,11 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
832
869
|
}
|
|
833
870
|
}
|
|
834
871
|
}
|
|
835
|
-
if (evt.type === "result" && evt.session_id) {
|
|
872
|
+
if (evt.type === "result" && evt.session_id) {
|
|
873
|
+
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
874
|
+
else { lastSessionId = evt.session_id; }
|
|
875
|
+
saveState();
|
|
876
|
+
}
|
|
836
877
|
if (evt.type === "result" && evt.result) assistantText = evt.result;
|
|
837
878
|
}
|
|
838
879
|
});
|
|
@@ -853,7 +894,8 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
853
894
|
const stderrLower = stderrBuffer.toLowerCase();
|
|
854
895
|
if (stderrLower.includes("unauthorized") || stderrLower.includes("auth") && stderrLower.includes("fail") ||
|
|
855
896
|
stderrLower.includes("api key") || stderrLower.includes("not logged in")) {
|
|
856
|
-
|
|
897
|
+
const hint = settings.backend === "cursor" ? "Run `agent login` to authenticate." : "Run `claude auth` to re-authenticate.";
|
|
898
|
+
await send(`Authentication error. ${hint}`);
|
|
857
899
|
return;
|
|
858
900
|
}
|
|
859
901
|
|
|
@@ -1137,10 +1179,38 @@ bot.onText(/\/budget$/, (msg) => {
|
|
|
1137
1179
|
bot.onText(/\/budget (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); });
|
|
1138
1180
|
|
|
1139
1181
|
bot.onText(/\/plan$/, (msg) => { if (!isAuthorized(msg)) return; const p = settings.permissionMode === "plan"; settings.permissionMode = p ? null : "plan"; send(p ? "Plan mode off." : "Plan mode on."); });
|
|
1140
|
-
bot.onText(/\/compact/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; if (!
|
|
1182
|
+
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); });
|
|
1141
1183
|
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 }); });
|
|
1142
1184
|
bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
|
|
1143
1185
|
|
|
1186
|
+
bot.onText(/\/cursor$/, async (msg) => {
|
|
1187
|
+
if (!isAuthorized(msg)) return;
|
|
1188
|
+
if (!resolvedCursorPath) return send("Cursor Agent CLI not found.\nSet CURSOR_PATH in .env or install: https://docs.cursor.com/agent");
|
|
1189
|
+
settings.backend = "cursor";
|
|
1190
|
+
settings.model = null;
|
|
1191
|
+
saveState();
|
|
1192
|
+
const sid = cursorSessionId ? `\nSession: ${cursorSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1193
|
+
send(`Switched to Cursor Agent.${sid}\n\n/claude — switch back`);
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
bot.onText(/\/claude$/, async (msg) => {
|
|
1197
|
+
if (!isAuthorized(msg)) return;
|
|
1198
|
+
settings.backend = "claude";
|
|
1199
|
+
settings.model = null;
|
|
1200
|
+
saveState();
|
|
1201
|
+
const sid = lastSessionId ? `\nSession: ${lastSessionId.slice(0, 8)}...` : "\nNew session.";
|
|
1202
|
+
send(`Switched to Claude Code.${sid}\n\n/cursor — switch to Cursor`);
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
bot.onText(/\/backend$/, async (msg) => {
|
|
1206
|
+
if (!isAuthorized(msg)) return;
|
|
1207
|
+
const label = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1208
|
+
const cursorAvail = resolvedCursorPath ? "available" : "not found";
|
|
1209
|
+
send(`Backend: ${label}\n\nClaude Code: available\nCursor Agent: ${cursorAvail}`, { keyboard: { inline_keyboard: [
|
|
1210
|
+
[{ text: "Claude Code", callback_data: "be:claude" }, { text: "Cursor Agent", callback_data: "be:cursor" }],
|
|
1211
|
+
] } });
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1144
1214
|
bot.onText(/\/mode$/, async (msg) => {
|
|
1145
1215
|
if (!isAuthorized(msg)) return;
|
|
1146
1216
|
await send("Bot mode: *direct* (default)\n\nSwitch to agent mode for non-blocking execution.\nIn agent mode, heavy tasks run in the background and you can keep chatting.", {
|
|
@@ -1176,8 +1246,10 @@ bot.onText(/\/stop/, async (msg) => {
|
|
|
1176
1246
|
bot.onText(/\/status/, (msg) => {
|
|
1177
1247
|
if (!isAuthorized(msg)) return;
|
|
1178
1248
|
if (!currentSession) return send("No session.", { keyboard: { inline_keyboard: [[{ text: "Pick", callback_data: "show:projects" }]] } });
|
|
1249
|
+
const backendLabel = settings.backend === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1179
1250
|
send([
|
|
1180
1251
|
`Project: ${currentSession.name}`,
|
|
1252
|
+
`Backend: ${backendLabel}`,
|
|
1181
1253
|
`Model: ${settings.model || "default"} | Effort: ${settings.effort || "default"}`,
|
|
1182
1254
|
`Vault: ${vault.isUnlocked() ? "unlocked" : "locked"} | Crons: ${activeCrons.size}`,
|
|
1183
1255
|
runningProcess ? "Working..." : "Ready.",
|
|
@@ -1317,6 +1389,16 @@ bot.on("callback_query", async (q) => {
|
|
|
1317
1389
|
if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
|
|
1318
1390
|
if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
|
|
1319
1391
|
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; }
|
|
1392
|
+
if (d.startsWith("be:")) {
|
|
1393
|
+
const be = d.slice(3);
|
|
1394
|
+
if (be === "cursor" && !resolvedCursorPath) { await send("Cursor Agent CLI not found."); return; }
|
|
1395
|
+
settings.backend = be;
|
|
1396
|
+
settings.model = null;
|
|
1397
|
+
saveState();
|
|
1398
|
+
const label = be === "cursor" ? "Cursor Agent" : "Claude Code";
|
|
1399
|
+
await send(`Switched to ${label}.`);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1320
1402
|
|
|
1321
1403
|
// Mode switching — writes mode file and restarts bot
|
|
1322
1404
|
if (d.startsWith("mode:")) {
|