ccbot 1.0.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.
- package/LICENSE +21 -0
- package/README.en.md +158 -0
- package/README.md +158 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +17 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +103 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +41 -0
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +85 -0
- package/dist/config-manager.d.ts +15 -0
- package/dist/config-manager.js +71 -0
- package/dist/hook/hook-handler.d.ts +9 -0
- package/dist/hook/hook-handler.js +126 -0
- package/dist/hook/hook-installer.d.ts +11 -0
- package/dist/hook/hook-installer.js +97 -0
- package/dist/hook/hook-server.d.ts +12 -0
- package/dist/hook/hook-server.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +59 -0
- package/dist/monitor/transcript-parser.d.ts +6 -0
- package/dist/monitor/transcript-parser.js +56 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +130 -0
- package/dist/telegram/bot.d.ts +15 -0
- package/dist/telegram/bot.js +79 -0
- package/dist/telegram/message-formatter.d.ts +13 -0
- package/dist/telegram/message-formatter.js +53 -0
- package/dist/telegram/message-sender.d.ts +2 -0
- package/dist/telegram/message-sender.js +44 -0
- package/dist/utils/error-utils.d.ts +1 -0
- package/dist/utils/error-utils.js +3 -0
- package/dist/utils/install-detection.d.ts +4 -0
- package/dist/utils/install-detection.js +40 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Claude Code Bot Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# π€ ccbot β Claude Code β Telegram Notification Bot
|
|
2
|
+
|
|
3
|
+
[TiαΊΏng Viα»t](./README.md)
|
|
4
|
+
|
|
5
|
+
> Get Telegram notifications when Claude Code completes a response β with git diff, processing time, and result summary.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
You're using Claude Code on your computer. You step away with your phone but have no idea if Claude Code is done yet or what files it changed.
|
|
12
|
+
|
|
13
|
+
**ccbot** is a lightweight bridge between Claude Code and Telegram β when Claude Code finishes, you get a notification right on your phone.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Claude Code completes response
|
|
17
|
+
β
|
|
18
|
+
Stop Hook triggers
|
|
19
|
+
β
|
|
20
|
+
ccbot receives event
|
|
21
|
+
β
|
|
22
|
+
Telegram notification π±
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- π **Auto notification** β Claude Code finishes β Telegram notifies you instantly
|
|
28
|
+
- π **Git diff included** β see changed files without opening your computer
|
|
29
|
+
- β± **Processing time** β know how long Claude Code took
|
|
30
|
+
- π **Response summary** β quick glance at what Claude Code replied
|
|
31
|
+
- π **User whitelist** β only authorized users can use the bot
|
|
32
|
+
- π **Auto-split messages** β long responses are automatically paginated `[1/N]`
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- **Node.js** β₯ 18
|
|
37
|
+
- **pnpm** (or npm/yarn)
|
|
38
|
+
- **Telegram Bot Token** β create from [@BotFather](https://t.me/BotFather)
|
|
39
|
+
- **Telegram User ID** β get from [@userinfobot](https://t.me/userinfobot)
|
|
40
|
+
|
|
41
|
+
## Getting Started
|
|
42
|
+
|
|
43
|
+
### Option 1: Global install (recommended)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add -g ccbot
|
|
47
|
+
ccbot setup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Option 2: npx (no install needed)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx ccbot setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Option 3: Clone repo (for development)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/palooza-kaida/ccbot.git
|
|
60
|
+
cd ccbot
|
|
61
|
+
pnpm install
|
|
62
|
+
pnpm setup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The setup wizard will guide you step by step:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
β π€ ccbot setup
|
|
69
|
+
β
|
|
70
|
+
β Telegram Bot Token
|
|
71
|
+
β your-bot-token
|
|
72
|
+
β
|
|
73
|
+
β Your Telegram User ID
|
|
74
|
+
β your-user-id
|
|
75
|
+
β
|
|
76
|
+
β Config saved
|
|
77
|
+
β Hook installed β ~/.claude/settings.json
|
|
78
|
+
β Chat ID registered
|
|
79
|
+
β
|
|
80
|
+
β π Setup complete!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
<details>
|
|
84
|
+
<summary>Manual setup (without wizard)</summary>
|
|
85
|
+
|
|
86
|
+
Create `~/.ccbot/config.json`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"telegram_bot_token": "123456:ABC-xxx",
|
|
91
|
+
"user_id": 123456789,
|
|
92
|
+
"hook_port": 9377
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then run `ccbot setup` to install the hook and register your chat ID.
|
|
97
|
+
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
### Start the bot
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Global install
|
|
106
|
+
ccbot
|
|
107
|
+
|
|
108
|
+
# Or npx
|
|
109
|
+
npx ccbot
|
|
110
|
+
|
|
111
|
+
# Or local dev
|
|
112
|
+
pnpm dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Once running, use Claude Code as usual β notifications will arrive on Telegram.
|
|
116
|
+
|
|
117
|
+
### Telegram Commands
|
|
118
|
+
|
|
119
|
+
| Command | Description |
|
|
120
|
+
|-----------|-----------------------------------------------------|
|
|
121
|
+
| `/start` | Re-register chat (auto during setup, rarely needed) |
|
|
122
|
+
| `/ping` | Check if bot is alive |
|
|
123
|
+
| `/status` | View bot status |
|
|
124
|
+
|
|
125
|
+
### Sample Notification
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
π€ Claude Code Response
|
|
129
|
+
π my-project | β± 45s
|
|
130
|
+
|
|
131
|
+
Fixed authentication bug in login.go. Main changes:
|
|
132
|
+
- Fix missing error check at line 42
|
|
133
|
+
- Add input validation...
|
|
134
|
+
|
|
135
|
+
π Changes:
|
|
136
|
+
βοΈ src/login.go
|
|
137
|
+
β src/validator.go
|
|
138
|
+
β src/old_auth.go
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Uninstall
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
ccbot uninstall
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
β ποΈ Uninstalling ccbot
|
|
149
|
+
β
|
|
150
|
+
β Hook removed from ~/.claude/settings.json
|
|
151
|
+
β Removed ~/.ccbot/ (config, state, hooks)
|
|
152
|
+
β
|
|
153
|
+
β ccbot uninstalled
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# π€ ccbot β Claude Code β Telegram Notification Bot
|
|
2
|
+
|
|
3
|
+
[English](./README.en.md)
|
|
4
|
+
|
|
5
|
+
> NhαΊn thΓ΄ng bΓ‘o Telegram khi Claude Code hoΓ n thΓ nh response β kΓ¨m git diff, thα»i gian xα» lΓ½, vΓ tΓ³m tαΊ―t kαΊΏt quαΊ£.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## VαΊ₯n Δα» giαΊ£i quyαΊΏt
|
|
10
|
+
|
|
11
|
+
BαΊ‘n Δang dΓΉng Claude Code trΓͺn mΓ‘y tΓnh. Ra ngoΓ i cαΊ§m Δiα»n thoαΊ‘i nhΖ°ng khΓ΄ng biαΊΏt Claude Code ΔΓ£ xong chΖ°a, thay Δα»i file nΓ o.
|
|
12
|
+
|
|
13
|
+
**ccbot** lΓ cαΊ§u nα»i nhαΊΉ giα»―a Claude Code vΓ Telegram β khi Claude Code xong viα»c, bαΊ‘n nhαΊn notification ngay trΓͺn Δiα»n thoαΊ‘i.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Claude Code xong response
|
|
17
|
+
β
|
|
18
|
+
Stop Hook trigger
|
|
19
|
+
β
|
|
20
|
+
ccbot nhαΊn event
|
|
21
|
+
β
|
|
22
|
+
Telegram notification π±
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## TΓnh nΔng
|
|
26
|
+
|
|
27
|
+
- π **Notification tα»± Δα»ng** β Claude Code xong β Telegram nhαΊn tin ngay
|
|
28
|
+
- π **Git diff kΓ¨m theo** β biαΊΏt file nΓ o thay Δα»i mΓ khΓ΄ng cαΊ§n mα» mΓ‘y tΓnh
|
|
29
|
+
- β± **Thα»i gian xα» lΓ½** β biαΊΏt Claude Code chαΊ‘y bao lΓ’u
|
|
30
|
+
- π **TΓ³m tαΊ―t response** β xem nhanh Claude Code trαΊ£ lα»i gΓ¬
|
|
31
|
+
- π **Whitelist user** β chα» user Δược phΓ©p mα»i dΓΉng Δược bot
|
|
32
|
+
- π **Auto-split message** β response dΓ i tα»± Δα»ng chia page `[1/N]`
|
|
33
|
+
|
|
34
|
+
## YΓͺu cαΊ§u
|
|
35
|
+
|
|
36
|
+
- **Node.js** β₯ 18
|
|
37
|
+
- **pnpm** (hoαΊ·c npm/yarn)
|
|
38
|
+
- **Telegram Bot Token** β tαΊ‘o tα»« [@BotFather](https://t.me/BotFather)
|
|
39
|
+
- **Telegram User ID** β lαΊ₯y tα»« [@userinfobot](https://t.me/userinfobot)
|
|
40
|
+
|
|
41
|
+
## BαΊ―t ΔαΊ§u
|
|
42
|
+
|
|
43
|
+
### CΓ‘ch 1: Global install (khuyαΊΏn nghα»)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add -g ccbot
|
|
47
|
+
ccbot setup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### CΓ‘ch 2: npx (khΓ΄ng cαΊ§n cΓ i)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx ccbot setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### CΓ‘ch 3: Clone repo (cho development)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/palooza-kaida/ccbot.git
|
|
60
|
+
cd ccbot
|
|
61
|
+
pnpm install
|
|
62
|
+
pnpm setup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Setup wizard sαΊ½ hΖ°α»ng dαΊ«n tα»«ng bΖ°α»c:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
β π€ ccbot setup
|
|
69
|
+
β
|
|
70
|
+
β Telegram Bot Token
|
|
71
|
+
β your-bot-token
|
|
72
|
+
β
|
|
73
|
+
β Your Telegram User ID
|
|
74
|
+
β your-user-id
|
|
75
|
+
β
|
|
76
|
+
β Config saved
|
|
77
|
+
β Hook installed β ~/.claude/settings.json
|
|
78
|
+
β Chat ID registered
|
|
79
|
+
β
|
|
80
|
+
β π Setup complete!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
<details>
|
|
84
|
+
<summary>ThiαΊΏt lαΊp thα»§ cΓ΄ng (khΓ΄ng dΓΉng wizard)</summary>
|
|
85
|
+
|
|
86
|
+
TαΊ‘o file `~/.ccbot/config.json`:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"telegram_bot_token": "123456:ABC-xxx",
|
|
91
|
+
"user_id": 123456789,
|
|
92
|
+
"hook_port": 9377
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Sau ΔΓ³ chαΊ‘y `ccbot setup` Δα» cΓ i hook vΓ ΔΔng kΓ½ chat ID.
|
|
97
|
+
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
## Sα» dα»₯ng
|
|
101
|
+
|
|
102
|
+
### Khα»i Δα»ng bot
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Global install
|
|
106
|
+
ccbot
|
|
107
|
+
|
|
108
|
+
# HoαΊ·c npx
|
|
109
|
+
npx ccbot
|
|
110
|
+
|
|
111
|
+
# HoαΊ·c local dev
|
|
112
|
+
pnpm dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Bot chαΊ‘y xong β dΓΉng Claude Code bΓ¬nh thΖ°α»ng β notification tα»± ΔαΊΏn Telegram.
|
|
116
|
+
|
|
117
|
+
### Telegram Commands
|
|
118
|
+
|
|
119
|
+
| Command | Chα»©c nΔng |
|
|
120
|
+
|-----------|---------------------------------------------------|
|
|
121
|
+
| `/start` | ΔΔng kΓ½ lαΊ‘i chat (tα»± Δα»ng khi setup, Γt khi cαΊ§n) |
|
|
122
|
+
| `/ping` | Kiα»m tra bot cΓ²n sα»ng khΓ΄ng |
|
|
123
|
+
| `/status` | Xem trαΊ‘ng thΓ‘i bot |
|
|
124
|
+
|
|
125
|
+
### Notification mαΊ«u
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
π€ Claude Code Response
|
|
129
|
+
π my-project | β± 45s
|
|
130
|
+
|
|
131
|
+
ΔΓ£ sα»a bug authentication trong login.go. Thay Δα»i chΓnh:
|
|
132
|
+
- Fix missing error check α» dΓ²ng 42
|
|
133
|
+
- ThΓͺm input validation...
|
|
134
|
+
|
|
135
|
+
π Changes:
|
|
136
|
+
βοΈ src/login.go
|
|
137
|
+
β src/validator.go
|
|
138
|
+
β src/old_auth.go
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Gα»‘ cΓ i ΔαΊ·t
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
ccbot uninstall
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
β ποΈ Uninstalling ccbot
|
|
149
|
+
β
|
|
150
|
+
β Hook removed from ~/.claude/settings.json
|
|
151
|
+
β Removed ~/.ccbot/ (config, state, hooks)
|
|
152
|
+
β
|
|
153
|
+
β ccbot uninstalled
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHelp(): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { detectCliPrefix } from "../utils/install-detection.js";
|
|
3
|
+
export function runHelp() {
|
|
4
|
+
const prefix = detectCliPrefix();
|
|
5
|
+
p.intro("π€ ccbot β Claude Code β Telegram Notification Bot");
|
|
6
|
+
p.log.message([
|
|
7
|
+
`Usage: ${prefix} [command]`,
|
|
8
|
+
"",
|
|
9
|
+
"Commands:",
|
|
10
|
+
" (none) Start the bot",
|
|
11
|
+
" setup Interactive setup (config + hooks)",
|
|
12
|
+
" update Update ccbot to latest version",
|
|
13
|
+
" uninstall Remove all ccbot data and hooks",
|
|
14
|
+
" help Show this help message",
|
|
15
|
+
].join("\n"));
|
|
16
|
+
p.outro("docs β https://github.com/palooza-kaida/ccbot");
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSetup(): Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { ConfigManager } from "../config-manager.js";
|
|
6
|
+
import { HookInstaller } from "../hook/hook-installer.js";
|
|
7
|
+
import { detectCliPrefix } from "../utils/install-detection.js";
|
|
8
|
+
import { formatError } from "../utils/error-utils.js";
|
|
9
|
+
export async function runSetup() {
|
|
10
|
+
p.intro("π€ ccbot setup");
|
|
11
|
+
let existing = null;
|
|
12
|
+
try {
|
|
13
|
+
existing = ConfigManager.load();
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
const credentials = await promptCredentials(existing);
|
|
17
|
+
const config = buildConfig(credentials, existing);
|
|
18
|
+
saveConfig(config);
|
|
19
|
+
installHook(config);
|
|
20
|
+
registerChatId(config.user_id);
|
|
21
|
+
const startCommand = detectCliPrefix();
|
|
22
|
+
p.outro(`π Setup complete!\n\n Next steps:\n 1. Start bot: ${startCommand}\n 2. Use Claude Code normally β notifications will arrive`);
|
|
23
|
+
}
|
|
24
|
+
async function promptCredentials(existing) {
|
|
25
|
+
const result = await p.group({
|
|
26
|
+
token: () => p.text({
|
|
27
|
+
message: "Telegram Bot Token",
|
|
28
|
+
placeholder: "Get from @BotFather β /newbot",
|
|
29
|
+
initialValue: existing?.telegram_bot_token ?? "",
|
|
30
|
+
validate(value) {
|
|
31
|
+
if (!value || !value.trim())
|
|
32
|
+
return "Bot token is required";
|
|
33
|
+
if (!value.includes(":"))
|
|
34
|
+
return "Invalid format (expected: 123456:ABC-xxx)";
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
userId: () => p.text({
|
|
38
|
+
message: "Your Telegram User ID",
|
|
39
|
+
placeholder: "Send /start to @userinfobot",
|
|
40
|
+
initialValue: existing?.user_id?.toString() ?? "",
|
|
41
|
+
validate(value) {
|
|
42
|
+
if (!value || !value.trim())
|
|
43
|
+
return "User ID is required";
|
|
44
|
+
if (isNaN(parseInt(value, 10)))
|
|
45
|
+
return "Must be a number";
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
}, {
|
|
49
|
+
onCancel: () => {
|
|
50
|
+
p.cancel("Setup cancelled.");
|
|
51
|
+
process.exit(0);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
token: result.token.trim(),
|
|
56
|
+
userId: parseInt(result.userId.trim(), 10),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildConfig(credentials, existing) {
|
|
60
|
+
return {
|
|
61
|
+
telegram_bot_token: credentials.token,
|
|
62
|
+
user_id: credentials.userId,
|
|
63
|
+
hook_port: existing?.hook_port || 9377,
|
|
64
|
+
hook_secret: existing?.hook_secret || ConfigManager.generateSecret(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function saveConfig(config) {
|
|
68
|
+
ConfigManager.save(config);
|
|
69
|
+
p.log.success("Config saved");
|
|
70
|
+
}
|
|
71
|
+
function installHook(config) {
|
|
72
|
+
try {
|
|
73
|
+
HookInstaller.install(config.hook_port, config.hook_secret);
|
|
74
|
+
p.log.success("Hook installed β ~/.claude/settings.json");
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const msg = formatError(err);
|
|
78
|
+
if (msg.includes("already installed")) {
|
|
79
|
+
p.log.step("Hook already installed");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
p.log.error(`Hook installation failed: ${msg}`);
|
|
83
|
+
throw new Error(`install hook: ${msg}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function registerChatId(userId) {
|
|
88
|
+
const stateDir = join(homedir(), ".ccbot");
|
|
89
|
+
const stateFile = join(stateDir, "state.json");
|
|
90
|
+
let state = { chat_id: null };
|
|
91
|
+
try {
|
|
92
|
+
const data = readFileSync(stateFile, "utf-8");
|
|
93
|
+
state = JSON.parse(data);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
if (state.chat_id === userId) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
state.chat_id = userId;
|
|
100
|
+
mkdirSync(stateDir, { recursive: true });
|
|
101
|
+
writeFileSync(stateFile, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
102
|
+
p.log.success("Chat ID registered");
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runUninstall(): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { HookInstaller } from "../hook/hook-installer.js";
|
|
6
|
+
import { detectInstallMethod } from "../utils/install-detection.js";
|
|
7
|
+
export function runUninstall() {
|
|
8
|
+
p.intro("ποΈ Uninstalling ccbot");
|
|
9
|
+
removeHook();
|
|
10
|
+
removeConfigDirectory();
|
|
11
|
+
printPostUninstallHint();
|
|
12
|
+
p.outro("ccbot uninstalled");
|
|
13
|
+
}
|
|
14
|
+
function removeHook() {
|
|
15
|
+
try {
|
|
16
|
+
HookInstaller.uninstall();
|
|
17
|
+
p.log.success("Hook removed from ~/.claude/settings.json");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
p.log.warn("No hook found (already removed)");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function removeConfigDirectory() {
|
|
24
|
+
const ccbotDir = join(homedir(), ".ccbot");
|
|
25
|
+
try {
|
|
26
|
+
rmSync(ccbotDir, { recursive: true, force: true });
|
|
27
|
+
p.log.success("Removed ~/.ccbot/ (config, state, hooks)");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
p.log.warn("~/.ccbot/ not found (already removed)");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function printPostUninstallHint() {
|
|
34
|
+
const method = detectInstallMethod();
|
|
35
|
+
if (method === "global") {
|
|
36
|
+
p.log.info("To also remove the package:\n pnpm remove -g ccbot");
|
|
37
|
+
}
|
|
38
|
+
else if (method === "git-clone") {
|
|
39
|
+
p.log.info("To also remove the source:\n rm -rf <ccbot-directory>");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runUpdate(): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { detectInstallMethod, getGitRepoRoot } from "../utils/install-detection.js";
|
|
6
|
+
export function runUpdate() {
|
|
7
|
+
const method = detectInstallMethod();
|
|
8
|
+
switch (method) {
|
|
9
|
+
case "npx":
|
|
10
|
+
p.intro("π¦ ccbot update");
|
|
11
|
+
p.log.step("Installed via npx β always uses latest version, no update needed.");
|
|
12
|
+
p.outro("Already up to date");
|
|
13
|
+
break;
|
|
14
|
+
case "global":
|
|
15
|
+
updateGlobal();
|
|
16
|
+
break;
|
|
17
|
+
case "git-clone":
|
|
18
|
+
updateGitClone();
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function detectGlobalPackageManager() {
|
|
23
|
+
const execPath = process.argv[1] ?? "";
|
|
24
|
+
if (execPath.includes("pnpm"))
|
|
25
|
+
return "pnpm";
|
|
26
|
+
if (execPath.includes("yarn"))
|
|
27
|
+
return "yarn";
|
|
28
|
+
if (execPath.includes("bun"))
|
|
29
|
+
return "bun";
|
|
30
|
+
return "npm";
|
|
31
|
+
}
|
|
32
|
+
function updateGlobal() {
|
|
33
|
+
const pm = detectGlobalPackageManager();
|
|
34
|
+
const pkg = "ccbot";
|
|
35
|
+
p.intro("π¦ ccbot update");
|
|
36
|
+
const s = p.spinner();
|
|
37
|
+
s.start(`Updating via ${pm}...`);
|
|
38
|
+
const cmd = pm === "yarn"
|
|
39
|
+
? `yarn global add ${pkg}`
|
|
40
|
+
: `${pm} install -g ${pkg}@latest`;
|
|
41
|
+
try {
|
|
42
|
+
execSync(cmd, { stdio: "pipe" });
|
|
43
|
+
s.stop("Updated successfully");
|
|
44
|
+
p.outro("Update complete");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
s.stop("Update failed");
|
|
48
|
+
p.log.error(`Try manually: ${cmd}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function updateGitClone() {
|
|
53
|
+
const scriptDir = dirname(process.argv[1] ?? "");
|
|
54
|
+
const repoRoot = getGitRepoRoot(scriptDir);
|
|
55
|
+
if (!repoRoot) {
|
|
56
|
+
p.log.error("Could not find git repo root.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
p.intro("π¦ ccbot update");
|
|
60
|
+
const s = p.spinner();
|
|
61
|
+
try {
|
|
62
|
+
s.start("Pulling latest changes...");
|
|
63
|
+
execSync("git pull", { cwd: repoRoot, stdio: "pipe" });
|
|
64
|
+
s.stop("Pulled latest changes");
|
|
65
|
+
const pm = existsSync(join(repoRoot, "pnpm-lock.yaml"))
|
|
66
|
+
? "pnpm"
|
|
67
|
+
: existsSync(join(repoRoot, "yarn.lock"))
|
|
68
|
+
? "yarn"
|
|
69
|
+
: existsSync(join(repoRoot, "bun.lockb"))
|
|
70
|
+
? "bun"
|
|
71
|
+
: "npm";
|
|
72
|
+
s.start("Installing dependencies...");
|
|
73
|
+
execSync(`${pm} install`, { cwd: repoRoot, stdio: "pipe" });
|
|
74
|
+
s.stop("Dependencies installed");
|
|
75
|
+
s.start("Building...");
|
|
76
|
+
execSync(`${pm} run build`, { cwd: repoRoot, stdio: "pipe" });
|
|
77
|
+
s.stop("Build complete");
|
|
78
|
+
p.outro("Update complete");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
s.stop("Update failed");
|
|
82
|
+
p.log.error("Try manually: git pull && npm install && npm run build");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
telegram_bot_token: string;
|
|
3
|
+
user_id: number;
|
|
4
|
+
hook_port: number;
|
|
5
|
+
hook_secret: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ConfigManager {
|
|
8
|
+
private static readonly CONFIG_DIR;
|
|
9
|
+
private static readonly CONFIG_FILE;
|
|
10
|
+
static load(): Config;
|
|
11
|
+
static save(cfg: Config): void;
|
|
12
|
+
static isOwner(cfg: Config, userId: number): boolean;
|
|
13
|
+
static generateSecret(): string;
|
|
14
|
+
private static validate;
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
export class ConfigManager {
|
|
6
|
+
static CONFIG_DIR = join(homedir(), ".ccbot");
|
|
7
|
+
static CONFIG_FILE = join(ConfigManager.CONFIG_DIR, "config.json");
|
|
8
|
+
static load() {
|
|
9
|
+
let data;
|
|
10
|
+
try {
|
|
11
|
+
data = readFileSync(ConfigManager.CONFIG_FILE, "utf-8");
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
15
|
+
throw new Error("config not found β run 'ccbot setup' first");
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`read config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18
|
+
}
|
|
19
|
+
const raw = JSON.parse(data);
|
|
20
|
+
return ConfigManager.validate(raw);
|
|
21
|
+
}
|
|
22
|
+
static save(cfg) {
|
|
23
|
+
mkdirSync(ConfigManager.CONFIG_DIR, { recursive: true });
|
|
24
|
+
writeFileSync(ConfigManager.CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
25
|
+
}
|
|
26
|
+
static isOwner(cfg, userId) {
|
|
27
|
+
return cfg.user_id === userId;
|
|
28
|
+
}
|
|
29
|
+
static generateSecret() {
|
|
30
|
+
return randomBytes(32).toString("hex");
|
|
31
|
+
}
|
|
32
|
+
static validate(data) {
|
|
33
|
+
if (typeof data !== "object" || data === null) {
|
|
34
|
+
throw new Error("config must be a JSON object");
|
|
35
|
+
}
|
|
36
|
+
const obj = data;
|
|
37
|
+
if (typeof obj.telegram_bot_token !== "string" || !obj.telegram_bot_token.includes(":")) {
|
|
38
|
+
throw new Error("telegram_bot_token must be a string containing ':' β run 'ccbot setup'");
|
|
39
|
+
}
|
|
40
|
+
if (typeof obj.user_id !== "number" || !Number.isInteger(obj.user_id)) {
|
|
41
|
+
throw new Error("user_id must be an integer β run 'ccbot setup'");
|
|
42
|
+
}
|
|
43
|
+
let hookPort = 9377;
|
|
44
|
+
if (obj.hook_port !== undefined) {
|
|
45
|
+
if (typeof obj.hook_port !== "number" || !Number.isInteger(obj.hook_port) || obj.hook_port < 1 || obj.hook_port > 65535) {
|
|
46
|
+
throw new Error("hook_port must be an integer between 1 and 65535");
|
|
47
|
+
}
|
|
48
|
+
hookPort = obj.hook_port;
|
|
49
|
+
}
|
|
50
|
+
let hookSecret;
|
|
51
|
+
if (typeof obj.hook_secret === "string" && obj.hook_secret.length > 0) {
|
|
52
|
+
if (!/^[a-f0-9]+$/i.test(obj.hook_secret)) {
|
|
53
|
+
throw new Error("hook_secret must contain only hex characters (a-f, 0-9)");
|
|
54
|
+
}
|
|
55
|
+
hookSecret = obj.hook_secret;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
hookSecret = ConfigManager.generateSecret();
|
|
59
|
+
}
|
|
60
|
+
const cfg = {
|
|
61
|
+
telegram_bot_token: obj.telegram_bot_token,
|
|
62
|
+
user_id: obj.user_id,
|
|
63
|
+
hook_port: hookPort,
|
|
64
|
+
hook_secret: hookSecret,
|
|
65
|
+
};
|
|
66
|
+
if (typeof obj.hook_secret !== "string" || obj.hook_secret.length === 0) {
|
|
67
|
+
ConfigManager.save(cfg);
|
|
68
|
+
}
|
|
69
|
+
return cfg;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type NotifyFunc = (text: string) => Promise<void>;
|
|
2
|
+
export declare class HookHandler {
|
|
3
|
+
private notify;
|
|
4
|
+
constructor(notify: NotifyFunc);
|
|
5
|
+
handleStopEvent(event: unknown): void;
|
|
6
|
+
private collectGitChanges;
|
|
7
|
+
private parseGitDiffOutput;
|
|
8
|
+
private parsePorcelainOutput;
|
|
9
|
+
}
|