@gonzih/cc-tg 0.9.51 → 0.9.54
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/README.md +4 -0
- package/dist/bot.d.ts +7 -2
- package/dist/bot.js +79 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ Open your bot in Telegram and start chatting.
|
|
|
32
32
|
| `ALLOWED_USER_IDS` | no | Comma-separated Telegram user IDs. Leave empty to allow anyone |
|
|
33
33
|
| `CWD` | no | Working directory for Claude Code. Defaults to current directory |
|
|
34
34
|
| `THREAD_CWD_MAP` | no | JSON mapping of forum topic names or IDs to CWD paths (see [Multi-topic sessions](#multi-topic-sessions)) |
|
|
35
|
+
| `CC_TG_AUTO_COMPACT_MESSAGES` | no | Automatically send `/compact` after this many messages in a session (default: 40, set to 0 to disable) |
|
|
36
|
+
| `CC_TG_COST_WARN_USD` | no | Send a cost warning notification when session cost exceeds this USD amount (default: 5.0, set to 0 to disable) |
|
|
35
37
|
|
|
36
38
|
*One of `CLAUDE_CODE_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, or `ANTHROPIC_API_KEY` required.
|
|
37
39
|
|
|
@@ -66,6 +68,8 @@ Message [@userinfobot](https://t.me/userinfobot) — it replies with your numeri
|
|
|
66
68
|
| `/mcp_version` | Show latest published cc-agent npm version and current cache |
|
|
67
69
|
| `/clear_npx_cache` | Clear npx cache and reload cc-agent (upgrades to latest version) |
|
|
68
70
|
| `/get_file <path>` | Send a file from the server to this chat |
|
|
71
|
+
| `/effort <level>` | Set Claude effort level (`low` / `medium` / `high` / `xhigh` / `max` / `auto`) |
|
|
72
|
+
| `/compact` | Compact context history to free tokens |
|
|
69
73
|
| `/restart` | Self-restart the cc-tg bot process (no SSH needed) |
|
|
70
74
|
| Any text | Sent directly to Claude Code |
|
|
71
75
|
| Voice message | Transcribed via whisper.cpp and sent to Claude |
|
package/dist/bot.d.ts
CHANGED
|
@@ -97,8 +97,6 @@ export declare class CcTgBot {
|
|
|
97
97
|
private handleGetFile;
|
|
98
98
|
private handleDrivers;
|
|
99
99
|
private handleAgents;
|
|
100
|
-
private wikiKey;
|
|
101
|
-
private wikiUpdatedKey;
|
|
102
100
|
private handleWiki;
|
|
103
101
|
private handleWikiList;
|
|
104
102
|
private handleWikiShow;
|
|
@@ -107,6 +105,13 @@ export declare class CcTgBot {
|
|
|
107
105
|
private handleWikiSync;
|
|
108
106
|
private callCcAgentTool;
|
|
109
107
|
private killSession;
|
|
108
|
+
private handleEffort;
|
|
109
|
+
private handleCompact;
|
|
110
|
+
/**
|
|
111
|
+
* If CC_TG_AUTO_COMPACT_MESSAGES threshold is reached, send /compact to the active Claude
|
|
112
|
+
* session before the next user message. Resets the per-session counter on fire.
|
|
113
|
+
*/
|
|
114
|
+
private maybeSendAutoCompact;
|
|
110
115
|
getMe(): Promise<TelegramBot.User>;
|
|
111
116
|
stop(): void;
|
|
112
117
|
}
|
package/dist/bot.js
CHANGED
|
@@ -17,7 +17,7 @@ import { getCurrentToken, rotateToken, getTokenIndex, getTokenCount } from "./to
|
|
|
17
17
|
import { writeChatLog } from "./notifier.js";
|
|
18
18
|
import { CronManager } from "./cron.js";
|
|
19
19
|
import { parseRoutingTag, ensureMetaAgent, routeToMetaAgent } from "./router.js";
|
|
20
|
-
import { VOICE_PENDING_KEY, VOICE_FAILED_KEY, TTL, metaAgentStatusKey } from "@gonzih/cc-wire";
|
|
20
|
+
import { VOICE_PENDING_KEY, VOICE_FAILED_KEY, TTL, metaAgentStatusKey, wikiKey, wikiUpdatedKey } from "@gonzih/cc-wire";
|
|
21
21
|
const BOT_COMMANDS = [
|
|
22
22
|
{ command: "start", description: "Reset session and start fresh" },
|
|
23
23
|
{ command: "reset", description: "Reset Claude session" },
|
|
@@ -37,6 +37,8 @@ const BOT_COMMANDS = [
|
|
|
37
37
|
{ command: "drivers", description: "List available agent drivers" },
|
|
38
38
|
{ command: "agents", description: "Show running meta-agents and their live status" },
|
|
39
39
|
{ command: "wiki", description: "Manage wiki pages — list/show/update/delete/sync" },
|
|
40
|
+
{ command: "effort", description: "Set effort level: low / medium / high / xhigh / max / auto" },
|
|
41
|
+
{ command: "compact", description: "Compact context history to free tokens" },
|
|
40
42
|
];
|
|
41
43
|
// Debounces streaming chunks. Resets on each chunk. Fires 800ms after last chunk.
|
|
42
44
|
const FLUSH_DELAY_MS = 800;
|
|
@@ -463,6 +465,16 @@ export class CcTgBot {
|
|
|
463
465
|
await this.handleWiki(chatId, text, threadId);
|
|
464
466
|
return;
|
|
465
467
|
}
|
|
468
|
+
// /effort <level> — forward effort level to active Claude session
|
|
469
|
+
if (text.startsWith("/effort")) {
|
|
470
|
+
await this.handleEffort(chatId, text, threadId);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
// /compact — compact context history in active Claude session
|
|
474
|
+
if (text === "/compact") {
|
|
475
|
+
await this.handleCompact(chatId, threadId);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
466
478
|
// #tag / #org/repo routing — delegate to meta-agent instead of local Claude session
|
|
467
479
|
if (this.redis) {
|
|
468
480
|
const routing = parseRoutingTag(text);
|
|
@@ -514,6 +526,7 @@ export class CcTgBot {
|
|
|
514
526
|
const enriched = await enrichPromptWithUrls(text);
|
|
515
527
|
const prompt = buildPromptWithReplyContext(enriched, msg);
|
|
516
528
|
session.currentPrompt = prompt;
|
|
529
|
+
this.maybeSendAutoCompact(session, chatId, threadId);
|
|
517
530
|
session.claude.sendPrompt(stampPrompt(prompt));
|
|
518
531
|
this.startTyping(chatId, session);
|
|
519
532
|
this.writeChatMessage("user", "telegram", text, chatId);
|
|
@@ -789,6 +802,8 @@ export class CcTgBot {
|
|
|
789
802
|
currentPrompt: "",
|
|
790
803
|
isRetry: false,
|
|
791
804
|
threadId,
|
|
805
|
+
messagesSinceCompact: 0,
|
|
806
|
+
costWarnSent: false,
|
|
792
807
|
};
|
|
793
808
|
claude.on("usage", (usage) => {
|
|
794
809
|
this.costStore.addUsage(chatId, usage);
|
|
@@ -847,6 +862,7 @@ export class CcTgBot {
|
|
|
847
862
|
return;
|
|
848
863
|
this.stopTyping(session);
|
|
849
864
|
this.costStore.incrementMessages(chatId);
|
|
865
|
+
session.messagesSinceCompact++;
|
|
850
866
|
const text = extractText(msg);
|
|
851
867
|
if (!text)
|
|
852
868
|
return;
|
|
@@ -955,6 +971,17 @@ export class CcTgBot {
|
|
|
955
971
|
catch (err) {
|
|
956
972
|
console.error(`[tg:${chatId}] uploadMentionedFiles error:`, err.message);
|
|
957
973
|
}
|
|
974
|
+
// Cost threshold warning — fires once per session when cost first exceeds the limit
|
|
975
|
+
if (!session.costWarnSent) {
|
|
976
|
+
const warnThreshold = parseFloat(process.env.CC_TG_COST_WARN_USD ?? "5.0");
|
|
977
|
+
if (!isNaN(warnThreshold) && warnThreshold > 0) {
|
|
978
|
+
const cost = this.costStore.get(chatId);
|
|
979
|
+
if (cost.totalCostUsd >= warnThreshold) {
|
|
980
|
+
session.costWarnSent = true;
|
|
981
|
+
this.replyToChat(chatId, `⚠️ Session cost: $${cost.totalCostUsd.toFixed(2)} — consider /compact or /reset`, threadId).catch(() => { });
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
958
985
|
}
|
|
959
986
|
trackWrittenFiles(msg, session, cwd) {
|
|
960
987
|
// Only look at assistant messages with tool_use blocks
|
|
@@ -1409,13 +1436,6 @@ export class CcTgBot {
|
|
|
1409
1436
|
await this.replyToChat(chatId, `Failed to get agents status: ${err.message}`, threadId);
|
|
1410
1437
|
}
|
|
1411
1438
|
}
|
|
1412
|
-
// ─── Wiki helpers ───────────────────────────────────────────────────────────
|
|
1413
|
-
wikiKey(repoSlug) {
|
|
1414
|
-
return `cca:wiki:${repoSlug}`;
|
|
1415
|
-
}
|
|
1416
|
-
wikiUpdatedKey(repoSlug) {
|
|
1417
|
-
return `cca:wiki:${repoSlug}:updated`;
|
|
1418
|
-
}
|
|
1419
1439
|
async handleWiki(chatId, text, threadId) {
|
|
1420
1440
|
const args = text.slice("/wiki".length).trim();
|
|
1421
1441
|
const parts = args.split(/\s+/);
|
|
@@ -1467,7 +1487,7 @@ export class CcTgBot {
|
|
|
1467
1487
|
return;
|
|
1468
1488
|
}
|
|
1469
1489
|
if (repoSlug) {
|
|
1470
|
-
const pages = await this.redis.hkeys(
|
|
1490
|
+
const pages = await this.redis.hkeys(wikiKey(repoSlug));
|
|
1471
1491
|
if (!pages.length) {
|
|
1472
1492
|
await this.replyToChat(chatId, `No wiki pages for "${repoSlug}".`, threadId);
|
|
1473
1493
|
return;
|
|
@@ -1500,7 +1520,7 @@ export class CcTgBot {
|
|
|
1500
1520
|
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1501
1521
|
return;
|
|
1502
1522
|
}
|
|
1503
|
-
const content = await this.redis.hget(
|
|
1523
|
+
const content = await this.redis.hget(wikiKey(repoSlug), pageName);
|
|
1504
1524
|
if (!content) {
|
|
1505
1525
|
await this.replyToChat(chatId, `Page "${pageName}" not found in "${repoSlug}".`, threadId);
|
|
1506
1526
|
return;
|
|
@@ -1520,8 +1540,8 @@ export class CcTgBot {
|
|
|
1520
1540
|
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1521
1541
|
return;
|
|
1522
1542
|
}
|
|
1523
|
-
await this.redis.hset(
|
|
1524
|
-
await this.redis.set(
|
|
1543
|
+
await this.redis.hset(wikiKey(repoSlug), pageName, content);
|
|
1544
|
+
await this.redis.set(wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1525
1545
|
await this.replyToChat(chatId, `Updated "${pageName}" in "${repoSlug}".`, threadId);
|
|
1526
1546
|
}
|
|
1527
1547
|
async handleWikiDelete(chatId, repoSlug, pageName, threadId) {
|
|
@@ -1529,9 +1549,9 @@ export class CcTgBot {
|
|
|
1529
1549
|
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1530
1550
|
return;
|
|
1531
1551
|
}
|
|
1532
|
-
const deleted = await this.redis.hdel(
|
|
1552
|
+
const deleted = await this.redis.hdel(wikiKey(repoSlug), pageName);
|
|
1533
1553
|
if (deleted) {
|
|
1534
|
-
await this.redis.set(
|
|
1554
|
+
await this.redis.set(wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1535
1555
|
await this.replyToChat(chatId, `Deleted "${pageName}" from "${repoSlug}".`, threadId);
|
|
1536
1556
|
}
|
|
1537
1557
|
else {
|
|
@@ -1567,7 +1587,7 @@ export class CcTgBot {
|
|
|
1567
1587
|
const pageName = file.replace(/\.md$/, "");
|
|
1568
1588
|
try {
|
|
1569
1589
|
const content = readFileSync(join(wikiDir, file), "utf8");
|
|
1570
|
-
await this.redis.hset(
|
|
1590
|
+
await this.redis.hset(wikiKey(repoSlug), pageName, content);
|
|
1571
1591
|
synced++;
|
|
1572
1592
|
}
|
|
1573
1593
|
catch (err) {
|
|
@@ -1575,7 +1595,7 @@ export class CcTgBot {
|
|
|
1575
1595
|
}
|
|
1576
1596
|
}
|
|
1577
1597
|
if (synced > 0) {
|
|
1578
|
-
await this.redis.set(
|
|
1598
|
+
await this.redis.set(wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1579
1599
|
}
|
|
1580
1600
|
let reply = `Synced ${synced}/${files.length} pages to cca:wiki:${repoSlug}`;
|
|
1581
1601
|
if (errors.length) {
|
|
@@ -1671,6 +1691,49 @@ export class CcTgBot {
|
|
|
1671
1691
|
this.sessions.delete(key);
|
|
1672
1692
|
}
|
|
1673
1693
|
}
|
|
1694
|
+
async handleEffort(chatId, text, threadId) {
|
|
1695
|
+
const VALID_LEVELS = new Set(["low", "medium", "high", "xhigh", "max", "auto"]);
|
|
1696
|
+
const parts = text.trim().split(/\s+/);
|
|
1697
|
+
const level = parts[1]?.toLowerCase();
|
|
1698
|
+
if (!level || !VALID_LEVELS.has(level)) {
|
|
1699
|
+
await this.replyToChat(chatId, "Usage: /effort <level>\nValid levels: low, medium, high, xhigh, max, auto", threadId);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const sessionKey = this.sessionKey(chatId, threadId);
|
|
1703
|
+
const session = this.sessions.get(sessionKey);
|
|
1704
|
+
if (!session || session.claude.exited) {
|
|
1705
|
+
await this.replyToChat(chatId, `No active session. Start one, then use /effort ${level} to set the effort level.`, threadId);
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
session.claude.sendPrompt(`/effort ${level}`);
|
|
1709
|
+
await this.replyToChat(chatId, `⚡ Effort set to: ${level}`, threadId);
|
|
1710
|
+
}
|
|
1711
|
+
async handleCompact(chatId, threadId) {
|
|
1712
|
+
const sessionKey = this.sessionKey(chatId, threadId);
|
|
1713
|
+
const session = this.sessions.get(sessionKey);
|
|
1714
|
+
if (!session || session.claude.exited) {
|
|
1715
|
+
await this.replyToChat(chatId, "No active session.", threadId);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
session.claude.sendPrompt("/compact");
|
|
1719
|
+
session.messagesSinceCompact = 0;
|
|
1720
|
+
await this.replyToChat(chatId, "🗜️ Compacting context history...", threadId);
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* If CC_TG_AUTO_COMPACT_MESSAGES threshold is reached, send /compact to the active Claude
|
|
1724
|
+
* session before the next user message. Resets the per-session counter on fire.
|
|
1725
|
+
*/
|
|
1726
|
+
maybeSendAutoCompact(session, chatId, threadId) {
|
|
1727
|
+
const threshold = parseInt(process.env.CC_TG_AUTO_COMPACT_MESSAGES ?? "40", 10);
|
|
1728
|
+
if (isNaN(threshold) || threshold <= 0)
|
|
1729
|
+
return;
|
|
1730
|
+
if (session.messagesSinceCompact >= threshold) {
|
|
1731
|
+
console.log(`[auto-compact:${chatId}] firing after ${session.messagesSinceCompact} messages`);
|
|
1732
|
+
session.messagesSinceCompact = 0;
|
|
1733
|
+
session.claude.sendPrompt("/compact");
|
|
1734
|
+
this.replyToChat(chatId, `🗜️ Auto-compacting context (${threshold} message limit reached)...`, threadId).catch(() => { });
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1674
1737
|
getMe() {
|
|
1675
1738
|
return this.bot.getMe();
|
|
1676
1739
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonzih/cc-tg",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.54",
|
|
4
4
|
"description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@gonzih/agent-ops": "^0.1.0",
|
|
22
|
-
"@gonzih/cc-wire": "^0.1.
|
|
22
|
+
"@gonzih/cc-wire": "^0.1.4",
|
|
23
23
|
"node-telegram-bot-api": "^0.66.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|