claude-telegram-mirror 0.1.0

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.
Files changed (99) hide show
  1. package/README.md +331 -0
  2. package/dist/bot/commands.d.ts +41 -0
  3. package/dist/bot/commands.d.ts.map +1 -0
  4. package/dist/bot/commands.js +231 -0
  5. package/dist/bot/commands.js.map +1 -0
  6. package/dist/bot/formatting.d.ts +62 -0
  7. package/dist/bot/formatting.d.ts.map +1 -0
  8. package/dist/bot/formatting.js +295 -0
  9. package/dist/bot/formatting.js.map +1 -0
  10. package/dist/bot/telegram.d.ts +93 -0
  11. package/dist/bot/telegram.d.ts.map +1 -0
  12. package/dist/bot/telegram.js +378 -0
  13. package/dist/bot/telegram.js.map +1 -0
  14. package/dist/bot/types.d.ts +28 -0
  15. package/dist/bot/types.d.ts.map +1 -0
  16. package/dist/bot/types.js +5 -0
  17. package/dist/bot/types.js.map +1 -0
  18. package/dist/bridge/daemon.d.ts +93 -0
  19. package/dist/bridge/daemon.d.ts.map +1 -0
  20. package/dist/bridge/daemon.js +626 -0
  21. package/dist/bridge/daemon.js.map +1 -0
  22. package/dist/bridge/index.d.ts +10 -0
  23. package/dist/bridge/index.d.ts.map +1 -0
  24. package/dist/bridge/index.js +9 -0
  25. package/dist/bridge/index.js.map +1 -0
  26. package/dist/bridge/injector.d.ts +97 -0
  27. package/dist/bridge/injector.d.ts.map +1 -0
  28. package/dist/bridge/injector.js +289 -0
  29. package/dist/bridge/injector.js.map +1 -0
  30. package/dist/bridge/session.d.ts +108 -0
  31. package/dist/bridge/session.d.ts.map +1 -0
  32. package/dist/bridge/session.js +381 -0
  33. package/dist/bridge/session.js.map +1 -0
  34. package/dist/bridge/socket.d.ts +97 -0
  35. package/dist/bridge/socket.d.ts.map +1 -0
  36. package/dist/bridge/socket.js +436 -0
  37. package/dist/bridge/socket.js.map +1 -0
  38. package/dist/bridge/types.d.ts +38 -0
  39. package/dist/bridge/types.d.ts.map +1 -0
  40. package/dist/bridge/types.js +5 -0
  41. package/dist/bridge/types.js.map +1 -0
  42. package/dist/cli.d.ts +7 -0
  43. package/dist/cli.d.ts.map +1 -0
  44. package/dist/cli.js +332 -0
  45. package/dist/cli.js.map +1 -0
  46. package/dist/hooks/handler.d.ts +94 -0
  47. package/dist/hooks/handler.d.ts.map +1 -0
  48. package/dist/hooks/handler.js +431 -0
  49. package/dist/hooks/handler.js.map +1 -0
  50. package/dist/hooks/index.d.ts +8 -0
  51. package/dist/hooks/index.d.ts.map +1 -0
  52. package/dist/hooks/index.js +7 -0
  53. package/dist/hooks/index.js.map +1 -0
  54. package/dist/hooks/installer.d.ts +46 -0
  55. package/dist/hooks/installer.d.ts.map +1 -0
  56. package/dist/hooks/installer.js +317 -0
  57. package/dist/hooks/installer.js.map +1 -0
  58. package/dist/hooks/types.d.ts +88 -0
  59. package/dist/hooks/types.d.ts.map +1 -0
  60. package/dist/hooks/types.js +6 -0
  61. package/dist/hooks/types.js.map +1 -0
  62. package/dist/index.d.ts +19 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +20 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/service/doctor.d.ts +10 -0
  67. package/dist/service/doctor.d.ts.map +1 -0
  68. package/dist/service/doctor.js +424 -0
  69. package/dist/service/doctor.js.map +1 -0
  70. package/dist/service/manager.d.ts +48 -0
  71. package/dist/service/manager.d.ts.map +1 -0
  72. package/dist/service/manager.js +584 -0
  73. package/dist/service/manager.js.map +1 -0
  74. package/dist/service/setup.d.ts +10 -0
  75. package/dist/service/setup.d.ts.map +1 -0
  76. package/dist/service/setup.js +266 -0
  77. package/dist/service/setup.js.map +1 -0
  78. package/dist/utils/chunker.d.ts +24 -0
  79. package/dist/utils/chunker.d.ts.map +1 -0
  80. package/dist/utils/chunker.js +123 -0
  81. package/dist/utils/chunker.js.map +1 -0
  82. package/dist/utils/config.d.ts +48 -0
  83. package/dist/utils/config.d.ts.map +1 -0
  84. package/dist/utils/config.js +154 -0
  85. package/dist/utils/config.js.map +1 -0
  86. package/dist/utils/logger.d.ts +7 -0
  87. package/dist/utils/logger.d.ts.map +1 -0
  88. package/dist/utils/logger.js +28 -0
  89. package/dist/utils/logger.js.map +1 -0
  90. package/package.json +88 -0
  91. package/postinstall.cjs +76 -0
  92. package/scripts/claude-wrapper.sh +122 -0
  93. package/scripts/doctor.sh +433 -0
  94. package/scripts/get-chat-id.sh +64 -0
  95. package/scripts/global-hooks.sh +39 -0
  96. package/scripts/install.sh +831 -0
  97. package/scripts/start-daemon.sh +49 -0
  98. package/scripts/telegram-hook.sh +449 -0
  99. package/scripts/uninstall.sh +261 -0
