agent4discord 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.
- package/README.ko.md +134 -0
- package/README.md +170 -0
- package/dist/bot.d.ts +1 -0
- package/dist/bot.js +114 -0
- package/dist/bot.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +44 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +152 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/model.d.ts +5 -0
- package/dist/commands/model.js +65 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/resume.d.ts +6 -0
- package/dist/commands/resume.js +113 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +49 -0
- package/dist/config.js.map +1 -0
- package/dist/formatters/chunker.d.ts +5 -0
- package/dist/formatters/chunker.js +46 -0
- package/dist/formatters/chunker.js.map +1 -0
- package/dist/formatters/embedBuilder.d.ts +28 -0
- package/dist/formatters/embedBuilder.js +32 -0
- package/dist/formatters/embedBuilder.js.map +1 -0
- package/dist/formatters/toolFormatter.d.ts +4 -0
- package/dist/formatters/toolFormatter.js +90 -0
- package/dist/formatters/toolFormatter.js.map +1 -0
- package/dist/guild.d.ts +22 -0
- package/dist/guild.js +41 -0
- package/dist/guild.js.map +1 -0
- package/dist/interactions/directoryBrowser.d.ts +61 -0
- package/dist/interactions/directoryBrowser.js +611 -0
- package/dist/interactions/directoryBrowser.js.map +1 -0
- package/dist/interactions/index.d.ts +5 -0
- package/dist/interactions/index.js +92 -0
- package/dist/interactions/index.js.map +1 -0
- package/dist/interactions/permissionHandler.d.ts +11 -0
- package/dist/interactions/permissionHandler.js +107 -0
- package/dist/interactions/permissionHandler.js.map +1 -0
- package/dist/interactions/sessionControls.d.ts +9 -0
- package/dist/interactions/sessionControls.js +95 -0
- package/dist/interactions/sessionControls.js.map +1 -0
- package/dist/sessions/eventHandler.d.ts +3 -0
- package/dist/sessions/eventHandler.js +209 -0
- package/dist/sessions/eventHandler.js.map +1 -0
- package/dist/sessions/sessionManager.d.ts +29 -0
- package/dist/sessions/sessionManager.js +203 -0
- package/dist/sessions/sessionManager.js.map +1 -0
- package/dist/sessions/sessionStore.d.ts +4 -0
- package/dist/sessions/sessionStore.js +29 -0
- package/dist/sessions/sessionStore.js.map +1 -0
- package/dist/sessions/streamHandler.d.ts +18 -0
- package/dist/sessions/streamHandler.js +119 -0
- package/dist/sessions/streamHandler.js.map +1 -0
- package/dist/sessions/toolProgress.d.ts +14 -0
- package/dist/sessions/toolProgress.js +65 -0
- package/dist/sessions/toolProgress.js.map +1 -0
- package/dist/sessions/usageTracker.d.ts +12 -0
- package/dist/sessions/usageTracker.js +222 -0
- package/dist/sessions/usageTracker.js.map +1 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +101 -0
- package/dist/setup.js.map +1 -0
- package/dist/utils/filesystem.d.ts +11 -0
- package/dist/utils/filesystem.js +26 -0
- package/dist/utils/filesystem.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.js +3 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/plugins.d.ts +6 -0
- package/dist/utils/plugins.js +66 -0
- package/dist/utils/plugins.js.map +1 -0
- package/package.json +45 -0
package/README.ko.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/banner.png" alt="Agent4Discord" width="640">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Discord를 통한 원격 Claude Code 세션</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="README.md">English</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Agent4Discord (A4D)는 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)를 Discord 채널에서 사용할 수 있게 해주는 셀프호스트 Discord 봇입니다. 각 세션은 전용 채널에 매핑되고, 도구 호출은 스레드에 표시되며, 권한 요청은 인터랙티브 버튼으로 나타납니다.
|
|
16
|
+
|
|
17
|
+
**내 PC. 내 봇. 내 Claude Code 세션.**
|
|
18
|
+
|
|
19
|
+
## 동작 방식
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="docs/images/architecture.png" alt="아키텍처" width="640">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
1. 본인의 Discord 봇 토큰으로 PC에서 봇을 실행
|
|
26
|
+
2. `/a4d init`으로 Discord 서버에 채널 구조 생성
|
|
27
|
+
3. 작업 디렉토리를 선택하고 Claude Code 세션 시작
|
|
28
|
+
4. Discord에서 Claude와 대화 — 스트리밍, 도구 호출, 권한 승인 모두 지원
|
|
29
|
+
|
|
30
|
+
## 주요 기능
|
|
31
|
+
|
|
32
|
+
- **디렉토리 브라우저** — 셀렉트 메뉴와 버튼으로 파일시스템 탐색
|
|
33
|
+
- **모델 선택** — 세션 시작 시 opus/sonnet/haiku 선택 (기본값: opus)
|
|
34
|
+
- **실시간 스트리밍** — 텍스트 출력, 생각, 도구 진행률을 라이브 업데이트 임베드로 표시
|
|
35
|
+
- **도구 호출 스레드** — 각 도구 실행이 포맷된 입출력과 함께 개별 스레드로 생성
|
|
36
|
+
- **권한 제어** — 위험한 작업에 Allow/Deny 버튼 (안전한 도구는 자동 허용)
|
|
37
|
+
- **세션 재개** — CLI에서 만든 세션이나 중단된 세션을 `/a4d resume`으로 재개
|
|
38
|
+
- **사용량 트래커** — `#a4d-usage` 채널에서 세션 비용, 토큰, 속도 제한 표시
|
|
39
|
+
- **플러그인 지원** — 설치된 Claude Code 플러그인 (스킬, 훅) 자동 로드
|
|
40
|
+
- **CLI 연동** — CLI와 동일한 JSONL 저장소를 공유하여 세션 호환
|
|
41
|
+
|
|
42
|
+
### 디렉토리 브라우저
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
### 세션 스트리밍
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
### 권한 요청
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## 빠른 시작
|
|
52
|
+
|
|
53
|
+
### 사전 요구사항
|
|
54
|
+
|
|
55
|
+
- **Node.js** >= 20.x
|
|
56
|
+
- **Claude Code** 인증 완료 (`claude login` 또는 `ANTHROPIC_API_KEY`)
|
|
57
|
+
- **Discord 봇 토큰** ([여기서 생성](https://discord.com/developers/applications))
|
|
58
|
+
|
|
59
|
+
### 설정
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx agent4discord@latest --setup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
설정 마법사가 안내합니다:
|
|
66
|
+
1. Discord 봇 토큰 입력
|
|
67
|
+
2. Client ID 입력
|
|
68
|
+
3. Message Content Intent 활성화 확인
|
|
69
|
+
4. 초대 URL 생성 및 브라우저 열기
|
|
70
|
+
|
|
71
|
+
### 실행
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx agent4discord@latest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Discord에서
|
|
78
|
+
|
|
79
|
+
1. 서버에서 `/a4d init` 실행
|
|
80
|
+
2. `#a4d-session`에서 디렉토리 탐색
|
|
81
|
+
3. **Session Start** 클릭, 모델 선택, 대화 시작
|
|
82
|
+
|
|
83
|
+
## 명령어
|
|
84
|
+
|
|
85
|
+
| 명령어 | 설명 |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `/a4d init` | 서버에 A4D 채널 구조 생성 |
|
|
88
|
+
| `/a4d resume` | 현재 채널에서 중단된 세션 재개 |
|
|
89
|
+
| `/a4d model <opus\|sonnet\|haiku>` | 세션 중 모델 변경 |
|
|
90
|
+
|
|
91
|
+
## 채널 구조
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
A4D - General
|
|
95
|
+
├── #a4d-general — 상태 메시지
|
|
96
|
+
├── #a4d-session — 디렉토리 브라우저 & 세션 시작
|
|
97
|
+
└── #a4d-usage — 사용량 & 속도 제한 트래커
|
|
98
|
+
|
|
99
|
+
A4D - Sessions
|
|
100
|
+
├── #a4d-myproject — 활성 세션 채널
|
|
101
|
+
└── #a4d-another — 다른 세션
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 설정 파일
|
|
105
|
+
|
|
106
|
+
`~/.agent4discord/config.json`에 저장됩니다:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"discordToken": "봇-토큰",
|
|
111
|
+
"discordClientId": "클라이언트-ID",
|
|
112
|
+
"claudeModel": "opus",
|
|
113
|
+
"permissionMode": "default",
|
|
114
|
+
"logLevel": "info"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 개발
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git clone https://github.com/raravel/Agent4Discord.git
|
|
122
|
+
cd Agent4Discord
|
|
123
|
+
npm install
|
|
124
|
+
|
|
125
|
+
# 자동 리로드 개발 모드
|
|
126
|
+
npx tsx watch src/cli.ts
|
|
127
|
+
|
|
128
|
+
# 타입 체크
|
|
129
|
+
npx tsc --noEmit
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 라이선스
|
|
133
|
+
|
|
134
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/banner.png" alt="Agent4Discord" width="640">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Remote Claude Code sessions through Discord</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="README.ko.md">한국어</a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
Agent4Discord (A4D) is a self-hosted Discord bot that lets you interact with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) through Discord channels. Each session maps to a dedicated channel, tool calls appear in threads, and permission requests show as interactive buttons.
|
|
16
|
+
|
|
17
|
+
**Your PC. Your bot. Your Claude Code sessions.**
|
|
18
|
+
|
|
19
|
+
## How It Works
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="docs/images/architecture.png" alt="Architecture" width="640">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
1. You run the bot on your PC with your own Discord bot token
|
|
26
|
+
2. `/a4d init` sets up channels in your Discord server
|
|
27
|
+
3. Pick a working directory and start a Claude Code session
|
|
28
|
+
4. Chat with Claude through Discord — streaming, tool calls, and permissions all work
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Directory Browser** — Navigate your filesystem with select menus and buttons
|
|
33
|
+
- **Model Selection** — Choose opus/sonnet/haiku when starting a session (default: opus)
|
|
34
|
+
- **Real-time Streaming** — Live-updating embeds for text output, thinking, and tool progress
|
|
35
|
+
- **Tool Call Threads** — Each tool execution gets its own thread with formatted input/output
|
|
36
|
+
- **Permission Control** — Allow/Deny buttons for dangerous operations (auto-allow for safe tools)
|
|
37
|
+
- **Session Resume** — Resume CLI-created sessions or stopped sessions with `/a4d resume`
|
|
38
|
+
- **Usage Tracker** — `#a4d-usage` channel shows session costs, tokens, and rate limits
|
|
39
|
+
- **Plugin Support** — Auto-loads your installed Claude Code plugins (skills, hooks)
|
|
40
|
+
- **CLI Interop** — Sessions share the same JSONL storage as the CLI
|
|
41
|
+
|
|
42
|
+
### Directory Browser
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
### Session with Streaming
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
### Permission Request
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
### Prerequisites
|
|
54
|
+
|
|
55
|
+
- **Node.js** >= 20.x
|
|
56
|
+
- **Claude Code** authenticated (`claude login` or `ANTHROPIC_API_KEY`)
|
|
57
|
+
- **Discord bot token** ([create one here](https://discord.com/developers/applications))
|
|
58
|
+
|
|
59
|
+
### Setup
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx agent4discord@latest --setup
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The setup wizard will:
|
|
66
|
+
1. Ask for your Discord bot token
|
|
67
|
+
2. Ask for your Client ID
|
|
68
|
+
3. Verify Message Content Intent is enabled
|
|
69
|
+
4. Generate an invite URL and open it in your browser
|
|
70
|
+
|
|
71
|
+
### Run
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx agent4discord@latest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### In Discord
|
|
78
|
+
|
|
79
|
+
1. Run `/a4d init` in your server
|
|
80
|
+
2. Go to `#a4d-session` and browse to a directory
|
|
81
|
+
3. Click **Session Start**, pick a model, and start chatting
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `/a4d init` | Set up A4D channels in your server |
|
|
88
|
+
| `/a4d resume` | Resume a stopped session in the current channel |
|
|
89
|
+
| `/a4d model <opus\|sonnet\|haiku>` | Change model mid-session |
|
|
90
|
+
|
|
91
|
+
## Channel Structure
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
A4D - General
|
|
95
|
+
├── #a4d-general — Status messages
|
|
96
|
+
├── #a4d-session — Directory browser & session start
|
|
97
|
+
└── #a4d-usage — Usage & rate limit tracker
|
|
98
|
+
|
|
99
|
+
A4D - Sessions
|
|
100
|
+
├── #a4d-myproject — Active session channel
|
|
101
|
+
└── #a4d-another — Another session
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
Config is stored at `~/.agent4discord/config.json`:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"discordToken": "your-bot-token",
|
|
111
|
+
"discordClientId": "your-client-id",
|
|
112
|
+
"claudeModel": "opus",
|
|
113
|
+
"permissionMode": "default",
|
|
114
|
+
"logLevel": "info"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git clone https://github.com/raravel/Agent4Discord.git
|
|
122
|
+
cd Agent4Discord
|
|
123
|
+
npm install
|
|
124
|
+
|
|
125
|
+
# Dev mode with auto-reload
|
|
126
|
+
npx tsx watch src/cli.ts
|
|
127
|
+
|
|
128
|
+
# Type check
|
|
129
|
+
npx tsc --noEmit
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Project Structure
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
src/
|
|
136
|
+
├── cli.ts # Entry point
|
|
137
|
+
├── setup.ts # Interactive setup wizard
|
|
138
|
+
├── config.ts # Config loading (~/.agent4discord/)
|
|
139
|
+
├── bot.ts # Discord client & event handlers
|
|
140
|
+
├── guild.ts # Guild config persistence
|
|
141
|
+
├── commands/
|
|
142
|
+
│ ├── index.ts # Slash command registry
|
|
143
|
+
│ ├── init.ts # /a4d init
|
|
144
|
+
│ ├── resume.ts # /a4d resume
|
|
145
|
+
│ └── model.ts # /a4d model
|
|
146
|
+
├── interactions/
|
|
147
|
+
│ ├── index.ts # Interaction router
|
|
148
|
+
│ ├── directoryBrowser.ts # Directory browser UI
|
|
149
|
+
│ ├── sessionControls.ts # Stop/Archive buttons
|
|
150
|
+
│ └── permissionHandler.ts # Allow/Deny/Details buttons
|
|
151
|
+
├── sessions/
|
|
152
|
+
│ ├── sessionManager.ts # SDK query() lifecycle
|
|
153
|
+
│ ├── sessionStore.ts # Session persistence
|
|
154
|
+
│ ├── eventHandler.ts # SDK events → Discord
|
|
155
|
+
│ ├── streamHandler.ts # Streaming text/thinking embeds
|
|
156
|
+
│ ├── toolProgress.ts # Tool execution progress
|
|
157
|
+
│ └── usageTracker.ts # Rate limit & cost tracking
|
|
158
|
+
├── formatters/
|
|
159
|
+
│ ├── embedBuilder.ts # Discord embeds
|
|
160
|
+
│ ├── chunker.ts # Message chunking
|
|
161
|
+
│ └── toolFormatter.ts # Tool-specific formatting
|
|
162
|
+
└── utils/
|
|
163
|
+
├── filesystem.ts # Directory listing
|
|
164
|
+
├── plugins.ts # Plugin auto-loader
|
|
165
|
+
└── logger.ts # Logging
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT
|
package/dist/bot.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startBot(): Promise<void>;
|
package/dist/bot.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Client, GatewayIntentBits, Events, } from 'discord.js';
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
import { commands, registerCommands } from './commands/index.js';
|
|
4
|
+
import { routeInteraction } from './interactions/index.js';
|
|
5
|
+
import { sessionManager } from './sessions/sessionManager.js';
|
|
6
|
+
import { setupEventHandlers, getAndClearTurnThreads } from './sessions/eventHandler.js';
|
|
7
|
+
import { removeSessionFromGuild } from './sessions/sessionStore.js';
|
|
8
|
+
import { setupUsageTracker } from './sessions/usageTracker.js';
|
|
9
|
+
export async function startBot() {
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const client = new Client({
|
|
12
|
+
intents: [
|
|
13
|
+
GatewayIntentBits.Guilds,
|
|
14
|
+
GatewayIntentBits.GuildMessages,
|
|
15
|
+
GatewayIntentBits.MessageContent,
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
// --- Ready ---
|
|
19
|
+
client.on(Events.ClientReady, async (readyClient) => {
|
|
20
|
+
console.log(`Ready! Logged in as ${readyClient.user.tag} (${readyClient.guilds.cache.size} guilds)`);
|
|
21
|
+
// Auto-register slash commands for all guilds
|
|
22
|
+
for (const [guildId] of readyClient.guilds.cache) {
|
|
23
|
+
try {
|
|
24
|
+
await registerCommands(config.discordClientId, config.discordToken, guildId);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
console.error(`Failed to register commands for guild ${guildId}:`, err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// --- Guild join ---
|
|
32
|
+
client.on(Events.GuildCreate, async (guild) => {
|
|
33
|
+
console.log(`Joined guild: ${guild.name} (${guild.id})`);
|
|
34
|
+
try {
|
|
35
|
+
await registerCommands(config.discordClientId, config.discordToken, guild.id);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error(`Failed to register commands for guild ${guild.id}:`, err);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// --- Interaction handling ---
|
|
42
|
+
client.on(Events.InteractionCreate, async (interaction) => {
|
|
43
|
+
// Slash commands
|
|
44
|
+
if (interaction.isChatInputCommand()) {
|
|
45
|
+
const handler = commands.get(interaction.commandName);
|
|
46
|
+
if (handler) {
|
|
47
|
+
try {
|
|
48
|
+
await handler(interaction);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error(`Error handling command "${interaction.commandName}":`, err);
|
|
52
|
+
const reply = { content: 'An error occurred while processing the command.', ephemeral: true };
|
|
53
|
+
if (interaction.replied || interaction.deferred) {
|
|
54
|
+
await interaction.followUp(reply);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await interaction.reply(reply);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Buttons and select menus
|
|
64
|
+
if (interaction.isButton() || interaction.isStringSelectMenu()) {
|
|
65
|
+
try {
|
|
66
|
+
await routeInteraction(interaction);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(`Error handling interaction "${interaction.customId}":`, err);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// --- Message handling ---
|
|
75
|
+
client.on(Events.MessageCreate, async (message) => {
|
|
76
|
+
if (message.author.bot)
|
|
77
|
+
return;
|
|
78
|
+
const session = sessionManager.getSession(message.channelId);
|
|
79
|
+
if (!session)
|
|
80
|
+
return;
|
|
81
|
+
// Archive previous turn's tool threads
|
|
82
|
+
const prevThreadIds = getAndClearTurnThreads(message.channelId);
|
|
83
|
+
for (const threadId of prevThreadIds) {
|
|
84
|
+
const thread = message.guild?.channels.cache.get(threadId);
|
|
85
|
+
if (thread?.isThread()) {
|
|
86
|
+
thread.setArchived(true).catch(() => { });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Add hourglass reaction to indicate processing
|
|
90
|
+
await message.react('\u23f3').catch(() => { });
|
|
91
|
+
try {
|
|
92
|
+
sessionManager.sendMessage(message.channelId, message.content);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error(`[message] Failed to relay message to session:`, err);
|
|
96
|
+
await message.reply('Could not send message to Claude Code session.').catch(() => { });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// --- Channel delete ---
|
|
100
|
+
client.on(Events.ChannelDelete, async (channel) => {
|
|
101
|
+
const session = sessionManager.getSession(channel.id);
|
|
102
|
+
if (!session)
|
|
103
|
+
return;
|
|
104
|
+
console.log(`[cleanup] Channel ${channel.id} deleted, cleaning up session ${session.sessionId} (guild: ${session.guildId})`);
|
|
105
|
+
sessionManager.removeSession(channel.id);
|
|
106
|
+
removeSessionFromGuild(session.guildId, channel.id);
|
|
107
|
+
});
|
|
108
|
+
// Wire up SDK event -> Discord message handlers
|
|
109
|
+
setupEventHandlers(client);
|
|
110
|
+
setupUsageTracker(client);
|
|
111
|
+
// Start
|
|
112
|
+
await client.login(config.discordToken);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=bot.js.map
|
package/dist/bot.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
const { values } = parseArgs({
|
|
4
|
+
options: {
|
|
5
|
+
setup: { type: 'boolean', default: false },
|
|
6
|
+
version: { type: 'boolean', default: false },
|
|
7
|
+
help: { type: 'boolean', default: false },
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
if (values.version) {
|
|
11
|
+
const { default: pkg } = await import('../package.json', { with: { type: 'json' } });
|
|
12
|
+
console.log(pkg.version);
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (values.help) {
|
|
16
|
+
console.log('Usage: agent4discord [--setup] [--version] [--help]');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
if (values.setup) {
|
|
20
|
+
const { runSetup } = await import('./setup.js');
|
|
21
|
+
await runSetup();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const { startBot } = await import('./bot.js');
|
|
25
|
+
await startBot();
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE;QACP,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAC1C,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QAC5C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;KAC1C;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,QAAQ,EAAE,CAAC;AACnB,CAAC;KAAM,CAAC;IACN,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,QAAQ,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChatInputCommandInteraction } from 'discord.js';
|
|
2
|
+
/** Handler function type for slash commands. */
|
|
3
|
+
export type CommandHandler = (interaction: ChatInputCommandInteraction) => Promise<void>;
|
|
4
|
+
/** Map of command names to their handler functions. */
|
|
5
|
+
export declare const commands: Map<string, CommandHandler>;
|
|
6
|
+
/**
|
|
7
|
+
* Register slash commands for a specific guild using the Discord REST API.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerCommands(clientId: string, token: string, guildId: string): Promise<void>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { SlashCommandBuilder, PermissionFlagsBits, } from 'discord.js';
|
|
2
|
+
import { REST, Routes } from 'discord.js';
|
|
3
|
+
import { handleInit } from './init.js';
|
|
4
|
+
import { handleResume } from './resume.js';
|
|
5
|
+
import { handleModel } from './model.js';
|
|
6
|
+
/** The /a4d slash command definition. */
|
|
7
|
+
const a4dCommand = new SlashCommandBuilder()
|
|
8
|
+
.setName('a4d')
|
|
9
|
+
.setDescription('Agent4Discord commands')
|
|
10
|
+
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
|
|
11
|
+
.addSubcommand((sub) => sub.setName('init').setDescription('Initialize Agent4Discord in this server'))
|
|
12
|
+
.addSubcommand((sub) => sub.setName('resume').setDescription('Resume a stopped session in this channel'))
|
|
13
|
+
.addSubcommand((sub) => sub.setName('model')
|
|
14
|
+
.setDescription('Change the model for this session')
|
|
15
|
+
.addStringOption((opt) => opt.setName('model')
|
|
16
|
+
.setDescription('Model to use')
|
|
17
|
+
.setRequired(true)
|
|
18
|
+
.addChoices({ name: 'Opus 4.6 (most capable)', value: 'opus' }, { name: 'Sonnet 4.6 (fast)', value: 'sonnet' }, { name: 'Haiku 4.5 (fastest)', value: 'haiku' })));
|
|
19
|
+
/** Map of command names to their handler functions. */
|
|
20
|
+
export const commands = new Map();
|
|
21
|
+
// Register the /a4d command handler with subcommand routing
|
|
22
|
+
commands.set('a4d', async (interaction) => {
|
|
23
|
+
const subcommand = interaction.options.getSubcommand();
|
|
24
|
+
if (subcommand === 'init') {
|
|
25
|
+
await handleInit(interaction);
|
|
26
|
+
}
|
|
27
|
+
else if (subcommand === 'resume') {
|
|
28
|
+
await handleResume(interaction);
|
|
29
|
+
}
|
|
30
|
+
else if (subcommand === 'model') {
|
|
31
|
+
await handleModel(interaction);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Register slash commands for a specific guild using the Discord REST API.
|
|
36
|
+
*/
|
|
37
|
+
export async function registerCommands(clientId, token, guildId) {
|
|
38
|
+
const rest = new REST({ version: '10' }).setToken(token);
|
|
39
|
+
const commandData = [a4dCommand.toJSON()];
|
|
40
|
+
await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
|
|
41
|
+
body: commandData,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAKzC,yCAAyC;AACzC,MAAM,UAAU,GAAG,IAAI,mBAAmB,EAAE;KACzC,OAAO,CAAC,KAAK,CAAC;KACd,cAAc,CAAC,wBAAwB,CAAC;KACxC,2BAA2B,CAAC,mBAAmB,CAAC,aAAa,CAAC;KAC9D,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CACrB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,yCAAyC,CAAC,CAC9E;KACA,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CACrB,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,cAAc,CAAC,0CAA0C,CAAC,CACjF;KACA,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CACrB,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;KACjB,cAAc,CAAC,mCAAmC,CAAC;KACnD,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CACvB,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;KACjB,cAAc,CAAC,cAAc,CAAC;KAC9B,WAAW,CAAC,IAAI,CAAC;KACjB,UAAU,CACT,EAAE,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE,MAAM,EAAE,EAClD,EAAE,IAAI,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,EAC9C,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,CAChD,CACJ,CACJ,CAAC;AAEJ,uDAAuD;AACvD,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;AAE1D,4DAA4D;AAC5D,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,WAAwC,EAAE,EAAE;IACrE,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;IAEvD,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;QAC1B,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;SAAM,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;SAAM,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAClC,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;AACH,CAAC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,KAAa,EACb,OAAe;IAEf,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAE1C,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,wBAAwB,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE;QACjE,IAAI,EAAE,WAAW;KAClB,CAAC,CAAC;AACL,CAAC"}
|