@gonzih/cc-tg 0.2.5 → 0.2.6

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 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,7 @@ export declare class CcTgBot {
29
30
  private uploadMentionedFiles;
30
31
  private extractToolName;
31
32
  private handleCron;
33
+ private handleCronEdit;
32
34
  private killSession;
33
35
  stop(): void;
34
36
  }
package/dist/bot.js CHANGED
@@ -10,6 +10,14 @@ import http from "http";
10
10
  import { ClaudeProcess, extractText } from "./claude.js";
11
11
  import { transcribeVoice, isVoiceAvailable } from "./voice.js";
12
12
  import { CronManager } from "./cron.js";
13
+ const BOT_COMMANDS = [
14
+ { command: "start", description: "Reset session and start fresh" },
15
+ { command: "reset", description: "Reset Claude session" },
16
+ { command: "stop", description: "Stop the current Claude task" },
17
+ { command: "status", description: "Check if a session is active" },
18
+ { command: "help", description: "Show all available commands" },
19
+ { command: "cron", description: "Manage cron jobs — add/list/edit/remove/clear" },
20
+ ];
13
21
  const FLUSH_DELAY_MS = 800; // debounce streaming chunks into one Telegram message
14
22
  const TYPING_INTERVAL_MS = 4000; // re-send typing action before Telegram's 5s expiry
15
23
  export class CcTgBot {
@@ -35,9 +43,15 @@ export class CcTgBot {
35
43
  console.error(`[cron] failed to fire for chat=${chatId}:`, err.message);
36
44
  }
37
45
  });
46
+ this.registerBotCommands();
38
47
  console.log("cc-tg bot started");
39
48
  console.log(`[voice] whisper available: ${isVoiceAvailable()}`);
40
49
  }
50
+ registerBotCommands() {
51
+ this.bot.setMyCommands(BOT_COMMANDS)
52
+ .then(() => console.log("[tg] bot commands registered"))
53
+ .catch((err) => console.error("[tg] setMyCommands failed:", err.message));
54
+ }
41
55
  isAllowed(userId) {
42
56
  if (!this.opts.allowedUserIds?.length)
43
57
  return true;
@@ -81,6 +95,12 @@ export class CcTgBot {
81
95
  await this.bot.sendMessage(chatId, has ? "Stopped." : "No active session.");
82
96
  return;
83
97
  }
98
+ // /help — list all commands
99
+ if (text === "/help") {
100
+ const lines = BOT_COMMANDS.map((c) => `/${c.command} — ${c.description}`);
101
+ await this.bot.sendMessage(chatId, lines.join("\n"));
102
+ return;
103
+ }
84
104
  // /status
85
105
  if (text === "/status") {
86
106
  const has = this.sessions.has(chatId);
@@ -378,8 +398,11 @@ export class CcTgBot {
378
398
  await this.bot.sendMessage(chatId, "No cron jobs.");
379
399
  return;
380
400
  }
381
- const lines = jobs.map((j) => `[${j.id}] ${j.schedule}: ${j.prompt}`);
382
- await this.bot.sendMessage(chatId, lines.join("\n"));
401
+ const lines = jobs.map((j, i) => {
402
+ const short = j.prompt.length > 50 ? j.prompt.slice(0, 50) + "" : j.prompt;
403
+ return `#${i + 1} ${j.schedule} — "${short}"`;
404
+ });
405
+ await this.bot.sendMessage(chatId, `Cron jobs (${jobs.length}):\n${lines.join("\n")}`);
383
406
  return;
384
407
  }
385
408
  // /cron clear
@@ -395,10 +418,15 @@ export class CcTgBot {
395
418
  await this.bot.sendMessage(chatId, ok ? `Removed ${id}.` : `Not found: ${id}`);
396
419
  return;
397
420
  }
421
+ // /cron edit [<#> ...]
422
+ if (args === "edit" || args.startsWith("edit ")) {
423
+ await this.handleCronEdit(chatId, args.slice("edit".length).trim());
424
+ return;
425
+ }
398
426
  // /cron every 1h <prompt>
399
427
  const scheduleMatch = args.match(/^(every\s+\d+[mhd])\s+(.+)$/i);
400
428
  if (!scheduleMatch) {
401
- await this.bot.sendMessage(chatId, "Usage:\n/cron every 1h <prompt>\n/cron list\n/cron remove <id>\n/cron clear");
429
+ 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
430
  return;
403
431
  }
404
432
  const schedule = scheduleMatch[1];
@@ -410,6 +438,87 @@ export class CcTgBot {
410
438
  }
411
439
  await this.bot.sendMessage(chatId, `Cron set [${job.id}]: ${schedule} — "${prompt}"`);
412
440
  }
