@gonzih/cc-tg 0.2.5 → 0.2.7
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/dist/bot.d.ts +9 -0
- package/dist/bot.js +199 -3
- package/dist/cron.d.ts +4 -0
- package/dist/cron.js +24 -0
- package/package.json +8 -2
package/dist/bot.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class CcTgBot {
|
|
|
14
14
|
private opts;
|
|
15
15
|
private cron;
|
|
16
16
|
constructor(opts: BotOptions);
|
|
17
|
+
private registerBotCommands;
|
|
17
18
|
private isAllowed;
|
|
18
19
|
private handleTelegram;
|
|
19
20
|
private handleVoice;
|
|
@@ -29,6 +30,14 @@ export declare class CcTgBot {
|
|
|
29
30
|
private uploadMentionedFiles;
|
|
30
31
|
private extractToolName;
|
|
31
32
|
private handleCron;
|
|
33
|
+
private handleCronEdit;
|
|
34
|
+
/** Find cc-agent PIDs via pgrep. Returns array of numeric PIDs. */
|
|
35
|
+
private findCcAgentPids;
|
|
36
|
+
/** Kill cc-agent PIDs with SIGTERM. Returns the list of killed PIDs. */
|
|
37
|
+
private killCcAgent;
|
|
38
|
+
private handleReloadMcp;
|
|
39
|
+
private handleMcpVersion;
|
|
40
|
+
private handleClearNpxCache;
|
|
32
41
|
private killSession;
|
|
33
42
|
stop(): void;
|
|
34
43
|
}
|
package/dist/bot.js
CHANGED
|
@@ -5,11 +5,23 @@
|
|
|
5
5
|
import TelegramBot from "node-telegram-bot-api";
|
|
6
6
|
import { existsSync, createWriteStream, mkdirSync } from "fs";
|
|
7
7
|
import { resolve, basename, join } from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
8
9
|
import https from "https";
|
|
9
10
|
import http from "http";
|
|
10
11
|
import { ClaudeProcess, extractText } from "./claude.js";
|
|
11
12
|
import { transcribeVoice, isVoiceAvailable } from "./voice.js";
|
|
12
13
|
import { CronManager } from "./cron.js";
|
|
14
|
+
const BOT_COMMANDS = [
|
|
15
|
+
{ command: "start", description: "Reset session and start fresh" },
|
|
16
|
+
{ command: "reset", description: "Reset Claude session" },
|
|
17
|
+
{ command: "stop", description: "Stop the current Claude task" },
|
|
18
|
+
{ command: "status", description: "Check if a session is active" },
|
|
19
|
+
{ command: "help", description: "Show all available commands" },
|
|
20
|
+
{ command: "cron", description: "Manage cron jobs — add/list/edit/remove/clear" },
|
|
21
|
+
{ command: "reload_mcp", description: "Restart the cc-agent MCP server process" },
|
|
22
|
+
{ command: "mcp_version", description: "Show cc-agent npm version and npx cache info" },
|
|
23
|
+
{ command: "clear_npx_cache", description: "Clear npx cache and restart MCP to pick up latest version" },
|
|
24
|
+
];
|
|
13
25
|
const FLUSH_DELAY_MS = 800; // debounce streaming chunks into one Telegram message
|
|
14
26
|
const TYPING_INTERVAL_MS = 4000; // re-send typing action before Telegram's 5s expiry
|
|
15
27
|
export class CcTgBot {
|
|
@@ -35,9 +47,15 @@ export class CcTgBot {
|
|
|
35
47
|
console.error(`[cron] failed to fire for chat=${chatId}:`, err.message);
|
|
36
48
|
}
|
|
37
49
|
});
|
|
50
|
+
this.registerBotCommands();
|
|
38
51
|
console.log("cc-tg bot started");
|
|
39
52
|
console.log(`[voice] whisper available: ${isVoiceAvailable()}`);
|
|
40
53
|
}
|
|
54
|
+
registerBotCommands() {
|
|
55
|
+
this.bot.setMyCommands(BOT_COMMANDS)
|
|
56
|
+
.then(() => console.log("[tg] bot commands registered"))
|
|
57
|
+
.catch((err) => console.error("[tg] setMyCommands failed:", err.message));
|
|
58
|
+
}
|
|
41
59
|
isAllowed(userId) {
|
|
42
60
|
if (!this.opts.allowedUserIds?.length)
|
|
43
61
|
return true;
|
|
@@ -81,6 +99,12 @@ export class CcTgBot {
|
|
|
81
99
|
await this.bot.sendMessage(chatId, has ? "Stopped." : "No active session.");
|
|
82
100
|
return;
|
|
83
101
|
}
|
|
102
|
+
// /help — list all commands
|
|
103
|
+
if (text === "/help") {
|
|
104
|
+
const lines = BOT_COMMANDS.map((c) => `/${c.command} — ${c.description}`);
|
|
105
|
+
await this.bot.sendMessage(chatId, lines.join("\n"));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
84
108
|
// /status
|
|
85
109
|
if (text === "/status") {
|
|
86
110
|
const has = this.sessions.has(chatId);
|
|
@@ -92,6 +116,21 @@ export class CcTgBot {
|
|
|
92
116
|
await this.handleCron(chatId, text);
|
|
93
117
|
return;
|
|
94
118
|
}
|
|
119
|
+
// /reload_mcp — kill cc-agent process so Claude Code auto-restarts it
|
|
120
|
+
if (text === "/reload_mcp") {
|
|
121
|
+
await this.handleReloadMcp(chatId);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// /mcp_version — show published npm version and cached npx entries
|
|
125
|
+
if (text === "/mcp_version") {
|
|
126
|
+
await this.handleMcpVersion(chatId);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// /clear_npx_cache — wipe ~/.npm/_npx/ then restart cc-agent
|
|
130
|
+
if (text === "/clear_npx_cache") {
|
|
131
|
+
await this.handleClearNpxCache(chatId);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
95
134
|
const session = this.getOrCreateSession(chatId);
|
|
96
135
|
try {
|
|
97
136
|
session.claude.sendPrompt(text);
|
|
@@ -378,8 +417,11 @@ export class CcTgBot {
|
|
|
378
417
|
await this.bot.sendMessage(chatId, "No cron jobs.");
|
|
379
418
|
return;
|
|
380
419
|
}
|
|
381
|
-
const lines = jobs.map((j) =>
|
|
382
|
-
|
|
420
|
+
const lines = jobs.map((j, i) => {
|
|
421
|
+
const short = j.prompt.length > 50 ? j.prompt.slice(0, 50) + "…" : j.prompt;
|
|
422
|
+
return `#${i + 1} ${j.schedule} — "${short}"`;
|
|
423
|
+
});
|
|
424
|
+
await this.bot.sendMessage(chatId, `Cron jobs (${jobs.length}):\n${lines.join("\n")}`);
|
|
383
425
|
return;
|
|
384
426
|
}
|
|
385
427
|
// /cron clear
|
|
@@ -395,10 +437,15 @@ export class CcTgBot {
|
|
|
395
437
|
await this.bot.sendMessage(chatId, ok ? `Removed ${id}.` : `Not found: ${id}`);
|
|
396
438
|
return;
|
|
397
439
|
}
|
|
440
|
+
// /cron edit [<#> ...]
|
|
441
|
+
if (args === "edit" || args.startsWith("edit ")) {
|
|
442
|
+
await this.handleCronEdit(chatId, args.slice("edit".length).trim());
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
398
445
|
// /cron every 1h <prompt>
|
|
399
446
|
const scheduleMatch = args.match(/^(every\s+\d+[mhd])\s+(.+)$/i);
|
|
400
447
|
if (!scheduleMatch) {
|
|
401
|
-
await this.bot.sendMessage(chatId, "Usage:\n/cron every 1h <prompt>\n/cron list\n/cron remove <id>\n/cron clear");
|
|
448
|
+
await this.bot.sendMessage(chatId, "Usage:\n/cron every 1h <prompt>\n/cron list\n/cron edit\n/cron remove <id>\n/cron clear");
|
|
402
449
|
return;
|
|
403
450
|
}
|
|
404
451
|
const schedule = scheduleMatch[1];
|
|
@@ -410,6 +457,155 @@ export class CcTgBot {
|
|
|
410
457
|
}
|
|
411
458
|
await this.bot.sendMessage(chatId, `Cron set [${job.id}]: ${schedule} — "${prompt}"`);
|
|
412
459
|
}
|
|
460
|
+
async handleCronEdit(chatId, editArgs) {
|
|
461
|
+
const jobs = this.cron.list(chatId);
|
|
462
|
+
// No args — show numbered list with edit instructions
|
|
463
|
+
if (!editArgs) {
|
|
464
|
+
if (!jobs.length) {
|
|
465
|
+
await this.bot.sendMessage(chatId, "No cron jobs to edit.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const lines = jobs.map((j, i) => {
|
|
469
|
+
const short = j.prompt.length > 50 ? j.prompt.slice(0, 50) + "…" : j.prompt;
|
|
470
|
+
return `#${i + 1} ${j.schedule} — "${short}"`;
|
|
471
|
+
});
|
|
472
|
+
await this.bot.sendMessage(chatId, `Cron jobs:\n${lines.join("\n")}\n\n` +
|
|
473
|
+
"Edit options:\n" +
|
|
474
|
+
"/cron edit <#> every <N><unit> <new prompt>\n" +
|
|
475
|
+
"/cron edit <#> schedule every <N><unit>\n" +
|
|
476
|
+
"/cron edit <#> prompt <new prompt>");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
// Expect: <index> <rest>
|
|
480
|
+
const indexMatch = editArgs.match(/^(\d+)\s+(.+)$/);
|
|
481
|
+
if (!indexMatch) {
|
|
482
|
+
await this.bot.sendMessage(chatId, "Usage: /cron edit <#> every <N><unit> <new prompt>");
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const index = parseInt(indexMatch[1], 10) - 1;
|
|
486
|
+
if (index < 0 || index >= jobs.length) {
|
|
487
|
+
await this.bot.sendMessage(chatId, `Invalid job number. Use /cron edit to see the list.`);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const job = jobs[index];
|
|
491
|
+
const editCmd = indexMatch[2];
|
|
492
|
+
// /cron edit <#> schedule every <N><unit>
|
|
493
|
+
if (editCmd.startsWith("schedule ")) {
|
|
494
|
+
const newSchedule = editCmd.slice("schedule ".length).trim();
|
|
495
|
+
const result = this.cron.update(chatId, job.id, { schedule: newSchedule });
|
|
496
|
+
if (result === null) {
|
|
497
|
+
await this.bot.sendMessage(chatId, "Invalid schedule. Use: every 30m / every 2h / every 1d");
|
|
498
|
+
}
|
|
499
|
+
else if (result === false) {
|
|
500
|
+
await this.bot.sendMessage(chatId, "Job not found.");
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
await this.bot.sendMessage(chatId, `#${index + 1} schedule updated to ${newSchedule}.`);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
// /cron edit <#> prompt <new-prompt>
|
|
508
|
+
if (editCmd.startsWith("prompt ")) {
|
|
509
|
+
const newPrompt = editCmd.slice("prompt ".length).trim();
|
|
510
|
+
const result = this.cron.update(chatId, job.id, { prompt: newPrompt });
|
|
511
|
+
if (result === false) {
|
|
512
|
+
await this.bot.sendMessage(chatId, "Job not found.");
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
await this.bot.sendMessage(chatId, `#${index + 1} prompt updated to "${newPrompt}".`);
|
|
516
|
+
}
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
// /cron edit <#> every <N><unit> <new-prompt>
|
|
520
|
+
const fullMatch = editCmd.match(/^(every\s+\d+[mhd])\s+(.+)$/i);
|
|
521
|
+
if (fullMatch) {
|
|
522
|
+
const newSchedule = fullMatch[1];
|
|
523
|
+
const newPrompt = fullMatch[2];
|
|
524
|
+
const result = this.cron.update(chatId, job.id, { schedule: newSchedule, prompt: newPrompt });
|
|
525
|
+
if (result === null) {
|
|
526
|
+
await this.bot.sendMessage(chatId, "Invalid schedule. Use: every 30m / every 2h / every 1d");
|
|
527
|
+
}
|
|
528
|
+
else if (result === false) {
|
|
529
|
+
await this.bot.sendMessage(chatId, "Job not found.");
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
await this.bot.sendMessage(chatId, `#${index + 1} updated: ${newSchedule} — "${newPrompt}"`);
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
await this.bot.sendMessage(chatId, "Edit options:\n" +
|
|
537
|
+
"/cron edit <#> every <N><unit> <new prompt>\n" +
|
|
538
|
+
"/cron edit <#> schedule every <N><unit>\n" +
|
|
539
|
+
"/cron edit <#> prompt <new prompt>");
|
|
540
|
+
}
|
|
541
|
+
/** Find cc-agent PIDs via pgrep. Returns array of numeric PIDs. */
|
|
542
|
+
findCcAgentPids() {
|
|
543
|
+
try {
|
|
544
|
+
const out = execSync("pgrep -f cc-agent", { encoding: "utf8" }).trim();
|
|
545
|
+
return out.split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n) && n > 0);
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
// pgrep exits with code 1 when no match — that's fine
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/** Kill cc-agent PIDs with SIGTERM. Returns the list of killed PIDs. */
|
|
553
|
+
killCcAgent() {
|
|
554
|
+
const pids = this.findCcAgentPids();
|
|
555
|
+
for (const pid of pids) {
|
|
556
|
+
try {
|
|
557
|
+
process.kill(pid, "SIGTERM");
|
|
558
|
+
console.log(`[mcp] sent SIGTERM to cc-agent pid=${pid}`);
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
console.warn(`[mcp] failed to kill pid=${pid}:`, err.message);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return pids;
|
|
565
|
+
}
|
|
566
|
+
async handleReloadMcp(chatId) {
|
|
567
|
+
const pids = this.killCcAgent();
|
|
568
|
+
if (pids.length === 0) {
|
|
569
|
+
await this.bot.sendMessage(chatId, "No cc-agent process found. MCP will start fresh on the next agent call.");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
await this.bot.sendMessage(chatId, `Sent SIGTERM to cc-agent (pid${pids.length > 1 ? "s" : ""}: ${pids.join(", ")}).\nMCP restarted. New process will load on next agent call.`);
|
|
573
|
+
}
|
|
574
|
+
async handleMcpVersion(chatId) {
|
|
575
|
+
let npmVersion = "unknown";
|
|
576
|
+
let cacheEntries = "(unavailable)";
|
|
577
|
+
try {
|
|
578
|
+
npmVersion = execSync("npm view @gonzih/cc-agent version", { encoding: "utf8" }).trim();
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
npmVersion = `error: ${err.message.split("\n")[0]}`;
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
const home = process.env.HOME ?? "~";
|
|
585
|
+
const cacheOut = execSync(`ls "${home}/.npm/_npx/" 2>/dev/null | head -5`, { encoding: "utf8", shell: "/bin/sh" }).trim();
|
|
586
|
+
cacheEntries = cacheOut || "(empty)";
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
cacheEntries = "(empty or not found)";
|
|
590
|
+
}
|
|
591
|
+
await this.bot.sendMessage(chatId, `cc-agent npm version: ${npmVersion}\n\nnpx cache (~/.npm/_npx/):\n${cacheEntries}`);
|
|
592
|
+
}
|
|
593
|
+
async handleClearNpxCache(chatId) {
|
|
594
|
+
try {
|
|
595
|
+
const home = process.env.HOME ?? "~";
|
|
596
|
+
execSync(`rm -rf "${home}/.npm/_npx/"`, { encoding: "utf8", shell: "/bin/sh" });
|
|
597
|
+
console.log("[mcp] cleared ~/.npm/_npx/");
|
|
598
|
+
}
|
|
599
|
+
catch (err) {
|
|
600
|
+
await this.bot.sendMessage(chatId, `Failed to clear npx cache: ${err.message}`);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const pids = this.killCcAgent();
|
|
604
|
+
const pidNote = pids.length > 0
|
|
605
|
+
? ` Sent SIGTERM to pid${pids.length > 1 ? "s" : ""}: ${pids.join(", ")}.`
|
|
606
|
+
: " No cc-agent process found (will start fresh on next call).";
|
|
607
|
+
await this.bot.sendMessage(chatId, `NPX cache cleared and MCP restarted.${pidNote} Will pick up latest npm version on next call.`);
|
|
608
|
+
}
|
|
413
609
|
killSession(chatId, keepCrons = true) {
|
|
414
610
|
const session = this.sessions.get(chatId);
|
|
415
611
|
if (session) {
|
package/dist/cron.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export declare class CronManager {
|
|
|
23
23
|
remove(chatId: number, id: string): boolean;
|
|
24
24
|
clearAll(chatId: number): number;
|
|
25
25
|
list(chatId: number): CronJob[];
|
|
26
|
+
update(chatId: number, id: string, updates: {
|
|
27
|
+
schedule?: string;
|
|
28
|
+
prompt?: string;
|
|
29
|
+
}): CronJob | null | false;
|
|
26
30
|
private persist;
|
|
27
31
|
private load;
|
|
28
32
|
}
|
package/dist/cron.js
CHANGED
|
@@ -70,6 +70,30 @@ export class CronManager {
|
|
|
70
70
|
.filter((j) => j.chatId === chatId)
|
|
71
71
|
.map(({ timer: _t, ...j }) => j);
|
|
72
72
|
}
|
|
73
|
+
update(chatId, id, updates) {
|
|
74
|
+
const job = this.jobs.get(id);
|
|
75
|
+
if (!job || job.chatId !== chatId)
|
|
76
|
+
return false;
|
|
77
|
+
if (updates.schedule !== undefined) {
|
|
78
|
+
const intervalMs = CronManager.parseSchedule(updates.schedule);
|
|
79
|
+
if (!intervalMs)
|
|
80
|
+
return null;
|
|
81
|
+
job.intervalMs = intervalMs;
|
|
82
|
+
job.schedule = updates.schedule;
|
|
83
|
+
}
|
|
84
|
+
if (updates.prompt !== undefined) {
|
|
85
|
+
job.prompt = updates.prompt;
|
|
86
|
+
}
|
|
87
|
+
// Recreate timer so it uses updated intervalMs and always reads latest job.prompt
|
|
88
|
+
clearInterval(job.timer);
|
|
89
|
+
job.timer = setInterval(() => {
|
|
90
|
+
console.log(`[cron:${job.id}] firing for chat=${job.chatId} prompt="${job.prompt}"`);
|
|
91
|
+
this.fire(job.chatId, job.prompt);
|
|
92
|
+
}, job.intervalMs);
|
|
93
|
+
this.persist();
|
|
94
|
+
const { timer: _t, ...cronJob } = job;
|
|
95
|
+
return cronJob;
|
|
96
|
+
}
|
|
73
97
|
persist() {
|
|
74
98
|
try {
|
|
75
99
|
const dir = join(this.storePath, "..");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonzih/cc-tg",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/Gonzih/cc-tg/issues"
|
|
32
32
|
},
|
|
33
|
-
"keywords": [
|
|
33
|
+
"keywords": [
|
|
34
|
+
"claude",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"telegram",
|
|
37
|
+
"bot",
|
|
38
|
+
"ai"
|
|
39
|
+
],
|
|
34
40
|
"license": "MIT"
|
|
35
41
|
}
|