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/README.md +42 -11
- package/package.json +4 -2
- package/src/__tests__/callback.test.ts +286 -0
- package/src/__tests__/cli.test.ts +377 -0
- package/src/__tests__/file-detection.test.ts +311 -0
- package/src/__tests__/session.test.ts +399 -0
- package/src/__tests__/shell-command.test.ts +310 -0
- package/src/bookmarks.ts +5 -1
- package/src/bot.ts +41 -0
- package/src/cli.ts +94 -0
- package/src/formatting.ts +289 -237
- package/src/handlers/callback.ts +46 -1
- package/src/handlers/commands.ts +417 -3
- package/src/handlers/index.ts +8 -0
- package/src/handlers/streaming.ts +185 -185
- package/src/handlers/text.ts +191 -113
- package/src/index.ts +19 -0
- package/src/session.ts +140 -6
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
|
|