@inetafrica/open-claudia 1.11.0 → 1.13.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 +5 -0
- package/bot-agent.js +42 -1
- package/bot.js +60 -2
- package/health.js +170 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
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
|
+
|
|
3
8
|
## v1.11.0
|
|
4
9
|
- Backend-aware plan mode: /plan passes `--mode plan` to Cursor Agent, `--permission-mode plan` to Claude
|
|
5
10
|
- New /ask command: read-only Q&A mode (Cursor Agent only, `--mode ask`)
|
package/bot-agent.js
CHANGED
|
@@ -845,7 +845,6 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
845
845
|
else if (block.type === "tool_use") {
|
|
846
846
|
currentTool = block.name;
|
|
847
847
|
toolUses.push(block.name);
|
|
848
|
-
// Extract useful detail from tool input
|
|
849
848
|
const input = block.input || {};
|
|
850
849
|
if (block.name === "Bash" && input.command) currentToolDetail = input.command.slice(0, 80);
|
|
851
850
|
else if (block.name === "Read" && input.file_path) currentToolDetail = input.file_path.split("/").slice(-2).join("/");
|
|
@@ -857,6 +856,48 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
857
856
|
}
|
|
858
857
|
}
|
|
859
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
|
+
}
|
|
860
901
|
if (evt.type === "result" && evt.session_id) {
|
|
861
902
|
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
862
903
|
else { lastSessionId = evt.session_id; }
|
package/bot.js
CHANGED
|
@@ -861,7 +861,6 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
861
861
|
else if (block.type === "tool_use") {
|
|
862
862
|
currentTool = block.name;
|
|
863
863
|
toolUses.push(block.name);
|
|
864
|
-
// Extract useful detail from tool input
|
|
865
864
|
const input = block.input || {};
|
|
866
865
|
if (block.name === "Bash" && input.command) currentToolDetail = input.command.slice(0, 80);
|
|
867
866
|
else if (block.name === "Read" && input.file_path) currentToolDetail = input.file_path.split("/").slice(-2).join("/");
|
|
@@ -873,6 +872,48 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
873
872
|
}
|
|
874
873
|
}
|
|
875
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
|
+
}
|
|
876
917
|
if (evt.type === "result" && evt.session_id) {
|
|
877
918
|
if (settings.backend === "cursor") { cursorSessionId = evt.session_id; }
|
|
878
919
|
else { lastSessionId = evt.session_id; }
|
|
@@ -1106,6 +1147,11 @@ bot.onText(/\/upgrade$/, async (msg) => {
|
|
|
1106
1147
|
cwd: process.env.HOME || require("os").homedir(),
|
|
1107
1148
|
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME || require("os").homedir() },
|
|
1108
1149
|
});
|
|
1150
|
+
// Run idempotent setup (install/configure tools like Playwright MCP)
|
|
1151
|
+
try {
|
|
1152
|
+
const { ensureSetup: postUpgradeSetup } = require("./health");
|
|
1153
|
+
postUpgradeSetup();
|
|
1154
|
+
} catch (e) { /* setup will run on next boot anyway */ }
|
|
1109
1155
|
// Read version and changelog from newly installed package
|
|
1110
1156
|
const root = execSync("npm root -g", { encoding: "utf-8", cwd: process.env.HOME || require("os").homedir(), env: { ...process.env, PATH: FULL_PATH } }).trim();
|
|
1111
1157
|
const newPkg = JSON.parse(fs.readFileSync(path.join(root, "@inetafrica", "open-claudia", "package.json"), "utf-8"));
|
|
@@ -1204,7 +1250,8 @@ bot.onText(/\/plan$/, (msg) => {
|
|
|
1204
1250
|
const p = settings.permissionMode === "plan";
|
|
1205
1251
|
settings.permissionMode = p ? null : "plan";
|
|
1206
1252
|
const label = settings.backend === "cursor" ? "read-only planning, no edits" : "plan permission mode";
|
|
1207
|
-
|
|
1253
|
+
if (p) cursorSessionId = null; // reset session so next message doesn't resume plan-mode session
|
|
1254
|
+
send(p ? "Plan mode off (session reset)." : `Plan mode on (${label}).`);
|
|
1208
1255
|
});
|
|
1209
1256
|
bot.onText(/\/ask$/, (msg) => {
|
|
1210
1257
|
if (!isAuthorized(msg)) return;
|
|
@@ -1647,6 +1694,17 @@ bot.on("message", async (msg) => {
|
|
|
1647
1694
|
});
|
|
1648
1695
|
|
|
1649
1696
|
// ── Startup ─────────────────────────────────────────────────────────
|
|
1697
|
+
// Idempotent setup: ensure tools & config are in place (safe on every boot)
|
|
1698
|
+
try {
|
|
1699
|
+
const { ensureSetup, formatSetupResults } = require("./health");
|
|
1700
|
+
const setupResult = ensureSetup();
|
|
1701
|
+
console.log("Setup check:");
|
|
1702
|
+
console.log(formatSetupResults(setupResult));
|
|
1703
|
+
if (!setupResult.ok) console.warn("Some setup steps failed — browser tools may be unavailable.");
|
|
1704
|
+
} catch (e) {
|
|
1705
|
+
console.warn("Setup check skipped:", e.message);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1650
1708
|
initCrons();
|
|
1651
1709
|
console.log("Claude Code Telegram bot running");
|
|
1652
1710
|
console.log(`Workspace: ${WORKSPACE}`);
|
package/health.js
CHANGED
|
@@ -336,6 +336,34 @@ async function runHealthChecks(options = {}) {
|
|
|
336
336
|
results.warnings.push(`FFmpeg not found at: ${env.FFMPEG} (voice notes disabled)`);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
// 12. Check Playwright MCP setup
|
|
340
|
+
try {
|
|
341
|
+
const root = execSync("npm root -g", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
342
|
+
const pwMcpPkg = path.join(root, "@playwright", "mcp", "package.json");
|
|
343
|
+
if (!fs.existsSync(pwMcpPkg)) {
|
|
344
|
+
results.warnings.push("@playwright/mcp not installed globally (browser tools unavailable)");
|
|
345
|
+
} else {
|
|
346
|
+
results.checks.playwright_mcp = { ok: true };
|
|
347
|
+
}
|
|
348
|
+
} catch (e) {
|
|
349
|
+
results.warnings.push("Could not check @playwright/mcp installation");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (fs.existsSync(MCP_CONFIG_FILE)) {
|
|
353
|
+
try {
|
|
354
|
+
const mcpCfg = JSON.parse(fs.readFileSync(MCP_CONFIG_FILE, "utf-8"));
|
|
355
|
+
if (!mcpCfg.mcpServers || !mcpCfg.mcpServers.playwright) {
|
|
356
|
+
results.warnings.push("Playwright MCP not configured in ~/.claude/mcp.json");
|
|
357
|
+
} else {
|
|
358
|
+
results.checks.mcp_config = { ok: true };
|
|
359
|
+
}
|
|
360
|
+
} catch (e) {
|
|
361
|
+
results.warnings.push("~/.claude/mcp.json is malformed");
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
results.warnings.push("~/.claude/mcp.json not found (MCP tools unavailable)");
|
|
365
|
+
}
|
|
366
|
+
|
|
339
367
|
return results;
|
|
340
368
|
}
|
|
341
369
|
|
|
@@ -376,6 +404,144 @@ function formatResults(results, verbose = false) {
|
|
|
376
404
|
return lines.join("\n");
|
|
377
405
|
}
|
|
378
406
|
|
|
407
|
+
// ── Idempotent setup: ensures required tools and config are in place ──
|
|
408
|
+
|
|
409
|
+
const MCP_CONFIG_FILE = path.join(process.env.HOME || require("os").homedir(), ".claude", "mcp.json");
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Ensure @playwright/mcp is installed globally.
|
|
413
|
+
* Returns { installed: bool, alreadyInstalled: bool, error?: string }
|
|
414
|
+
*/
|
|
415
|
+
function ensurePlaywrightMcp() {
|
|
416
|
+
// Check if already installed
|
|
417
|
+
try {
|
|
418
|
+
const root = execSync("npm root -g", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
419
|
+
const pkgPath = path.join(root, "@playwright", "mcp", "package.json");
|
|
420
|
+
if (fs.existsSync(pkgPath)) {
|
|
421
|
+
return { installed: true, alreadyInstalled: true };
|
|
422
|
+
}
|
|
423
|
+
} catch (e) { /* not installed */ }
|
|
424
|
+
|
|
425
|
+
// Install it
|
|
426
|
+
try {
|
|
427
|
+
execSync("npm install -g @playwright/mcp@latest 2>&1", {
|
|
428
|
+
encoding: "utf-8",
|
|
429
|
+
timeout: 120000,
|
|
430
|
+
stdio: "pipe",
|
|
431
|
+
env: { ...process.env, PATH: process.env.PATH },
|
|
432
|
+
});
|
|
433
|
+
return { installed: true, alreadyInstalled: false };
|
|
434
|
+
} catch (e) {
|
|
435
|
+
return { installed: false, error: (e.stderr || e.message || "").slice(-300) };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Ensure ~/.claude/mcp.json has the Playwright MCP server configured.
|
|
441
|
+
* Idempotent — won't duplicate if already present.
|
|
442
|
+
* Returns { configured: bool, alreadyConfigured: bool, error?: string }
|
|
443
|
+
*/
|
|
444
|
+
function ensureMcpConfig() {
|
|
445
|
+
const claudeDir = path.dirname(MCP_CONFIG_FILE);
|
|
446
|
+
|
|
447
|
+
// Ensure ~/.claude directory exists
|
|
448
|
+
if (!fs.existsSync(claudeDir)) {
|
|
449
|
+
try {
|
|
450
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
451
|
+
} catch (e) {
|
|
452
|
+
return { configured: false, error: `Failed to create ${claudeDir}: ${e.message}` };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Load existing config or start fresh
|
|
457
|
+
let mcpConfig = { mcpServers: {} };
|
|
458
|
+
if (fs.existsSync(MCP_CONFIG_FILE)) {
|
|
459
|
+
try {
|
|
460
|
+
mcpConfig = JSON.parse(fs.readFileSync(MCP_CONFIG_FILE, "utf-8"));
|
|
461
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
462
|
+
} catch (e) {
|
|
463
|
+
// Corrupted config — back up and start fresh
|
|
464
|
+
const backup = MCP_CONFIG_FILE + ".bak." + Date.now();
|
|
465
|
+
try { fs.copyFileSync(MCP_CONFIG_FILE, backup); } catch (e2) {}
|
|
466
|
+
mcpConfig = { mcpServers: {} };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Check if Playwright MCP is already configured
|
|
471
|
+
if (mcpConfig.mcpServers.playwright) {
|
|
472
|
+
return { configured: true, alreadyConfigured: true };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Find the installed binary
|
|
476
|
+
let mcpBin = "playwright-mcp";
|
|
477
|
+
try {
|
|
478
|
+
const resolved = execSync("which playwright-mcp", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
479
|
+
if (resolved) mcpBin = resolved;
|
|
480
|
+
} catch (e) {
|
|
481
|
+
// Try npx path
|
|
482
|
+
try {
|
|
483
|
+
const root = execSync("npm root -g", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
484
|
+
const binPath = path.join(root, "@playwright", "mcp", "cli.js");
|
|
485
|
+
if (fs.existsSync(binPath)) mcpBin = binPath;
|
|
486
|
+
} catch (e2) {}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Add Playwright MCP server config
|
|
490
|
+
mcpConfig.mcpServers.playwright = {
|
|
491
|
+
command: "npx",
|
|
492
|
+
args: ["@playwright/mcp@latest", "--headless"],
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Write config
|
|
496
|
+
try {
|
|
497
|
+
fs.writeFileSync(MCP_CONFIG_FILE, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
498
|
+
return { configured: true, alreadyConfigured: false };
|
|
499
|
+
} catch (e) {
|
|
500
|
+
return { configured: false, error: `Failed to write ${MCP_CONFIG_FILE}: ${e.message}` };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Run all idempotent setup steps.
|
|
506
|
+
* Safe to call on every startup and after every upgrade.
|
|
507
|
+
* Returns { ok: bool, steps: { name: result } }
|
|
508
|
+
*/
|
|
509
|
+
function ensureSetup() {
|
|
510
|
+
const results = { ok: true, steps: {} };
|
|
511
|
+
|
|
512
|
+
// Step 1: Install Playwright MCP globally
|
|
513
|
+
results.steps.playwright_mcp = ensurePlaywrightMcp();
|
|
514
|
+
if (!results.steps.playwright_mcp.installed) {
|
|
515
|
+
results.ok = false;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Step 2: Configure MCP in Claude Code config
|
|
519
|
+
results.steps.mcp_config = ensureMcpConfig();
|
|
520
|
+
if (!results.steps.mcp_config.configured) {
|
|
521
|
+
results.ok = false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return results;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Format ensureSetup results for logging/display
|
|
529
|
+
*/
|
|
530
|
+
function formatSetupResults(results) {
|
|
531
|
+
const lines = [];
|
|
532
|
+
for (const [name, result] of Object.entries(results.steps)) {
|
|
533
|
+
const label = name.replace(/_/g, " ");
|
|
534
|
+
if (result.alreadyInstalled || result.alreadyConfigured) {
|
|
535
|
+
lines.push(` ${label}: already set up`);
|
|
536
|
+
} else if (result.installed || result.configured) {
|
|
537
|
+
lines.push(` ${label}: configured`);
|
|
538
|
+
} else {
|
|
539
|
+
lines.push(` ${label}: FAILED — ${result.error || "unknown error"}`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return lines.join("\n");
|
|
543
|
+
}
|
|
544
|
+
|
|
379
545
|
module.exports = {
|
|
380
546
|
runHealthChecks,
|
|
381
547
|
formatResults,
|
|
@@ -386,6 +552,10 @@ module.exports = {
|
|
|
386
552
|
checkNodeVersion,
|
|
387
553
|
checkForConflictingProcesses,
|
|
388
554
|
binaryExists,
|
|
555
|
+
ensureSetup,
|
|
556
|
+
formatSetupResults,
|
|
557
|
+
ensurePlaywrightMcp,
|
|
558
|
+
ensureMcpConfig,
|
|
389
559
|
REQUIRED_ENV_KEYS,
|
|
390
560
|
OPTIONAL_ENV_KEYS,
|
|
391
561
|
};
|