@gonzih/cc-tg 0.9.49 → 0.9.51
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 +10 -0
- package/dist/bot.js +190 -0
- package/package.json +1 -1
package/dist/bot.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export declare class CcTgBot {
|
|
|
32
32
|
private cron;
|
|
33
33
|
/** In-memory cache of forum topic names: `${chatId}:${threadId}` → topic name */
|
|
34
34
|
private topicNameCache;
|
|
35
|
+
/** Pending /wiki update state: chatId → {repoSlug, pageName, threadId} — awaiting user's next message as content */
|
|
36
|
+
private pendingWikiUpdates;
|
|
35
37
|
constructor(opts: BotOptions);
|
|
36
38
|
private registerBotCommands;
|
|
37
39
|
/** Write a message to the Redis chat log. Fire-and-forget — no-op if Redis is not configured. */
|
|
@@ -95,6 +97,14 @@ export declare class CcTgBot {
|
|
|
95
97
|
private handleGetFile;
|
|
96
98
|
private handleDrivers;
|
|
97
99
|
private handleAgents;
|
|
100
|
+
private wikiKey;
|
|
101
|
+
private wikiUpdatedKey;
|
|
102
|
+
private handleWiki;
|
|
103
|
+
private handleWikiList;
|
|
104
|
+
private handleWikiShow;
|
|
105
|
+
private handleWikiUpdateContent;
|
|
106
|
+
private handleWikiDelete;
|
|
107
|
+
private handleWikiSync;
|
|
98
108
|
private callCcAgentTool;
|
|
99
109
|
private killSession;
|
|
100
110
|
getMe(): Promise<TelegramBot.User>;
|
package/dist/bot.js
CHANGED
|
@@ -36,6 +36,7 @@ const BOT_COMMANDS = [
|
|
|
36
36
|
{ command: "voice_retry", description: "Retry failed voice message transcriptions" },
|
|
37
37
|
{ command: "drivers", description: "List available agent drivers" },
|
|
38
38
|
{ command: "agents", description: "Show running meta-agents and their live status" },
|
|
39
|
+
{ command: "wiki", description: "Manage wiki pages — list/show/update/delete/sync" },
|
|
39
40
|
];
|
|
40
41
|
// Debounces streaming chunks. Resets on each chunk. Fires 800ms after last chunk.
|
|
41
42
|
const FLUSH_DELAY_MS = 800;
|
|
@@ -179,6 +180,8 @@ export class CcTgBot {
|
|
|
179
180
|
cron;
|
|
180
181
|
/** In-memory cache of forum topic names: `${chatId}:${threadId}` → topic name */
|
|
181
182
|
topicNameCache = new Map();
|
|
183
|
+
/** Pending /wiki update state: chatId → {repoSlug, pageName, threadId} — awaiting user's next message as content */
|
|
184
|
+
pendingWikiUpdates = new Map();
|
|
182
185
|
constructor(opts) {
|
|
183
186
|
this.opts = opts;
|
|
184
187
|
this.redis = opts.redis;
|
|
@@ -348,6 +351,13 @@ export class CcTgBot {
|
|
|
348
351
|
text = text.replace(new RegExp(`@${this.botUsername}\\s*`, "g"), "").trim();
|
|
349
352
|
}
|
|
350
353
|
const sessionKey = this.sessionKey(chatId, threadId);
|
|
354
|
+
// Pending /wiki update — next message is the new page content
|
|
355
|
+
const pendingWiki = this.pendingWikiUpdates.get(chatId);
|
|
356
|
+
if (pendingWiki && !text.startsWith("/")) {
|
|
357
|
+
this.pendingWikiUpdates.delete(chatId);
|
|
358
|
+
await this.handleWikiUpdateContent(chatId, pendingWiki.repoSlug, pendingWiki.pageName, text, pendingWiki.threadId);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
351
361
|
// /start or /reset — kill existing session and ack
|
|
352
362
|
if (text === "/start" || text === "/reset") {
|
|
353
363
|
this.killSession(chatId, true, threadId);
|
|
@@ -448,6 +458,11 @@ export class CcTgBot {
|
|
|
448
458
|
await this.handleAgents(chatId, threadId);
|
|
449
459
|
return;
|
|
450
460
|
}
|
|
461
|
+
// /wiki <subcommand> — manage wiki pages in Redis
|
|
462
|
+
if (text.startsWith("/wiki")) {
|
|
463
|
+
await this.handleWiki(chatId, text, threadId);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
451
466
|
// #tag / #org/repo routing — delegate to meta-agent instead of local Claude session
|
|
452
467
|
if (this.redis) {
|
|
453
468
|
const routing = parseRoutingTag(text);
|
|
@@ -1394,6 +1409,181 @@ export class CcTgBot {
|
|
|
1394
1409
|
await this.replyToChat(chatId, `Failed to get agents status: ${err.message}`, threadId);
|
|
1395
1410
|
}
|
|
1396
1411
|
}
|
|
1412
|
+
// ─── Wiki helpers ───────────────────────────────────────────────────────────
|
|
1413
|
+
wikiKey(repoSlug) {
|
|
1414
|
+
return `cca:wiki:${repoSlug}`;
|
|
1415
|
+
}
|
|
1416
|
+
wikiUpdatedKey(repoSlug) {
|
|
1417
|
+
return `cca:wiki:${repoSlug}:updated`;
|
|
1418
|
+
}
|
|
1419
|
+
async handleWiki(chatId, text, threadId) {
|
|
1420
|
+
const args = text.slice("/wiki".length).trim();
|
|
1421
|
+
const parts = args.split(/\s+/);
|
|
1422
|
+
const subCmd = parts[0] ?? "";
|
|
1423
|
+
if (subCmd === "list" || subCmd === "") {
|
|
1424
|
+
await this.handleWikiList(chatId, parts[1], threadId);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
if (subCmd === "show") {
|
|
1428
|
+
const repoSlug = parts[1];
|
|
1429
|
+
const pageName = parts.slice(2).join(" ");
|
|
1430
|
+
if (!repoSlug || !pageName) {
|
|
1431
|
+
await this.replyToChat(chatId, "Usage: /wiki show <repo_slug> <page_name>", threadId);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
await this.handleWikiShow(chatId, repoSlug, pageName, threadId);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (subCmd === "update") {
|
|
1438
|
+
const repoSlug = parts[1];
|
|
1439
|
+
const pageName = parts.slice(2).join(" ");
|
|
1440
|
+
if (!repoSlug || !pageName) {
|
|
1441
|
+
await this.replyToChat(chatId, "Usage: /wiki update <repo_slug> <page_name>", threadId);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
this.pendingWikiUpdates.set(chatId, { repoSlug, pageName, threadId });
|
|
1445
|
+
await this.replyToChat(chatId, `Send the new content for page "${pageName}" in repo "${repoSlug}":`, threadId);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (subCmd === "delete") {
|
|
1449
|
+
const repoSlug = parts[1];
|
|
1450
|
+
const pageName = parts.slice(2).join(" ");
|
|
1451
|
+
if (!repoSlug || !pageName) {
|
|
1452
|
+
await this.replyToChat(chatId, "Usage: /wiki delete <repo_slug> <page_name>", threadId);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
await this.handleWikiDelete(chatId, repoSlug, pageName, threadId);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
if (subCmd === "sync") {
|
|
1459
|
+
await this.handleWikiSync(chatId, threadId);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
await this.replyToChat(chatId, "Usage:\n/wiki list [repo_slug]\n/wiki show <repo_slug> <page_name>\n/wiki update <repo_slug> <page_name>\n/wiki delete <repo_slug> <page_name>\n/wiki sync", threadId);
|
|
1463
|
+
}
|
|
1464
|
+
async handleWikiList(chatId, repoSlug, threadId) {
|
|
1465
|
+
if (!this.redis) {
|
|
1466
|
+
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (repoSlug) {
|
|
1470
|
+
const pages = await this.redis.hkeys(this.wikiKey(repoSlug));
|
|
1471
|
+
if (!pages.length) {
|
|
1472
|
+
await this.replyToChat(chatId, `No wiki pages for "${repoSlug}".`, threadId);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
const lines = pages.sort().map((p, i) => `${i + 1}. ${p}`);
|
|
1476
|
+
await this.replyToChat(chatId, `Wiki pages for ${repoSlug} (${pages.length}):\n${lines.join("\n")}`, threadId);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
// List all repos — scan for cca:wiki:* keys, exclude :updated keys
|
|
1480
|
+
const keys = [];
|
|
1481
|
+
let cursor = "0";
|
|
1482
|
+
do {
|
|
1483
|
+
const [nextCursor, found] = await this.redis.scan(cursor, "MATCH", "cca:wiki:*", "COUNT", 100);
|
|
1484
|
+
cursor = nextCursor;
|
|
1485
|
+
keys.push(...found.filter((k) => !k.endsWith(":updated")));
|
|
1486
|
+
} while (cursor !== "0");
|
|
1487
|
+
if (!keys.length) {
|
|
1488
|
+
await this.replyToChat(chatId, "No wiki repos found.", threadId);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const counts = await Promise.all(keys.sort().map(async (key) => {
|
|
1492
|
+
const slug = key.slice("cca:wiki:".length);
|
|
1493
|
+
const count = await this.redis.hlen(key);
|
|
1494
|
+
return `${slug} (${count} pages)`;
|
|
1495
|
+
}));
|
|
1496
|
+
await this.replyToChat(chatId, `Wiki repos:\n${counts.join("\n")}`, threadId);
|
|
1497
|
+
}
|
|
1498
|
+
async handleWikiShow(chatId, repoSlug, pageName, threadId) {
|
|
1499
|
+
if (!this.redis) {
|
|
1500
|
+
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
const content = await this.redis.hget(this.wikiKey(repoSlug), pageName);
|
|
1504
|
+
if (!content) {
|
|
1505
|
+
await this.replyToChat(chatId, `Page "${pageName}" not found in "${repoSlug}".`, threadId);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const TG_LIMIT = 4000;
|
|
1509
|
+
const header = `📄 ${repoSlug}/${pageName}\n\n`;
|
|
1510
|
+
const fullText = header + content;
|
|
1511
|
+
if (fullText.length <= TG_LIMIT) {
|
|
1512
|
+
await this.replyToChat(chatId, fullText, threadId);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
const truncated = fullText.slice(0, TG_LIMIT - 20) + "\n...(truncated)";
|
|
1516
|
+
await this.replyToChat(chatId, truncated, threadId);
|
|
1517
|
+
}
|
|
1518
|
+
async handleWikiUpdateContent(chatId, repoSlug, pageName, content, threadId) {
|
|
1519
|
+
if (!this.redis) {
|
|
1520
|
+
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
await this.redis.hset(this.wikiKey(repoSlug), pageName, content);
|
|
1524
|
+
await this.redis.set(this.wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1525
|
+
await this.replyToChat(chatId, `Updated "${pageName}" in "${repoSlug}".`, threadId);
|
|
1526
|
+
}
|
|
1527
|
+
async handleWikiDelete(chatId, repoSlug, pageName, threadId) {
|
|
1528
|
+
if (!this.redis) {
|
|
1529
|
+
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const deleted = await this.redis.hdel(this.wikiKey(repoSlug), pageName);
|
|
1533
|
+
if (deleted) {
|
|
1534
|
+
await this.redis.set(this.wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1535
|
+
await this.replyToChat(chatId, `Deleted "${pageName}" from "${repoSlug}".`, threadId);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
await this.replyToChat(chatId, `Page "${pageName}" not found in "${repoSlug}".`, threadId);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
async handleWikiSync(chatId, threadId) {
|
|
1542
|
+
if (!this.redis) {
|
|
1543
|
+
await this.replyToChat(chatId, "Redis not configured — wiki unavailable.", threadId);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
const wikiDir = "/Users/feral/money-brain/wiki";
|
|
1547
|
+
if (!existsSync(wikiDir)) {
|
|
1548
|
+
await this.replyToChat(chatId, `Wiki directory not found: ${wikiDir}`, threadId);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
let files;
|
|
1552
|
+
try {
|
|
1553
|
+
files = readdirSync(wikiDir).filter((f) => f.endsWith(".md"));
|
|
1554
|
+
}
|
|
1555
|
+
catch (err) {
|
|
1556
|
+
await this.replyToChat(chatId, `Failed to read wiki directory: ${err.message}`, threadId);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
if (!files.length) {
|
|
1560
|
+
await this.replyToChat(chatId, "No .md files found in wiki directory.", threadId);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const repoSlug = "gonzih-money-brain";
|
|
1564
|
+
let synced = 0;
|
|
1565
|
+
const errors = [];
|
|
1566
|
+
for (const file of files) {
|
|
1567
|
+
const pageName = file.replace(/\.md$/, "");
|
|
1568
|
+
try {
|
|
1569
|
+
const content = readFileSync(join(wikiDir, file), "utf8");
|
|
1570
|
+
await this.redis.hset(this.wikiKey(repoSlug), pageName, content);
|
|
1571
|
+
synced++;
|
|
1572
|
+
}
|
|
1573
|
+
catch (err) {
|
|
1574
|
+
errors.push(`${file}: ${err.message}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
if (synced > 0) {
|
|
1578
|
+
await this.redis.set(this.wikiUpdatedKey(repoSlug), new Date().toISOString());
|
|
1579
|
+
}
|
|
1580
|
+
let reply = `Synced ${synced}/${files.length} pages to cca:wiki:${repoSlug}`;
|
|
1581
|
+
if (errors.length) {
|
|
1582
|
+
reply += `\nErrors:\n${errors.join("\n")}`;
|
|
1583
|
+
}
|
|
1584
|
+
await this.replyToChat(chatId, reply, threadId);
|
|
1585
|
+
}
|
|
1586
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
1397
1587
|
callCcAgentTool(toolName, args = {}) {
|
|
1398
1588
|
// For spawn tools, pass through the configured driver and model
|
|
1399
1589
|
const spawnTools = new Set(["spawn_agent", "spawn_from_profile"]);
|