@inetafrica/open-claudia 1.10.1 → 1.12.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.12.0
4
+ - Cursor Agent tool progress: Shell, Read, Edit, Write, Grep, Glob calls now show in real-time Telegram updates
5
+ - Plan output surfaced to Telegram: when Cursor creates a plan (`--mode plan`), the full plan markdown and task list are sent to the user
6
+ - Handles all Cursor tool_call event types with fallback for unknown tools
7
+
8
+ ## v1.11.0
9
+ - Backend-aware plan mode: /plan passes `--mode plan` to Cursor Agent, `--permission-mode plan` to Claude
10
+ - New /ask command: read-only Q&A mode (Cursor Agent only, `--mode ask`)
11
+ - /effort and /budget now warn when on Cursor backend (unsupported flags)
12
+ - Worktree flag (`--worktree`) wired into Cursor Agent args
13
+ - buildCursorArgs now forwards plan/ask/worktree settings
14
+
3
15
  ## v1.10.0
4
16
  - Cursor Agent backend: switch between Claude Code and Cursor Agent CLI
5
17
  - New commands: /cursor, /claude, /backend with inline keyboard
package/README.md CHANGED
@@ -126,7 +126,8 @@ When you select a project, the last conversation is automatically resumed. Tap "
126
126
  | `/model` | Switch model (opus / sonnet / haiku for Claude; any model flag for Cursor) |
127
127
  | `/effort` | Set effort level (low / medium / high / max) |
128
128
  | `/budget` | Set max spend for next task (e.g. `/budget 0.50`) — Claude only |
129
- | `/plan` | Toggle plan mode — 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) |
130
131
  | `/compact` | Summarize conversation context |
131
132
  | `/worktree` | Toggle isolated git branch — Claude only |
132
133
  | `/mode` | Switch between direct and agent bot modes |
@@ -158,9 +159,10 @@ When you select a project, the last conversation is automatically resumed. Tap "
158
159
  | Session flag | `--resume <id>` | `--resume <id>` |
159
160
  | Auth | `claude auth` | `agent login` |
160
161
  | Plan mode | Yes (`--permission-mode plan`) | Yes (`--mode plan`) |
162
+ | Ask mode | No | Yes (`--mode ask`) |
161
163
  | Budget control | Yes (`--max-budget-usd`) | No |
162
164
  | Effort levels | Yes | No |
163
- | Worktree | Yes | No |
165
+ | Worktree | Yes (`--worktree`) | Yes (`--worktree`) |
164
166
  | Model switching | Yes | Yes |
165
167
  | Dangerously skip permissions | Yes | Yes (`--trust`) |
166
168
 
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
  }
@@ -841,7 +845,6 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
841
845
  else if (block.type === "tool_use") {
842
846
  currentTool = block.name;
843
847
  toolUses.push(block.name);
844
- // Extract useful detail from tool input
845
848
  const input = block.input || {};
846
849
  if (block.name === "Bash" && input.command) currentToolDetail = input.command.slice(0, 80);
847
850
  else if (block.name === "Read" && input.file_path) currentToolDetail = input.file_path.split("/").slice(-2).join("/");
@@ -853,6 +856,48 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
853
856
  }
854
857
  }
855
858
  }