441
+ async handleCronEdit(chatId, editArgs) {
442
+ const jobs = this.cron.list(chatId);
443
+ // No args — show numbered list with edit instructions
444
+ if (!editArgs) {
445
+ if (!jobs.length) {
446
+ await this.bot.sendMessage(chatId, "No cron jobs to edit.");
447
+ return;
448
+ }
449
+ const lines = jobs.map((j, i) => {
450
+ const short = j.prompt.length > 50 ? j.prompt.slice(0, 50) + "…" : j.prompt;
451
+ return `#${i + 1} ${j.schedule} — "${short}"`;
452
+ });
453
+ await this.bot.sendMessage(chatId, `Cron jobs:\n${lines.join("\n")}\n\n` +
454
+ "Edit options:\n" +
455
+ "/cron edit <#> every <N><unit> <new prompt>\n" +
456
+ "/cron edit <#> schedule every <N><unit>\n" +
457
+ "/cron edit <#> prompt <new prompt>");
458
+ return;
459
+ }
460
+ // Expect: <index> <rest>
461
+ const indexMatch = editArgs.match(/^(\d+)\s+(.+)$/);
462
+ if (!indexMatch) {
463
+ await this.bot.sendMessage(chatId, "Usage: /cron edit <#> every <N><unit> <new prompt>");
464
+ return;
465
+ }
466
+ const index = parseInt(indexMatch[1], 10) - 1;
467
+ if (index < 0 || index >= jobs.length) {
468
+ await this.bot.sendMessage(chatId, `Invalid job number. Use /cron edit to see the list.`);
469
+ return;
470
+ }
471
+ const job = jobs[index];
472
+ const editCmd = indexMatch[2];
473
+ // /cron edit <#> schedule every <N><unit>
474
+ if (editCmd.startsWith("schedule ")) {
475
+ const newSchedule = editCmd.slice("schedule ".length).trim();
476
+ const result = this.cron.update(chatId, job.id, { schedule: newSchedule });
477
+ if (result === null) {
478
+ await this.bot.sendMessage(chatId, "Invalid schedule. Use: every 30m / every 2h / every 1d");
479
+ }
480
+ else if (result === false) {
481
+ await this.bot.sendMessage(chatId, "Job not found.");
482
+ }
483
+ else {
484
+ await this.bot.sendMessage(chatId, `#${index + 1} schedule updated to ${newSchedule}.`);
485
+ }
486
+ return;
487
+ }
488
+ // /cron edit <#> prompt <new-prompt>
489
+ if (editCmd.startsWith("prompt ")) {
490
+ const newPrompt = editCmd.slice("prompt ".length).trim();
491
+ const result = this.cron.update(chatId, job.id, { prompt: newPrompt });
492
+ if (result === false) {
493
+ await this.bot.sendMessage(chatId, "Job not found.");
494
+ }
495
+ else {
496
+ await this.bot.sendMessage(chatId, `#${index + 1} prompt updated to "${newPrompt}".`);
497
+ }
498
+ return;
499
+ }
500
+ // /cron edit <#> every <N><unit> <new-prompt>
501
+ const fullMatch = editCmd.match(/^(every\s+\d+[mhd])\s+(.+)$/i);
502
+ if (fullMatch) {
503
+ const newSchedule = fullMatch[1];
504
+ const newPrompt = fullMatch[2];
505
+ const result = this.cron.update(chatId, job.id, { schedule: newSchedule, prompt: newPrompt });
506
+ if (result === null) {
507
+ await this.bot.sendMessage(chatId, "Invalid schedule. Use: every 30m / every 2h / every 1d");
508
+ }
509
+ else if (result === false) {
510
+ await this.bot.sendMessage(chatId, "Job not found.");
511
+ }
512
+ else {
513
+ await this.bot.sendMessage(chatId, `#${index + 1} updated: ${newSchedule} — "${newPrompt}"`);
514
+ }
515
+ return;
516
+ }
517
+ await this.bot.sendMessage(chatId, "Edit options:\n" +
518
+ "/cron edit <#> every <N><unit> <new prompt>\n" +
519
+ "/cron edit <#> schedule every <N><unit>\n" +
520
+ "/cron edit <#> prompt <new prompt>");
521
+ }
413
522
  killSession(chatId, keepCrons = true) {
414
523
  const session = this.sessions.get(chatId);
415
524
  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.5",
3
+ "version": "0.2.6",
4
4
  "description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
5
5
  "type": "module",
6
6
  "bin": {