@@ -0,0 +1,831 @@
1
+ #!/bin/bash
2
+ #
3
+ # Claude Telegram Mirror - Interactive Installer
4
+ #
5
+ # Usage:
6
+ # curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh | bash
7
+ #
8
+ # Or run locally:
9
+ # ./scripts/install.sh
10
+ #
11
+ # Supports Linux and macOS
12
+ #
13
+
14
+ set -e
15
+
16
+ # Colors
17
+ RED='\033[0;31m'
18
+ GREEN='\033[0;32m'
19
+ YELLOW='\033[1;33m'
20
+ BLUE='\033[0;34m'
21
+ CYAN='\033[0;36m'
22
+ BOLD='\033[1m'
23
+ NC='\033[0m' # No Color
24
+
25
+ # Configuration
26
+ REPO_URL="https://github.com/robertelee78/claude-telegram-mirror.git"
27
+ INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/share/claude-telegram-mirror}"
28
+ CONFIG_FILE="$HOME/.telegram-env"
29
+
30
+ # State variables (filled during installation)
31
+ BOT_TOKEN=""
32
+ BOT_USERNAME=""
33
+ CHAT_ID=""
34
+
35
+ # ============================================
36
+ # TTY HANDLING FOR curl | bash
37
+ # ============================================
38
+ # When run via "curl ... | bash", stdin is the curl output (the script itself).
39
+ # We need to read user input from /dev/tty instead.
40
+ #
41
+ # This is the standard pattern used by rustup, nvm, and other installers.
42
+
43
+ # Check if we have a TTY available for interactive input
44
+ if [ -t 0 ]; then
45
+ # stdin is a terminal - normal execution
46
+ TTY_INPUT="/dev/stdin"
47
+ else
48
+ # stdin is NOT a terminal (likely piped from curl)
49
+ # Try to use /dev/tty for interactive input
50
+ # We need to verify /dev/tty actually works, not just exists
51
+ if [ -e /dev/tty ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then
52
+ # Test that we can actually read from it
53
+ if (echo "" > /dev/tty) 2>/dev/null; then
54
+ TTY_INPUT="/dev/tty"
55
+ else
56
+ echo ""
57
+ echo -e "${RED}ERROR: This installer requires interactive input.${NC}"
58
+ echo ""
59
+ echo " You're running via 'curl | bash' but /dev/tty is not usable."
60
+ echo ""
61
+ echo " Options:"
62
+ echo " 1. Download and run the script directly:"
63
+ echo " curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh -o install.sh"
64
+ echo " bash install.sh"
65
+ echo ""
66
+ echo " 2. If in a Docker container, run with -it flags:"
67
+ echo " docker run -it ..."
68
+ echo ""
69
+ exit 1
70
+ fi
71
+ else
72
+ echo ""
73
+ echo -e "${RED}ERROR: This installer requires interactive input.${NC}"
74
+ echo ""
75
+ echo " You're running via 'curl | bash' but /dev/tty is not available."
76
+ echo ""
77
+ echo " Options:"
78
+ echo " 1. Download and run the script directly:"
79
+ echo " curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh -o install.sh"
80
+ echo " bash install.sh"
81
+ echo ""
82
+ echo " 2. If in a Docker container, run with -it flags:"
83
+ echo " docker run -it ..."
84
+ echo ""
85
+ exit 1
86
+ fi
87
+ fi
88
+
89
+ # Wrapper function for read that uses the correct input source
90
+ # Usage: prompt_read "prompt text" VARIABLE_NAME
91
+ prompt_read() {
92
+ local prompt="$1"
93
+ local varname="$2"
94
+ local result
95
+
96
+ # Print prompt to stderr (so it shows regardless of stdin redirection)
97
+ # Use -n to avoid newline after prompt
98
+ echo -n "$prompt" >&2
99
+
100
+ # Read from TTY - use || true to prevent set -e from killing us
101
+ # The -r flag prevents backslash interpretation
102
+ if ! read -r result < "$TTY_INPUT"; then
103
+ echo "" >&2
104
+ echo -e "${RED}ERROR: Failed to read input from terminal${NC}" >&2
105
+ echo " TTY_INPUT=$TTY_INPUT" >&2
106
+ exit 1
107
+ fi
108
+
109
+ # Assign to the named variable
110
+ eval "$varname=\$result"
111
+ }
112
+
113
+ # Wrapper for simple "press enter to continue" prompts
114
+ prompt_continue() {
115
+ local prompt="${1:-Press Enter to continue...}"
116
+ local dummy
117
+
118
+ echo -n "$prompt" >&2
119
+
120
+ if ! read -r dummy < "$TTY_INPUT"; then
121
+ echo "" >&2
122
+ echo -e "${RED}ERROR: Failed to read input from terminal${NC}" >&2
123
+ exit 1
124
+ fi
125
+ }
126
+
127
+ # ============================================
128
+ # UTILITY FUNCTIONS
129
+ # ============================================
130
+
131
+ info() {
132
+ echo -e "${BLUE}ℹ${NC} $1"
133
+ }
134
+
135
+ success() {
136
+ echo -e "${GREEN}✓${NC} $1"
137
+ }
138
+
139
+ warn() {
140
+ echo -e "${YELLOW}⚠${NC} $1"
141
+ }
142
+
143
+ error() {
144
+ echo -e "${RED}✗${NC} $1"
145
+ }
146
+
147
+ header() {
148
+ echo ""
149
+ echo -e "${BOLD}═══════════════════════════════════════════════${NC}"
150
+ echo -e "${BOLD} $1${NC}"
151
+ echo -e "${BOLD}═══════════════════════════════════════════════${NC}"
152
+ echo ""
153
+ }
154
+
155
+ # ============================================
156
+ # STEP 1: PREREQUISITES CHECK
157
+ # ============================================
158
+
159
+ check_prerequisites() {
160
+ header "STEP 1: CHECKING PREREQUISITES"
161
+
162
+ local missing=()
163
+ local os_type=$(uname -s)
164
+
165
+ # Check each required tool
166
+ for cmd in node npm git jq tmux nc curl; do
167
+ if command -v "$cmd" &> /dev/null; then
168
+ if [[ "$cmd" == "node" ]]; then
169
+ local node_version=$(node --version | sed 's/v//' | cut -d. -f1)
170
+ if [[ $node_version -ge 18 ]]; then
171
+ success "$cmd $(node --version)"
172
+ else
173
+ error "$cmd $(node --version) - Need v18+"
174
+ missing+=("node")
175
+ fi
176
+ else
177
+ success "$cmd"
178
+ fi
179
+ else
180
+ error "$cmd not found"
181
+ missing+=("$cmd")
182
+ fi
183
+ done
184
+
185
+ # If anything missing, show install instructions
186
+ if [[ ${#missing[@]} -gt 0 ]]; then
187
+ echo ""
188
+ warn "Missing prerequisites: ${missing[*]}"
189
+ echo ""
190
+
191
+ if [[ "$os_type" == "Darwin" ]]; then
192
+ echo " Install with Homebrew:"
193
+ echo " brew install ${missing[*]}"
194
+ else
195
+ echo " Install on Debian/Ubuntu:"
196
+ echo " sudo apt update && sudo apt install -y ${missing[*]}"
197
+ echo ""
198
+ echo " Install on Fedora/RHEL:"
199
+ echo " sudo dnf install -y ${missing[*]}"
200
+ fi
201
+
202
+ if [[ " ${missing[*]} " =~ " node " ]]; then
203
+ echo ""
204
+ echo " For Node.js 18+, consider using nvm:"
205
+ echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash"
206
+ echo " nvm install 18"
207
+ fi
208
+
209
+ echo ""
210
+ error "Please install missing prerequisites and run again."
211
+ exit 1
212
+ fi
213
+
214
+ success "All prerequisites met!"
215
+ }
216
+
217
+ # ============================================
218
+ # STEP 2: BOT TOKEN
219
+ # ============================================
220
+
221
+ get_bot_token() {
222
+ header "STEP 2: CREATE TELEGRAM BOT"
223
+
224
+ echo " You need to create a Telegram bot via @BotFather."
225
+ echo ""
226
+ echo " 1. Open Telegram and search for @BotFather"
227
+ echo " 2. Send /newbot"
228
+ echo " 3. Choose a name (e.g., 'Claude Mirror')"
229
+ echo " 4. Choose a username (must end in 'bot', e.g., 'claude_mirror_bot')"
230
+ echo " 5. Copy the API token"
231
+ echo ""
232
+
233
+ while true; do
234
+ prompt_read "Enter your bot token: " BOT_TOKEN
235
+
236
+ if [[ -z "$BOT_TOKEN" ]]; then
237
+ warn "Token cannot be empty"
238
+ continue
239
+ fi
240
+
241
+ # Validate token format (should be like: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz)
242
+ if [[ ! "$BOT_TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]]; then
243
+ warn "Token format looks incorrect. Expected format: 123456789:ABCdefGHI..."
244
+ prompt_read "Try again? [Y/n]: " retry
245
+ [[ "$retry" =~ ^[Nn] ]] && exit 1
246
+ continue
247
+ fi
248
+
249
+ # Verify token with Telegram API
250
+ info "Verifying token with Telegram..."
251
+ local response=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getMe")
252
+
253
+ if echo "$response" | jq -e '.ok == true' &> /dev/null; then
254
+ BOT_USERNAME=$(echo "$response" | jq -r '.result.username')
255
+ success "Bot verified: @${BOT_USERNAME}"
256
+ break
257
+ else
258
+ local error_msg=$(echo "$response" | jq -r '.description // "Unknown error"')
259
+ error "Token validation failed: $error_msg"
260
+ prompt_read "Try again? [Y/n]: " retry
261
+ [[ "$retry" =~ ^[Nn] ]] && exit 1
262
+ fi
263
+ done
264
+ }
265
+
266
+ # ============================================
267
+ # STEP 3: DISABLE PRIVACY MODE
268
+ # ============================================
269
+
270
+ configure_privacy_mode() {
271
+ header "STEP 3: DISABLE PRIVACY MODE"
272
+
273
+ echo " Your bot needs to see all group messages (not just commands)."
274
+ echo ""
275
+ echo " 1. Go back to @BotFather in Telegram"
276
+ echo " 2. Send /mybots"
277
+ echo " 3. Select @${BOT_USERNAME}"
278
+ echo " 4. Click 'Bot Settings'"
279
+ echo " 5. Click 'Group Privacy'"
280
+ echo " 6. Click 'Turn off'"
281
+ echo ""
282
+ echo " You should see: 'Privacy mode is disabled for @${BOT_USERNAME}'"
283
+ echo ""
284
+
285
+ while true; do
286
+ prompt_read "Have you disabled privacy mode? [y/n]: " confirmed
287
+
288
+ if [[ "$confirmed" =~ ^[Yy] ]]; then
289
+ success "Privacy mode configured"
290
+ break
291
+ elif [[ "$confirmed" =~ ^[Nn] ]]; then
292
+ warn "Privacy mode MUST be disabled for the bot to work."
293
+ echo " Please complete this step before continuing."
294
+ prompt_read "Try again? [Y/n]: " retry
295
+ [[ "$retry" =~ ^[Nn] ]] && exit 1
296
+ else
297
+ warn "Please enter 'y' or 'n'"
298
+ fi
299
+ done
300
+ }
301
+
302
+ # ============================================
303
+ # STEP 4: CREATE SUPERGROUP WITH TOPICS
304
+ # ============================================
305
+
306
+ setup_telegram_group() {
307
+ header "STEP 4: SETUP SUPERGROUP WITH TOPICS"
308
+
309
+ echo " You need a Telegram supergroup with Topics enabled."
310
+ echo ""
311
+ echo " ${BOLD}Option A: Use an existing supergroup${NC}"
312
+ echo " 1. Add @${BOT_USERNAME} to your existing supergroup"
313
+ echo " 2. Make the bot an admin with 'Manage Topics' permission"
314
+ echo " 3. Send any message in the group (so we can detect it)"
315
+ echo ""
316
+ echo " ${BOLD}Option B: Create a new group${NC}"
317
+ echo " 1. In Telegram, create a new group"
318
+ echo " 2. Add @${BOT_USERNAME} to the group"
319
+ echo " 3. Go to group settings → Enable 'Topics'"
320
+ echo " (This converts it to a supergroup)"
321
+ echo " 4. Make the bot an admin with 'Manage Topics' permission"
322
+ echo " 5. Send any message in the group"
323
+ echo ""
324
+
325
+ prompt_continue "Press Enter when you've completed these steps..."
326
+
327
+ # Try to auto-detect the chat ID
328
+ info "Looking for your group..."
329
+
330
+ local updates=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=100")
331
+
332
+ if ! echo "$updates" | jq -e '.ok == true' &> /dev/null; then
333
+ error "Failed to get updates from Telegram API"
334
+ manual_chat_id_entry
335
+ return
336
+ fi
337
+
338
+ # Extract unique supergroups (chat_id starting with -100)
339
+ local groups=$(echo "$updates" | jq -r '
340
+ .result[]
341
+ | .message.chat // .my_chat_member.chat // empty
342
+ | select(.id < 0)
343
+ | select(.id | tostring | startswith("-100"))
344
+ | "\(.id)|\(.title // "Unknown")"
345
+ ' | sort -u)
346
+
347
+ if [[ -z "$groups" ]]; then
348
+ warn "No supergroups found. This can happen if:"
349
+ echo " - The bot hasn't seen any messages yet"
350
+ echo " - The group wasn't converted to a supergroup (enable Topics!)"
351
+ echo ""
352
+ manual_chat_id_entry
353
+ return
354
+ fi
355
+
356
+ # Count groups
357
+ local group_count=$(echo "$groups" | wc -l | tr -d ' ')
358
+
359
+ if [[ $group_count -eq 1 ]]; then
360
+ # Only one group found
361
+ CHAT_ID=$(echo "$groups" | cut -d'|' -f1)
362
+ local group_title=$(echo "$groups" | cut -d'|' -f2)
363
+ success "Found group: $group_title ($CHAT_ID)"
364
+
365
+ prompt_read "Is this the correct group? [Y/n]: " confirm
366
+ if [[ "$confirm" =~ ^[Nn] ]]; then
367
+ manual_chat_id_entry
368
+ return
369
+ fi
370
+ else
371
+ # Multiple groups found
372
+ echo ""
373
+ echo " Found multiple groups:"
374
+ echo ""
375
+
376
+ local i=1
377
+ declare -a group_ids
378
+ declare -a group_titles
379
+
380
+ while IFS='|' read -r gid gtitle; do
381
+ echo " $i) $gtitle ($gid)"
382
+ group_ids+=("$gid")
383
+ group_titles+=("$gtitle")
384
+ ((i++))
385
+ done <<< "$groups"
386
+
387
+ echo ""
388
+ prompt_read "Select group number (1-$group_count): " selection
389
+
390
+ if [[ "$selection" =~ ^[0-9]+$ ]] && [[ $selection -ge 1 ]] && [[ $selection -le $group_count ]]; then
391
+ CHAT_ID="${group_ids[$((selection-1))]}"
392
+ success "Selected: ${group_titles[$((selection-1))]} ($CHAT_ID)"
393
+ else
394
+ warn "Invalid selection"
395
+ manual_chat_id_entry
396
+ return
397
+ fi
398
+ fi
399
+ }
400
+
401
+ manual_chat_id_entry() {
402
+ echo ""
403
+ echo " Enter the chat ID manually."
404
+ echo " You can find it by:"
405
+ echo " 1. Send a message in the group"
406
+ echo " 2. Visit: https://api.telegram.org/bot${BOT_TOKEN}/getUpdates"
407
+ echo " 3. Look for 'chat':{'id': -100XXXXXXXXXX}"
408
+ echo ""
409
+
410
+ while true; do
411
+ prompt_read "Enter chat ID (starts with -100): " CHAT_ID
412
+
413
+ if [[ "$CHAT_ID" =~ ^-100[0-9]+$ ]]; then
414
+ success "Chat ID format valid"
415
+ break
416
+ else
417
+ warn "Chat ID should start with -100 (supergroup format)"
418
+ prompt_read "Try again? [Y/n]: " retry
419
+ [[ "$retry" =~ ^[Nn] ]] && exit 1
420
+ fi
421
+ done
422
+ }
423
+
424
+ # ============================================
425
+ # STEP 5: VERIFY BOT CAN POST
426
+ # ============================================
427
+
428
+ verify_bot_permissions() {
429
+ header "STEP 5: VERIFY BOT PERMISSIONS"
430
+
431
+ info "Testing if bot can post to the group..."
432
+
433
+ local response=$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
434
+ -H "Content-Type: application/json" \
435
+ -d "{\"chat_id\": \"${CHAT_ID}\", \"text\": \"🤖 Claude Telegram Mirror - Installation test successful!\"}")
436
+
437
+ if echo "$response" | jq -e '.ok == true' &> /dev/null; then
438
+ success "Bot can post to the group!"
439
+ echo ""
440
+ echo " Check your Telegram group - you should see a test message."
441
+ prompt_continue "Press Enter to continue..."
442
+ else
443
+ local error_msg=$(echo "$response" | jq -r '.description // "Unknown error"')
444
+ error "Bot cannot post: $error_msg"
445
+ echo ""
446
+ echo " Common fixes:"
447
+ echo " - Make sure the bot is an admin in the group"
448
+ echo " - Ensure 'Post Messages' permission is enabled"
449
+ echo " - Check that 'Manage Topics' permission is enabled"
450
+ echo ""
451
+ prompt_continue "Fix the issue and press Enter to retry, or Ctrl+C to exit..."
452
+ verify_bot_permissions
453
+ fi
454
+ }
455
+
456
+ # ============================================
457
+ # STEP 6: INSTALL PACKAGE
458
+ # ============================================
459
+
460
+ install_package() {
461
+ header "STEP 6: INSTALL PACKAGE"
462
+
463
+ # Check if already installed
464
+ if [[ -d "$INSTALL_DIR" ]]; then
465
+ warn "Installation directory already exists: $INSTALL_DIR"
466
+ prompt_read "Remove and reinstall? [y/N]: " reinstall
467
+ if [[ "$reinstall" =~ ^[Yy] ]]; then
468
+ info "Removing existing installation..."
469
+ rm -rf "$INSTALL_DIR"
470
+ else
471
+ info "Keeping existing installation, updating..."
472
+ cd "$INSTALL_DIR"
473
+ git pull
474
+ npm install
475
+ npm run build
476
+ success "Package updated"
477
+ return
478
+ fi
479
+ fi
480
+
481
+ # Clone repository
482
+ info "Cloning repository..."
483
+ mkdir -p "$(dirname "$INSTALL_DIR")"
484
+ git clone "$REPO_URL" "$INSTALL_DIR"
485
+
486
+ # Install dependencies
487
+ cd "$INSTALL_DIR"
488
+ info "Installing dependencies..."
489
+ npm install
490
+
491
+ # Build
492
+ info "Building..."
493
+ npm run build
494
+
495
+ success "Package installed to $INSTALL_DIR"
496
+
497
+ # Create ctm symlink
498
+ local bin_dir="$HOME/.local/bin"
499
+ mkdir -p "$bin_dir"
500
+
501
+ if [[ ! -f "$bin_dir/ctm" ]]; then
502
+ cat > "$bin_dir/ctm" << EOF
503
+ #!/bin/bash
504
+ node "$INSTALL_DIR/dist/cli.js" "\$@"
505
+ EOF
506
+ chmod +x "$bin_dir/ctm"
507
+ success "Created 'ctm' command in $bin_dir"
508
+
509
+ # Check if bin_dir is in PATH
510
+ if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
511
+ warn "$bin_dir is not in your PATH"
512
+ echo " Add this to your ~/.bashrc or ~/.zshrc:"
513
+ echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
514
+ fi
515
+ fi
516
+ }
517
+
518
+ # ============================================
519
+ # STEP 7: CREATE CONFIG
520
+ # ============================================
521
+
522
+ create_config() {
523
+ header "STEP 7: CREATE CONFIGURATION"
524
+
525
+ info "Creating $CONFIG_FILE..."
526
+
527
+ cat > "$CONFIG_FILE" << EOF
528
+ # Claude Telegram Mirror Configuration
529
+ # Generated by install.sh on $(date)
530
+
531
+ export TELEGRAM_BOT_TOKEN="$BOT_TOKEN"
532
+ export TELEGRAM_CHAT_ID="$CHAT_ID"
533
+ export TELEGRAM_MIRROR=true
534
+
535
+ # Optional settings:
536
+ # export TELEGRAM_MIRROR_VERBOSE=true
537
+ # export TELEGRAM_BRIDGE_SOCKET=~/.config/claude-telegram-mirror/bridge.sock
538
+ EOF
539
+
540
+ chmod 600 "$CONFIG_FILE"
541
+ success "Configuration saved to $CONFIG_FILE"
542
+
543
+ # Add to shell profile
544
+ local shell_profile=""
545
+ if [[ -f "$HOME/.bashrc" ]]; then
546
+ shell_profile="$HOME/.bashrc"
547
+ elif [[ -f "$HOME/.zshrc" ]]; then
548
+ shell_profile="$HOME/.zshrc"
549
+ elif [[ -f "$HOME/.profile" ]]; then
550
+ shell_profile="$HOME/.profile"
551
+ fi
552
+
553
+ if [[ -n "$shell_profile" ]]; then
554
+ if ! grep -q "telegram-env" "$shell_profile" 2>/dev/null; then
555
+ echo "" >> "$shell_profile"
556
+ echo "# Claude Telegram Mirror" >> "$shell_profile"
557
+ echo "[[ -f ~/.telegram-env ]] && source ~/.telegram-env" >> "$shell_profile"
558
+ success "Added to $shell_profile"
559
+ else
560
+ info "Already in $shell_profile"
561
+ fi
562
+ fi
563
+
564
+ # Source it now
565
+ source "$CONFIG_FILE"
566
+ }
567
+
568
+ # ============================================
569
+ # STEP 8: INSTALL HOOKS
570
+ # ============================================
571
+
572
+ install_hooks() {
573
+ header "STEP 8: INSTALL CLAUDE CODE HOOKS"
574
+
575
+ cd "$INSTALL_DIR"
576
+
577
+ # Install global hooks
578
+ info "Installing global hooks..."
579
+ node dist/cli.js install-hooks
580
+ success "Global hooks installed to ~/.claude/settings.json"
581
+
582
+ # CRITICAL: Warn about project-level settings
583
+ echo ""
584
+ echo "┌─────────────────────────────────────────────────────────────┐"
585
+ echo "│ ⚠️ IMPORTANT: PROJECT-LEVEL HOOKS │"
586
+ echo "├─────────────────────────────────────────────────────────────┤"
587
+ echo "│ │"
588
+ echo "│ If you use Claude Code in projects that have their own │"
589
+ echo "│ .claude/settings.json file, the GLOBAL hooks we just │"
590
+ echo "│ installed will be IGNORED in those projects. │"
591
+ echo "│ │"
592
+ echo "│ To enable Telegram mirroring in a specific project: │"
593
+ echo "│ │"
594
+ echo "│ cd /path/to/your/project │"
595
+ echo "│ ctm install-hooks --project │"
596
+ echo "│ │"
597
+ echo "│ This adds hooks to that project's .claude/settings.json │"
598
+ echo "│ │"
599
+ echo "└─────────────────────────────────────────────────────────────┘"
600
+ echo ""
601
+
602
+ # Ask if they want to install to a project now
603
+ prompt_read "Do you have a project with .claude/settings.json that needs hooks? [y/N]: " HAS_PROJECT
604
+
605
+ if [[ "$HAS_PROJECT" =~ ^[Yy] ]]; then
606
+ install_project_hooks
607
+ fi
608
+ }
609
+
610
+ install_project_hooks() {
611
+ while true; do
612
+ echo ""
613
+ prompt_read "Enter project path (or 'done' to finish): " PROJECT_PATH
614
+
615
+ [[ "$PROJECT_PATH" == "done" || -z "$PROJECT_PATH" ]] && break
616
+
617
+ # Expand ~ if present
618
+ PROJECT_PATH="${PROJECT_PATH/#\~/$HOME}"
619
+
620
+ # Check if it's a valid project with .claude/
621
+ if [[ -d "$PROJECT_PATH/.claude" ]]; then
622
+ info "Installing hooks to $PROJECT_PATH..."
623
+
624
+ # Save current dir, go to project, install, come back
625
+ pushd "$PROJECT_PATH" > /dev/null
626
+ node "$INSTALL_DIR/dist/cli.js" install-hooks --project
627
+ popd > /dev/null
628
+
629
+ success "Hooks installed to $PROJECT_PATH/.claude/settings.json"
630
+ elif [[ -d "$PROJECT_PATH" ]]; then
631
+ warn "No .claude/ directory in $PROJECT_PATH"
632
+ echo " This project doesn't have custom Claude settings."
633
+ echo " Global hooks will work here - no action needed!"
634
+ else
635
+ warn "Directory not found: $PROJECT_PATH"
636
+ fi
637
+
638
+ prompt_read "Add another project? [y/N]: " ANOTHER
639
+ [[ "$ANOTHER" =~ ^[Yy] ]] || break
640
+ done
641
+ }
642
+
643
+ # ============================================
644
+ # STEP 9: INSTALL SERVICE
645
+ # ============================================
646
+
647
+ install_service() {
648
+ header "STEP 9: INSTALL SYSTEM SERVICE"
649
+
650
+ cd "$INSTALL_DIR"
651
+
652
+ local os_type=$(uname -s)
653
+
654
+ info "Installing service..."
655
+ node dist/cli.js service install
656
+
657
+ info "Starting service..."
658
+ node dist/cli.js service start
659
+
660
+ # Verify service is running
661
+ sleep 2
662
+
663
+ if node dist/cli.js service status 2>/dev/null | grep -q "running\|active"; then
664
+ success "Service is running!"
665
+ else
666
+ warn "Service may not have started correctly"
667
+
668
+ if [[ "$os_type" == "Linux" ]]; then
669
+ echo " Check status: systemctl --user status claude-telegram-mirror"
670
+ echo " View logs: journalctl --user -u claude-telegram-mirror -f"
671
+ echo ""
672
+ echo " You may need to enable user lingering:"
673
+ echo " loginctl enable-linger $USER"
674
+ else
675
+ echo " Check status: launchctl list | grep claude"
676
+ echo " View logs: cat ~/Library/Logs/claude-telegram-mirror.*.log"
677
+ fi
678
+ fi
679
+ }
680
+
681
+ # ============================================
682
+ # STEP 10: COMPLETION
683
+ # ============================================
684
+
685
+ show_completion() {
686
+ echo ""
687
+ echo "╔═════════════════════════════════════════════╗"
688
+ echo "║ ✅ INSTALLATION COMPLETE ║"
689
+ echo "╚═════════════════════════════════════════════╝"
690
+ echo ""
691
+ echo " Bot: @${BOT_USERNAME}"
692
+ echo " Chat: ${CHAT_ID}"
693
+ echo " Config: ~/.telegram-env"
694
+ echo " Install: ${INSTALL_DIR}"
695
+ echo ""
696
+ echo " Commands:"
697
+ echo " ctm start # Start daemon (foreground)"
698
+ echo " ctm status # Show status"
699
+ echo " ctm service status # Service status"
700
+ echo ""
701
+
702
+ local os_type=$(uname -s)
703
+ if [[ "$os_type" == "Linux" ]]; then
704
+ echo " Logs: journalctl --user -u claude-telegram-mirror -f"
705
+ else
706
+ echo " Logs: cat ~/Library/Logs/claude-telegram-mirror.*.log"
707
+ fi
708
+
709
+ echo ""
710
+ echo " ┌─────────────────────────────────────────────────────────┐"
711
+ echo " │ 📌 REMEMBER: Project-specific hooks │"
712
+ echo " │ │"
713
+ echo " │ For projects with .claude/settings.json: │"
714
+ echo " │ cd /path/to/project && ctm install-hooks -p │"
715
+ echo " └─────────────────────────────────────────────────────────┘"
716
+ echo ""
717
+ echo " Next steps:"
718
+ echo " 1. Run 'source ~/.bashrc' or restart terminal"
719
+ echo " 2. Start a Claude Code session in tmux:"
720
+ echo " tmux new -s claude"
721
+ echo " claude"
722
+ echo ""
723
+ echo " Your Claude sessions will now be mirrored to Telegram!"
724
+ echo ""
725
+ }
726
+
727
+ # ============================================
728
+ # CLEANUP ON ERROR
729
+ # ============================================
730
+
731
+ cleanup_on_error() {
732
+ echo ""
733
+ error "Installation failed!"
734
+ echo ""
735
+ echo " Partial installation may exist at:"
736
+ echo " $INSTALL_DIR"
737
+ echo " $CONFIG_FILE"
738
+ echo ""
739
+ echo " To clean up and retry:"
740
+ echo " rm -rf $INSTALL_DIR"
741
+ echo " rm -f $CONFIG_FILE"
742
+ echo ""
743
+ exit 1
744
+ }
745
+
746
+ # ============================================
747
+ # NONINTERACTIVE MODE (existing config)
748
+ # ============================================
749
+
750
+ check_existing_config() {
751
+ if [[ -f "$CONFIG_FILE" ]]; then
752
+ source "$CONFIG_FILE"
753
+
754
+ if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
755
+ echo ""
756
+ info "Found existing configuration at $CONFIG_FILE"
757
+
758
+ # Validate existing token
759
+ local response=$(curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe")
760
+ if echo "$response" | jq -e '.ok == true' &> /dev/null; then
761
+ BOT_USERNAME=$(echo "$response" | jq -r '.result.username')
762
+ BOT_TOKEN="$TELEGRAM_BOT_TOKEN"
763
+ CHAT_ID="$TELEGRAM_CHAT_ID"
764
+
765
+ echo " Bot: @${BOT_USERNAME}"
766
+ echo " Chat: ${CHAT_ID}"
767
+ echo ""
768
+
769
+ prompt_read "Use existing configuration? [Y/n]: " use_existing
770
+ if [[ ! "$use_existing" =~ ^[Nn] ]]; then
771
+ return 0 # Use existing
772
+ fi
773
+ else
774
+ warn "Existing token is invalid"
775
+ fi
776
+ fi
777
+ fi
778
+
779
+ return 1 # Need new config
780
+ }
781
+
782
+ # ============================================
783
+ # MAIN
784
+ # ============================================
785
+
786
+ main() {
787
+ echo ""
788
+ echo "╔═══════════════════════════════════════════════════════════════╗"
789
+ echo "║ ║"
790
+ echo "║ 🤖 Claude Telegram Mirror Installer ║"
791
+ echo "║ ║"
792
+ echo "║ Mirror your Claude Code sessions to Telegram ║"
793
+ echo "║ Control Claude from your phone! ║"
794
+ echo "║ ║"
795
+ echo "╚═══════════════════════════════════════════════════════════════╝"
796
+ echo ""
797
+
798
+ # Set up error handler
799
+ trap cleanup_on_error ERR
800
+
801
+ # Step 1: Prerequisites
802
+ check_prerequisites
803
+
804
+ # Check for existing config
805
+ if check_existing_config; then
806
+ # Skip Telegram setup, go straight to install
807
+ install_package
808
+ install_hooks
809
+ install_service
810
+ show_completion
811
+ exit 0
812
+ fi
813
+
814
+ # Steps 2-5: Telegram setup
815
+ get_bot_token
816
+ configure_privacy_mode
817
+ setup_telegram_group
818
+ verify_bot_permissions
819
+
820
+ # Steps 6-9: Installation
821
+ install_package
822
+ create_config
823
+ install_hooks
824
+ install_service
825
+
826
+ # Step 10: Done!
827
+ show_completion
828
+ }
829
+
830
+ # Run main
831
+ main "$@"