859
+ // Cursor Agent tool_call events (different format from Claude's tool_use blocks)
860
+ if (evt.type === "tool_call" && evt.subtype === "started" && evt.tool_call) {
861
+ const tc = evt.tool_call;
862
+ if (tc.shellToolCall) {
863
+ const a = tc.shellToolCall.args || {};
864
+ currentTool = "Shell"; toolUses.push("Shell");
865
+ currentToolDetail = (a.description || a.command || "").slice(0, 80);
866
+ } else if (tc.readToolCall) {
867
+ currentTool = "Read"; toolUses.push("Read");
868
+ currentToolDetail = (tc.readToolCall.args?.path || "").split("/").slice(-2).join("/");
869
+ } else if (tc.editToolCall) {
870
+ currentTool = "Edit"; toolUses.push("Edit");
871
+ currentToolDetail = (tc.editToolCall.args?.filePath || "").split("/").slice(-2).join("/");
872
+ } else if (tc.writeToolCall) {
873
+ currentTool = "Write"; toolUses.push("Write");
874
+ currentToolDetail = (tc.writeToolCall.args?.filePath || "").split("/").slice(-2).join("/");
875
+ } else if (tc.grepToolCall) {
876
+ currentTool = "Grep"; toolUses.push("Grep");
877
+ currentToolDetail = (tc.grepToolCall.args?.pattern || "").slice(0, 40);
878
+ } else if (tc.globToolCall) {
879
+ currentTool = "Glob"; toolUses.push("Glob");
880
+ currentToolDetail = (tc.globToolCall.args?.globPattern || "").slice(0, 40);
881
+ } else if (tc.createPlanToolCall) {
882
+ currentTool = "Plan"; toolUses.push("Plan");
883
+ const plan = tc.createPlanToolCall.args || {};
884
+ let planText = "";
885
+ if (plan.name) planText += `**${plan.name}**\n\n`;
886
+ if (plan.plan) planText += plan.plan + "\n";
887
+ if (plan.todos && plan.todos.length) {
888
+ planText += "\n**Tasks:**\n";
889
+ for (const todo of plan.todos) {
890
+ planText += `• ${todo.content || todo.id}\n`;
891
+ }
892
+ }
893
+ if (planText) assistantText = planText;
894
+ currentToolDetail = plan.name || "creating plan";
895
+ } else {
896
+ const toolKey = Object.keys(tc)[0] || "unknown";
897
+ currentTool = toolKey.replace("ToolCall", ""); toolUses.push(currentTool);
898
+ currentToolDetail = "";
899
+ }
900
+ }
856
901
  if (evt.type === "result" && evt.session_id) {
857
902
  if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
858
903
  else { lastSessionId = evt.session_id; }
@@ -1126,23 +1171,46 @@ bot.onText(/\/model (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; set
1126
1171
 
1127
1172
  bot.onText(/\/effort$/, (msg) => {
1128
1173
  if (!isAuthorized(msg)) return;
1174
+ if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
1129
1175
  send(`Effort: ${settings.effort || "default"}`, { keyboard: { inline_keyboard: [
1130
1176
  [{ text: "Low", callback_data: "e:low" }, { text: "Med", callback_data: "e:medium" }, { text: "High", callback_data: "e:high" }, { text: "Max", callback_data: "e:max" }],
1131
1177
  [{ text: "Default", callback_data: "e:default" }],
1132
1178
  ] } });
1133
1179
  });
1134
- bot.onText(/\/effort (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`); });
1180
+ bot.onText(/\/effort (.+)/, (msg, match) => {
1181
+ if (!isAuthorized(msg)) return;
1182
+ if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.");
1183
+ const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`);
1184
+ });
1135
1185
 
1136
1186
  bot.onText(/\/budget$/, (msg) => {
1137
1187
  if (!isAuthorized(msg)) return;
1188
+ if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
1138
1189
  send(`Budget: ${settings.budget ? "$" + settings.budget : "none"}`, { keyboard: { inline_keyboard: [
1139
1190
  [{ text: "$1", callback_data: "b:1" }, { text: "$5", callback_data: "b:5" }, { text: "$10", callback_data: "b:10" }, { text: "$25", callback_data: "b:25" }],
1140
1191
  [{ text: "No limit", callback_data: "b:none" }],
1141
1192
  ] } });
1142
1193
  });
1143
- 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"}`); });
1194
+ bot.onText(/\/budget (.+)/, (msg, match) => {
1195
+ if (!isAuthorized(msg)) return;
1196
+ if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.");
1197
+ const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`);
1198
+ });
1144
1199
 
1145
- 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."); });
1200
+ bot.onText(/\/plan$/, (msg) => {
1201
+ if (!isAuthorized(msg)) return;
1202
+ const p = settings.permissionMode === "plan";
1203
+ settings.permissionMode = p ? null : "plan";
1204
+ const label = settings.backend === "cursor" ? "read-only planning, no edits" : "plan permission mode";
1205
+ send(p ? "Plan mode off." : `Plan mode on (${label}).`);
1206
+ });
1207
+ bot.onText(/\/ask$/, (msg) => {
1208
+ if (!isAuthorized(msg)) return;
1209
+ if (settings.backend !== "cursor") return send("Ask mode is only available on Cursor Agent.\nUse /cursor to switch.");
1210
+ const a = settings.permissionMode === "ask";
1211
+ settings.permissionMode = a ? null : "ask";
1212
+ send(a ? "Ask mode off." : "Ask mode on (read-only Q&A, no edits).");
1213
+ });
1146
1214
  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); });
1147
1215
  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 }); });
1148
1216
  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
  }
@@ -857,7 +861,6 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
857
861
  else if (block.type === "tool_use") {
858
862
  currentTool = block.name;
859
863
  toolUses.push(block.name);
860
- // Extract useful detail from tool input
861
864
  const input = block.input || {};
862
865
  if (block.name === "Bash" && input.command) currentToolDetail = input.command.slice(0, 80);
863
866
  else if (block.name === "Read" && input.file_path) currentToolDetail = input.file_path.split("/").slice(-2).join("/");
@@ -869,6 +872,48 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
869
872
  }
870
873
  }
871
874
  }
875
+ // Cursor Agent tool_call events (different format from Claude's tool_use blocks)
876
+ if (evt.type === "tool_call" && evt.subtype === "started" && evt.tool_call) {
877
+ const tc = evt.tool_call;
878
+ if (tc.shellToolCall) {
879
+ const a = tc.shellToolCall.args || {};
880
+ currentTool = "Shell"; toolUses.push("Shell");
881
+ currentToolDetail = (a.description || a.command || "").slice(0, 80);
882
+ } else if (tc.readToolCall) {
883
+ currentTool = "Read"; toolUses.push("Read");
884
+ currentToolDetail = (tc.readToolCall.args?.path || "").split("/").slice(-2).join("/");
885
+ } else if (tc.editToolCall) {
886
+ currentTool = "Edit"; toolUses.push("Edit");
887
+ currentToolDetail = (tc.editToolCall.args?.filePath || "").split("/").slice(-2).join("/");
888
+ } else if (tc.writeToolCall) {
889
+ currentTool = "Write"; toolUses.push("Write");
890
+ currentToolDetail = (tc.writeToolCall.args?.filePath || "").split("/").slice(-2).join("/");
891
+ } else if (tc.grepToolCall) {
892
+ currentTool = "Grep"; toolUses.push("Grep");
893
+ currentToolDetail = (tc.grepToolCall.args?.pattern || "").slice(0, 40);
894
+ } else if (tc.globToolCall) {
895
+ currentTool = "Glob"; toolUses.push("Glob");
896
+ currentToolDetail = (tc.globToolCall.args?.globPattern || "").slice(0, 40);
897
+ } else if (tc.createPlanToolCall) {
898
+ currentTool = "Plan"; toolUses.push("Plan");
899
+ const plan = tc.createPlanToolCall.args || {};
900
+ let planText = "";
901
+ if (plan.name) planText += `**${plan.name}**\n\n`;
902
+ if (plan.plan) planText += plan.plan + "\n";
903
+ if (plan.todos && plan.todos.length) {
904
+ planText += "\n**Tasks:**\n";
905
+ for (const todo of plan.todos) {
906
+ planText += `• ${todo.content || todo.id}\n`;
907
+ }
908
+ }
909
+ if (planText) assistantText = planText;
910
+ currentToolDetail = plan.name || "creating plan";
911
+ } else {
912
+ const toolKey = Object.keys(tc)[0] || "unknown";
913
+ currentTool = toolKey.replace("ToolCall", ""); toolUses.push(currentTool);
914
+ currentToolDetail = "";
915
+ }
916
+ }
872
917
  if (evt.type === "result" && evt.session_id) {
873
918
  if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
874
919
  else { lastSessionId = evt.session_id; }
@@ -1169,23 +1214,47 @@ bot.onText(/\/model (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; set
1169
1214
 
1170
1215
  bot.onText(/\/effort$/, (msg) => {
1171
1216
  if (!isAuthorized(msg)) return;
1217
+ if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
1172
1218
  send(`Effort: ${settings.effort || "default"}`, { keyboard: { inline_keyboard: [
1173
1219
  [{ text: "Low", callback_data: "e:low" }, { text: "Med", callback_data: "e:medium" }, { text: "High", callback_data: "e:high" }, { text: "Max", callback_data: "e:max" }],
1174
1220
  [{ text: "Default", callback_data: "e:default" }],
1175
1221
  ] } });
1176
1222
  });
1177
- bot.onText(/\/effort (.+)/, (msg, match) => { if (!isAuthorized(msg)) return; const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`); });
1223
+ bot.onText(/\/effort (.+)/, (msg, match) => {
1224
+ if (!isAuthorized(msg)) return;
1225
+ if (settings.backend === "cursor") return send("Effort levels are not supported on Cursor Agent.");
1226
+ const e = match[1].trim().toLowerCase(); settings.effort = ["low","medium","high","max"].includes(e) ? e : null; send(`Effort: ${settings.effort || "default"}`);
1227
+ });
1178
1228
 
1179
1229
  bot.onText(/\/budget$/, (msg) => {
1180
1230
  if (!isAuthorized(msg)) return;
1231
+ if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.\nSwitch to Claude with /claude to use this.");
1181
1232
  send(`Budget: ${settings.budget ? "$" + settings.budget : "none"}`, { keyboard: { inline_keyboard: [
1182
1233
  [{ text: "$1", callback_data: "b:1" }, { text: "$5", callback_data: "b:5" }, { text: "$10", callback_data: "b:10" }, { text: "$25", callback_data: "b:25" }],
1183
1234
  [{ text: "No limit", callback_data: "b:none" }],
1184
1235
  ] } });
1185
1236
  });
1186
- 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"}`); });
1237
+ bot.onText(/\/budget (.+)/, (msg, match) => {
1238
+ if (!isAuthorized(msg)) return;
1239
+ if (settings.backend === "cursor") return send("Budget limits are not supported on Cursor Agent.");
1240
+ const v = parseFloat(match[1].replace("$","")); settings.budget = v > 0 ? v : null; send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`);
1241
+ });
1187
1242
 
1188
- 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."); });
1243
+ bot.onText(/\/plan$/, (msg) => {
1244
+ if (!isAuthorized(msg)) return;
1245
+ const p = settings.permissionMode === "plan";
1246
+ settings.permissionMode = p ? null : "plan";
1247
+ const label = settings.backend === "cursor" ? "read-only planning, no edits" : "plan permission mode";
1248
+ if (p) cursorSessionId = null; // reset session so next message doesn't resume plan-mode session
1249
+ send(p ? "Plan mode off (session reset)." : `Plan mode on (${label}).`);
1250
+ });
1251
+ bot.onText(/\/ask$/, (msg) => {
1252
+ if (!isAuthorized(msg)) return;
1253
+ if (settings.backend !== "cursor") return send("Ask mode is only available on Cursor Agent.\nUse /cursor to switch.");
1254
+ const a = settings.permissionMode === "ask";
1255
+ settings.permissionMode = a ? null : "ask";
1256
+ send(a ? "Ask mode off." : "Ask mode on (read-only Q&A, no edits).");
1257
+ });
1189
1258
  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); });
1190
1259
  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 }); });
1191
1260
  bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "1.10.1",
3
+ "version": "1.12.1",
4
4
  "description": "Your always-on AI coding assistant — Claude Code via Telegram",
5
5
  "main": "bot.js",
6
6
  "bin": {