Bikin Discord Bot untuk Claude Code
Saya punya kebiasaan kerja yang mungkin agak aneh: saya ingin bisa ngobrol dengan Claude Code langsung dari Discord — bukan buka terminal, bukan buka browser, tapi langsung dari chat yang sudah saya buka sepanjang hari. Jadi saya bikin bot-nya sendiri.
Tulisan ini bukan tutorial step-by-step, tapi lebih ke sharing arsitektur — gimana semua bagiannya nyambung satu sama lain.
Gambaran Besar
Bot-nya satu binary TypeScript yang jalan di host machine (bukan di Docker), karena dia butuh akses ke claude -p CLI yang sudah login di sini. Kalau di-container-ize, session Claude-nya tidak akan terbawa.
Flow dasarnya begini:
User kirim pesan di Discord thread
↓
Bot terima event dari Discord Gateway
↓
Diteruskan ke tmux window (satu window per thread)
↓
Claude Code CLI jalan di sana, output ke file .jsonl
↓
Session monitor polling file itu tiap 2 detik
↓
Output dikirim balik ke Discord thread
Satu tmux Session untuk Semua
Bagian yang paling menarik menurut saya adalah penggunaan tmux sebagai “runtime” untuk Claude.
Semua sesi Claude berjalan dalam satu tmux session bernama claude-sessions. Tiap Discord thread punya satu tmux window sendiri. Jadi kalau ada 3 thread aktif, ada 3 window di tmux itu.
Kenapa tmux? Karena claude -p adalah CLI interaktif — dia butuh terminal. Tmux memberi kita terminal yang bisa kita kontrol secara programatik: bisa kirim keystrokes, bisa baca output-nya, bisa kita tinggal pergi dan balik lagi.
// Buka window baru untuk sesi Discord thread ini
await tmux.newWindow('claude-sessions', windowName, workingDir);
// Kirim pesan user ke Claude
await tmux.sendKeys(windowName, `${userMessage}\n`);
Membaca Output Claude
Claude Code menulis output-nya ke file .jsonl di ~/.claude/projects/{encoded_cwd}/{sessionId}.jsonl. Formatnya satu JSON object per baris, tiap baris adalah satu “event” — bisa teks biasa, tool use, error, dsb.
Session monitor polling file ini tiap 2 detik, membaca baris-baris baru yang belum diproses, lalu mem-format-nya untuk dikirim ke Discord.
Yang agak tricky adalah Discord punya limit 2000 karakter per pesan. Jadi kalau output Claude panjang, bot akan attach output tersebut sebagai file .txt alih-alih kirim sebagai pesan teks.
Tool use pun diformat supaya ringkas:
🔧 _Bash_ `cat package.json`
Slash Commands
Ada beberapa slash command yang tersedia:
| Command | Fungsi |
|---|---|
/session [folder] |
Buka thread baru dengan sesi Claude interaktif |
/new |
Mulai percakapan baru di dalam thread yang sama |
/endsession |
Matikan sesi, lock thread |
/ask <prompt> |
Query satu kali ke AI (tidak interaktif) |
/summary |
Lihat ringkasan history percakapan |
/reset |
Hapus history untuk channel ini |
/sessions |
Lihat semua sesi aktif di guild |
/remote [folder] |
Buka sesi Claude remote-control (dapat URL yang bisa dibuka di browser) |
/session dan /ask punya tujuan yang berbeda. /ask itu ringan — satu pertanyaan, satu jawaban, ada history rolling-nya. /session itu berat — buka thread baru, spawn tmux window, streaming output penuh dari Claude Code termasuk tool use.
Dua Provider AI
Ada abstraksi AiProvider yang pluggable:
claude-cli— menjalankanclaude -pdengan inject conversation history. Ini yang dipakai untuk/ask.command— menjalankan external command/program lain sebagai AI backend.none— no-op, untuk testing.
Untuk sesi interaktif (/session), routing-nya bypass provider ini — langsung ke tmux. Jadi provider hanya relevan untuk command one-shot seperti /ask.
SQLite untuk History
Conversation history untuk /ask disimpan di SQLite. Bot Discord punya database-nya sendiri (DISCORD_DATABASE_PATH), terpisah dari bot Telegram yang juga jalan di host yang sama.
Ada dua tabel utama: messages (rolling N pesan terakhir) dan summary (ringkasan yang di-refresh secara otomatis setelah N pesan baru). Ini supaya context tidak membengkak tanpa batas.
Access Control
Sebelum command apapun dieksekusi, bot cek dua hal:
- Apakah guild ini ada di
DISCORD_ALLOWED_GUILD_IDS? - Apakah user ini ada di
DISCORD_ALLOWED_USER_IDS?
Kalau salah satu tidak lolos, perintah diabaikan tanpa balasan. Bot ini memang private — tidak dimaksudkan untuk publik.
Menjalankannya
cd chat
npm install
npm run dev:discord # development dengan tsx watch
# atau
npm run build && npm run start:discord # production
Env var minimal yang dibutuhkan: DISCORD_BOT_TOKEN dan DISCORD_APPLICATION_ID. Slash commands akan register per-guild kalau DISCORD_ALLOWED_GUILD_IDS di-set (propagasi instan), atau globally kalau tidak di-set (~1 jam).
Hasilnya? Saya sekarang bisa minta Claude baca file, jalankan perintah, atau review kode langsung dari Discord — tanpa pindah window. Cukup ketik di thread, dan Claude merespons seperti biasa.