ctb 1.0.0 → 1.2.1

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/src/bookmarks.ts CHANGED
@@ -99,8 +99,12 @@ export function isBookmarked(path: string): boolean {
99
99
 
100
100
  /**
101
101
  * Resolve path with ~ expansion.
102
+ * If baseDir is provided, relative paths are resolved from there.
102
103
  */
103
- export function resolvePath(path: string): string {
104
+ export function resolvePath(path: string, baseDir?: string): string {
104
105
  const expanded = path.replace(/^~/, homedir());
106
+ if (baseDir && !expanded.startsWith("/")) {
107
+ return resolve(baseDir, expanded);
108
+ }
105
109
  return resolve(expanded);
106
110
  }
package/src/bot.ts CHANGED
@@ -18,16 +18,24 @@ import {
18
18
  handleBookmarks,
19
19
  handleCallback,
20
20
  handleCd,
21
+ handleCompact,
22
+ handleCost,
21
23
  handleDocument,
24
+ handleFile,
25
+ handleModel,
22
26
  handleNew,
23
27
  handlePhoto,
28
+ handlePlan,
24
29
  handleRestart,
25
30
  handleResume,
26
31
  handleRetry,
32
+ handleSkill,
27
33
  handleStart,
28
34
  handleStatus,
29
35
  handleStop,
30
36
  handleText,
37
+ handleThink,
38
+ handleUndo,
31
39
  handleVoice,
32
40
  } from "./handlers";
33
41
 
@@ -60,11 +68,22 @@ bot.use(
60
68
  bot.command("start", handleStart);
61
69
  bot.command("new", handleNew);
62
70
  bot.command("stop", handleStop);
71
+ bot.command("c", handleStop);
72
+ bot.command("kill", handleStop);
73
+ bot.command("dc", handleStop);
63
74
  bot.command("status", handleStatus);
64
75
  bot.command("resume", handleResume);
65
76
  bot.command("restart", handleRestart);
66
77
  bot.command("retry", handleRetry);
67
78
  bot.command("cd", handleCd);
79
+ bot.command("skill", handleSkill);
80
+ bot.command("file", handleFile);
81
+ bot.command("model", handleModel);
82
+ bot.command("cost", handleCost);
83
+ bot.command("think", handleThink);
84
+ bot.command("plan", handlePlan);
85
+ bot.command("compact", handleCompact);
86
+ bot.command("undo", handleUndo);
68
87
  bot.command("bookmarks", handleBookmarks);
69
88
 
70
89
  // ============== Message Handlers ==============
@@ -104,6 +123,28 @@ console.log("Starting bot...");
104
123
  const botInfo = await bot.api.getMe();
105
124
  console.log(`Bot started: @${botInfo.username}`);
106
125
 
126
+ // Set up Telegram menu commands
127
+ await bot.api.setMyCommands([
128
+ { command: "start", description: "Show status and user ID" },
129
+ { command: "new", description: "Start a fresh session" },
130
+ { command: "resume", description: "Resume last session" },
131
+ { command: "stop", description: "Interrupt current query" },
132
+ { command: "status", description: "Check what Claude is doing" },
133
+ { command: "model", description: "Switch model (sonnet/opus/haiku)" },
134
+ { command: "cost", description: "Show token usage and cost" },
135
+ { command: "think", description: "Force extended thinking" },
136
+ { command: "plan", description: "Toggle planning mode" },
137
+ { command: "compact", description: "Trigger context compaction" },
138
+ { command: "undo", description: "Revert file changes" },
139
+ { command: "cd", description: "Change working directory" },
140
+ { command: "skill", description: "Invoke a Claude Code skill" },
141
+ { command: "file", description: "Download a file" },
142
+ { command: "bookmarks", description: "Manage directory bookmarks" },
143
+ { command: "retry", description: "Retry last message" },
144
+ { command: "restart", description: "Restart the bot" },
145
+ ]);
146
+ console.log("Menu commands registered");
147
+
107
148
  // Check for pending restart message to update
108
149
  if (existsSync(RESTART_FILE)) {
109
150
  try {
package/src/cli.ts CHANGED
@@ -23,6 +23,7 @@ interface CliOptions {
23
23
  dir?: string;
24
24
  help?: boolean;
25
25
  version?: boolean;
26
+ tut?: boolean;
26
27
  }
27
28
 
28
29
  function parseArgs(args: string[]): CliOptions {
@@ -33,6 +34,8 @@ function parseArgs(args: string[]): CliOptions {
33
34
  options.help = true;
34
35
  } else if (arg === "--version" || arg === "-v") {
35
36
  options.version = true;
37
+ } else if (arg === "tut" || arg === "tutorial") {
38
+ options.tut = true;
36
39
  } else if (arg.startsWith("--token=")) {
37
40
  options.token = arg.slice(8);
38
41
  } else if (arg.startsWith("--users=")) {
@@ -53,6 +56,7 @@ Run a Telegram bot that controls Claude Code in your project directory.
53
56
 
54
57
  USAGE:
55
58
  ctb [options]
59
+ ctb tut Show setup tutorial
56
60
 
57
61
  OPTIONS:
58
62
  --help, -h Show this help message
@@ -75,6 +79,91 @@ Multiple instances can run simultaneously in different directories.
75
79
  `);
76
80
  }
77
81
 
82
+ function showTutorial(): void {
83
+ console.log(`
84
+ ╔══════════════════════════════════════════════════════════════════╗
85
+ ║ CTB Setup Tutorial ║
86
+ ╚══════════════════════════════════════════════════════════════════╝
87
+
88
+ Follow these steps to set up your Claude Telegram Bot:
89
+
90
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
91
+ STEP 1: Create a Telegram Bot
92
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
93
+
94
+ 1. Open Telegram and search for @BotFather
95
+ 2. Send /newbot
96
+ 3. Follow the prompts:
97
+ - Choose a name (e.g., "My Claude Bot")
98
+ - Choose a username (must end in "bot", e.g., "my_claude_bot")
99
+ 4. Copy the token that looks like:
100
+ 1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
101
+
102
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
103
+ STEP 2: Get Your Telegram User ID
104
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
105
+
106
+ 1. Open Telegram and search for @userinfobot
107
+ 2. Send any message to it
108
+ 3. It will reply with your user ID (a number like 123456789)
109
+ 4. Copy this number
110
+
111
+ Tip: Add multiple user IDs separated by commas for team access
112
+
113
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
114
+ STEP 3: Configure the Bot
115
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
116
+
117
+ Option A: Interactive setup (easiest)
118
+ Just run: ctb
119
+ It will prompt you for the token and user IDs.
120
+
121
+ Option B: Create a .env file
122
+ Create a file named .env in your project directory:
123
+
124
+ TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
125
+ TELEGRAM_ALLOWED_USERS=123456789,987654321
126
+
127
+ Option C: Use command-line arguments
128
+ ctb --token=YOUR_TOKEN --users=YOUR_USER_ID
129
+
130
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
131
+ STEP 4: Set Up Bot Commands (Optional)
132
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
133
+
134
+ 1. Go back to @BotFather
135
+ 2. Send /setcommands
136
+ 3. Select your bot
137
+ 4. Paste this command list:
138
+
139
+ start - Show status and user ID
140
+ new - Start a fresh session
141
+ resume - Resume last session
142
+ stop - Interrupt current query
143
+ status - Check what Claude is doing
144
+ undo - Revert file changes
145
+ cd - Change working directory
146
+ file - Download a file
147
+ bookmarks - Manage directory bookmarks
148
+ retry - Retry last message
149
+ restart - Restart the bot
150
+
151
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
152
+ STEP 5: Start the Bot
153
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
154
+
155
+ cd ~/your-project
156
+ ctb
157
+
158
+ The bot will start and show "Bot started: @your_bot_username"
159
+ Open Telegram and message your bot to start using Claude!
160
+
161
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
162
+ Need help? https://github.com/htlin/claude-telegram-bot
163
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
164
+ `);
165
+ }
166
+
78
167
  async function prompt(question: string): Promise<string> {
79
168
  const rl = createInterface({
80
169
  input: process.stdin,
@@ -226,6 +315,11 @@ async function main(): Promise<void> {
226
315
  process.exit(0);
227
316
  }
228
317
 
318
+ if (options.tut) {
319
+ showTutorial();
320
+ process.exit(0);
321
+ }
322
+
229
323
  // Determine working directory
230
324
  const workingDir = options.dir ? resolve(options.dir) : process.cwd();
231
325