# Javis Bot

Ubuntu 데스크톱(VNC) 위에서 도는 **디스코드 네이티브 음성 비서**입니다.
[isair/jarvis](https://github.com/isair/jarvis)의 성숙한 AI "두뇌"(메모리·툴·답변엔진·STT/TTS)를 그대로 쓰면서,
입출력 인터페이스를 로컬 마이크/스피커에서 **디스코드 음성 + 화면 방송**으로 바꾼 하이브리드 구성입니다.

- 🎙️ 디스코드 음성 채널에서 말로 대화 (음성 입력 → 두뇌 → 음성 출력)
- 🖥️ VNC 화면을 디스코드로 송출해서 같이 보기 (셀프봇 실시간 / noVNC / 스크린샷 선택)
- ⌨️ `/자비스` 슬래시 명령으로 호출 — 호출한 사람이 음성 채널에 있으면 그 채널로 접속
- 🔒 모든 슬래시 명령 응답은 **호출한 사람만 보이는 ephemeral** 메시지
- 🧠 크롬/웹 제어, 메모리, MCP 툴 등 jarvis의 기능 유지

> 언어 선택 근거(Python 유지 vs 재작성)는 [docs/language-comparison.md](docs/language-comparison.md) 참고.
> VNC + XFCE 호스트 셋업은 [docs/vnc-xfce-setup.md](docs/vnc-xfce-setup.md) 참고.
> 원본 jarvis README는 [docs/UPSTREAM-README.md](docs/UPSTREAM-README.md)에 보존했습니다.

---

## 아키텍처 (하이브리드)

```
Discord  ──voice / video / slash──▶  bot/      (Node + bun, discord.js)
                                       │  HTTP(localhost)
                                       ▼
                                     bridge/    (Python, Flask)
                                       │  in-process import
                                       ▼
                                     src/jarvis (기존 두뇌: STT·답변엔진·메모리·툴·TTS)
```

- **bot/** — 디스코드 관련 전부. 슬래시 명령, 음성 송수신, VNC 화면 송출. AI 로직 없음.
- **bridge/** — 얇은 HTTP 서비스. 음성(WAV) → 텍스트(STT) → 두뇌(답변) → 음성(TTS).
- **src/jarvis** — 원본 jarvis 두뇌. 거의 손대지 않음. (PyQt 데스크톱 GUI/단축키 받아쓰기는 이 배포에선 사용하지 않음.)

왜 이렇게? 디스코드 봇은 정책상 영상(Go Live)을 송출할 수 없고, 봇 영상 송출이 되는 라이브러리는 Node 전용 + 셀프봇만 가능합니다. 반면 jarvis 두뇌는 검증된 Python 39k줄입니다. 그래서 영상이 가능한 Node로 인터페이스만 새로 짜고 두뇌는 Python 그대로 두는 하이브리드가 비용/위험 대비 최선입니다.

---

## 요구 사항

Docker로 돌리면(권장) 호스트에는 Docker + (GPU 쓸 경우) NVIDIA 드라이버만 있으면 되고, Python/bun/Ollama/ffmpeg/Whisper/Piper는 전부 컨테이너 안에 포함됩니다.

OS별 호스트 준비물:

| | Linux (Ubuntu 등) | Windows 11 |
|---|---|---|
| 컨테이너 런타임 | Docker Engine (CDI 지원, Docker 25+) | Docker Desktop + WSL2 백엔드 |
| GPU 가속(선택) | `nvidia-container-toolkit` + `nvidia-ctk cdi generate` | NVIDIA 드라이버 + Docker Desktop GPU(WSL2) 활성화 |
| GPU 넣는 compose | `docker-compose.gpu-linux.yml` | `docker-compose.gpu-windows.yml` |

- 디스코드 **봇** 토큰 1개 (음성/슬래시) — 또는 (셀프봇 송출 사용 시) 디스코드 **버너 유저** 토큰 1개
- (도커 없이 수동 실행 시에만) Python 3.11+, [bun](https://bun.sh), Ollama, `ffmpeg`를 호스트에 직접 설치 — 아래 "수동" 절 참고

> VNC 데스크톱 호스트를 직접 구성하는 경우(도커 미사용)는 `docs/vnc-xfce-setup.md` 참고. 도커 실행에서는 VNC+XFCE가 컨테이너 안에 이미 들어 있습니다.

---

## 실행 — Docker (권장)

환경 설정 없이 통째로 컨테이너에서 돌립니다. VNC 데스크톱 + 크롬 + Python 브릿지 + Node 봇이 한 컨테이너(`javis`)에, LLM 백엔드(Ollama)가 별도 컨테이너에 뜹니다. **올리기만 하면 Ollama 모델까지 자동으로** 받아집니다.

베이스 `docker-compose.yml`에는 GPU 설정이 없습니다(이식성 유지). GPU는 OS에 맞는 override 파일을 같이 얹어서 켭니다. **돌리는 OS에 따라 명령이 다릅니다:**

```bash
# ── Linux (Ubuntu 등, nvidia-container-toolkit + CDI) ──
docker compose -f docker-compose.yml -f docker-compose.gpu-linux.yml up -d --build

# ── Windows 11 (Docker Desktop + WSL2 + NVIDIA) ──
docker compose -f docker-compose.yml -f docker-compose.gpu-windows.yml up -d --build

# ── GPU 없이 (CPU 전용 호스트) ──
#   .env 에 WHISPER_DEVICE=cpu 를 넣고 베이스만 사용
docker compose up -d --build
```

매번 `-f`를 치기 싫으면 `.env`에 한 줄 넣어두면 그냥 `docker compose up -d`로 됩니다(override가 자동 적용):

```bash
# Linux / macOS  (구분자 = 콜론 ":")
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml
# Windows 11     (구분자 = 세미콜론 ";" — 콜론은 드라이브 문자 C: 와 충돌)
COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml
```

> ⚠️ `COMPOSE_FILE`의 파일 구분자는 OS마다 다릅니다: Linux/macOS는 `:`, Windows는 `;`. Windows에서 `:`를 쓰면 Docker가 전체를 파일 하나 이름으로 읽어 `... The system cannot find the file specified` 에러가 납니다. 헷갈리면 `COMPOSE_FILE`을 비워두고 실행 시 직접 지정하세요: `docker compose -f docker-compose.yml -f docker-compose.gpu-windows.yml up -d --build`.

> Linux와 Windows는 GPU를 컨테이너에 넣는 방식이 달라서 override 파일이 갈립니다. Linux는 CDI(`devices: nvidia.com/gpu=all`), Windows(Docker Desktop)는 Compose의 `deploy.resources.reservations.devices`(`driver: nvidia`)를 씁니다. 호스트 사전 준비는 아래 "GPU 가속" 절 참고.

`docker compose up` 한 번이면 자동으로:
- Ollama 서버가 뜨고, `ollama-init`이 채팅/보조(의도·라우팅)/임베딩 모델을 **자동 pull** (보조 모델 `OLLAMA_INTENT_MODEL`은 기본 `qwen2.5:3b`로, 큰 채팅 모델은 답변에만 쓰고 내부 분류 호출은 이 작은 모델이 처리)
- VNC+XFCE 데스크톱 + 크롬 + Python 브릿지가 기동
- Whisper STT 모델 / Piper TTS 음성 자동 다운로드(볼륨에 캐시)

화면 보기: VNC 뷰어 → `localhost:5901` (비밀번호 = `.env`의 `VNC_PASSWORD`, 기본 `javis123`) 또는 브라우저 → `http://localhost:6080/vnc.html`.
로그: `docker compose logs -f javis`.

### 디스코드 토큰은 마지막에

토큰 없이도 위의 모든 게 정상 동작합니다(봇만 대기). 준비되면 `.env`를 만들어 토큰을 채웁니다.

기본 모드는 **유저봇(selfbot)** 입니다. 음성 참여와 화면 송출(Go Live)을 한 유저 계정으로 처리하며, Discord가 일반 봇 계정에는 Go Live를 허용하지 않기 때문에 이 방식이 기본입니다.

```bash
cp .env.example .env          # DISCORD_SELFBOT_TOKEN(버너 계정) + DISCORD_VOICE_CHANNEL_ID 채우기
docker compose up -d          # 유저봇이 로그인해 지정 음성채널에 자동 참여
```

유저봇은 슬래시 명령을 쓸 수 없으므로 텍스트로 제어합니다: 음성 채널에서 `!자비스 join` / `!자비스 leave`. `DISCORD_VOICE_CHANNEL_ID`를 채워두면 시작 시 자동 참여합니다.

> ⚠️ 유저봇은 Discord ToS 위반이며 계정 정지 위험이 있습니다. 반드시 일회용 **버너 계정** 토큰만 사용하세요. 자세한 주의사항은 아래 "셀프봇 주의" 절을 참고하세요.

일반 봇(슬래시 명령 `/자비스`)으로 돌리려면 `DISCORD_BOT_TOKEN` / `DISCORD_APP_ID` / `DISCORD_GUILD_ID`를 채우세요. 다만 일반 봇은 화면 송출(Go Live)을 할 수 없습니다. `DISCORD_BOT_TOKEN`이 비어 있고 `DISCORD_SELFBOT_TOKEN`이 있으면 자동으로 유저봇 모드로 동작합니다. (`OLLAMA_CHAT_MODEL` 등 모델을 바꾸려면 `.env`에서 지정 후 `docker compose up -d`.)

### GPU 가속 (OS별)

LLM(Ollama)과 Whisper STT가 GPU에서 돕니다(env 기본 `WHISPER_DEVICE=cuda`). TTS는 기본값이 Edge TTS(온라인 한국어 음성)라 GPU를 쓰지 않습니다. NVIDIA Blackwell(sm_120, 예: RTX 5050/5070Ti)에서 검증: 컨테이너 내 torch cu128 CUDA 동작, Ollama GPU 오프로드, faster-whisper float16 모두 확인.

GPU는 위 "실행 — Docker"의 OS별 override 파일로 켜집니다. 호스트 사전 준비는 OS마다 다릅니다:

**Linux (Ubuntu 등) — CDI 방식, 1회:**

```bash
# nvidia-container-toolkit 설치 후 CDI 스펙 생성 (Docker 25+ CDI, 데몬 재시작 불필요)
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
docker run --rm --device nvidia.com/gpu=all ubuntu nvidia-smi -L   # GPU 보이면 OK
```

`docker-compose.gpu-linux.yml`이 두 컨테이너에 `devices: ["nvidia.com/gpu=all"]`(CDI)로 GPU를 넣습니다.

**Windows 11 — Docker Desktop + WSL2:**

- 최신 NVIDIA 게임/스튜디오 드라이버 설치(별도 CUDA 툴킷 불필요).
- Docker Desktop → Settings → Resources → WSL Integration 활성화(WSL2 백엔드). 최신 Docker Desktop은 WSL2에서 GPU를 자동 노출합니다.
- 확인: PowerShell에서 `docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi`.
- `docker-compose.gpu-windows.yml`이 `deploy.resources.reservations.devices`(`driver: nvidia`, `count: all`)로 GPU를 넣습니다.

**공통:**

- 모델: 베이스 compose 기본은 `qwen2.5:3b`(8GB VRAM에서 도구호출 안정적). 더 무겁게(`qwen2.5:7b`, `qwen3:8b` 등) 쓰려면 `.env`의 `OLLAMA_CHAT_MODEL` 변경.
- **GPU가 없거나 인식 실패 시 자동으로 CPU 폴백**(Whisper)하므로 안전합니다. 명시적으로 CPU만 쓰려면 override 파일 없이 베이스만 올리고 `.env`에 `WHISPER_DEVICE=cpu`를 두세요.

- 데이터(메모리 DB), Whisper 캐시, Piper 음성은 named volume에 영속됩니다.
- 셀프봇 영상 송출 의존성은 이미지에 기본 포함하지 않습니다. 쓰려면 컨테이너에서 `cd /app/bot && bun add discord.js-selfbot-v13 @dank074/discord-video-stream` 후 재시작(또는 Dockerfile에 추가).

---

## 실행 — 수동(도커 없이)

도커 없이 호스트에서 직접 돌릴 때는 OS별로 venv 활성화·ffmpeg 설치·실행 스크립트가 다릅니다.

**Linux / macOS:**

```bash
# 1) 환경 변수
cp .env.example .env          # DISCORD_BOT_TOKEN / DISCORD_APP_ID / DISCORD_GUILD_ID 등 채우기

# 2) Python 두뇌 + 브릿지 의존성
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt           # jarvis 두뇌
pip install flask                          # 브릿지(없으면)

# 3) 디스코드 봇 의존성 (bun)
cd bot && bun install && cd ..

# 4) 한 번에 실행 (브릿지 + 봇)
./scripts/dev.sh
#    또는 따로: ./scripts/start_bridge.sh  /  ./scripts/start_bot.sh
```

- `ffmpeg`: Ubuntu `sudo apt install ffmpeg`, macOS `brew install ffmpeg`.

**Windows 11 (PowerShell):**

```powershell
# 1) 환경 변수
copy .env.example .env        # 같은 키들 채우기

# 2) Python 두뇌 + 브릿지 의존성  (venv 활성화 경로가 다름)
py -3 -m venv .venv; .\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
pip install flask

# 3) 디스코드 봇 의존성 (bun — Windows 네이티브 또는 WSL2)
cd bot; bun install; cd ..

# 4) 실행: .sh 스크립트는 bash 전용이라 Windows에서는 두 프로세스를 따로 띄웁니다
#    (PowerShell 창 2개, 또는 WSL2에서 위 Linux 절차 그대로 사용 권장)
python -m bridge.server          # 창 1: 브릿지
cd bot; bun run register; bun run start   # 창 2: (일반 봇이면) 슬래시 등록 후 봇 기동
```

- `ffmpeg`: `winget install Gyan.FFmpeg` 또는 `choco install ffmpeg` 후 PATH 확인.
- `scripts/*.sh`(dev/start_bridge/start_bot)는 bash 스크립트라 순수 Windows에선 동작하지 않습니다. 가장 간단한 길은 **WSL2 안에서 위 Linux 절차를 그대로** 쓰는 것입니다(도커도 WSL2 백엔드와 동일).

봇이 뜨면 디스코드에서 `/자비스 join` 으로 음성 채널에 부르세요.

---

## 슬래시 명령 (`/자비스`)

| 명령 | 동작 |
|---|---|
| `/자비스 join` | 호출자가 있는 음성 채널에 접속해 듣기 시작 |
| `/자비스 leave` | 음성 채널에서 나감 |
| `/자비스 ask 질문:<내용>` | 텍스트로 질문하고 답을 받음 |
| `/자비스 stream` | VNC 화면을 디스코드에 송출 시작 |
| `/자비스 stop` | 송출 중단 |
| `/자비스 status` | 브릿지 두뇌/세션/송출 상태 확인 |

모든 응답은 **호출한 사람에게만** 보입니다(ephemeral).

---

## VNC 화면 송출 백엔드 (`STREAM_BACKEND`)

`.env`에서 교체 가능합니다. 코드 변경 없이 위험/방식만 바꿉니다.

| 값 | 방식 | 실시간 | 디스코드 native | 밴 위험 |
|---|---|---|---|---|
| `selfbot` (기본) | 버너 유저 계정으로 Go Live 실시간 송출 | ✅ | ✅ | ⚠️ ToS 위반·정지 위험 |
| `novnc` | noVNC 브라우저 링크 공유 | ✅ | ❌ | 없음 |
| `screenshot` | N초마다 채널에 스크린샷 업로드 | ❌ | ❌ | 없음 |
| `none` | 비활성화 | — | — | — |

### 셀프봇(selfbot) 주의

- 디스코드 봇은 영상 송출이 불가능해, 실시간 화면 방송은 **유저 계정 토큰(셀프봇)** 으로만 됩니다.
- 이는 Discord ToS 위반이며 계정이 영구 정지될 수 있습니다.
- 반드시 **버너(일회용) 계정**을 만들어 그 토큰을 `DISCORD_SELFBOT_TOKEN`에 넣고, 본계정은 절대 쓰지 마세요.
- 영상 송출만 조용히 하는 패턴은 상대적으로 위험이 낮지만 0은 아닙니다.
- 의존성(네이티브)은 선택 설치입니다:
  ```bash
  cd bot && bun add discord.js-selfbot-v13 @dank074/discord-video-stream
  ```

---

## 환경 변수

전체 목록과 설명은 [`.env.example`](.env.example)에 있습니다. 핵심:

- `DISCORD_BOT_TOKEN`, `DISCORD_APP_ID`, `DISCORD_GUILD_ID` — 봇/길드
- `BRIDGE_URL` — 봇이 호출할 브릿지 주소 (기본 `http://127.0.0.1:8765`)
- `STREAM_BACKEND`, `DISCORD_SELFBOT_TOKEN`, `NOVNC_URL` — 화면 송출
- `VNC_DISPLAY=:1`, `VNC_RESOLUTION`, `VNC_FRAMERATE`, `VNC_BITRATE_KBPS` — 캡처
- `WHISPER_DEVICE/COMPUTE_TYPE` — GPU 호스트면 `cuda`/`float16`, CPU 전용이면 `cpu`(GPU 자체는 OS별 override compose 파일로 켬)
- `OLLAMA_CHAT_MODEL` — 두뇌 LLM (기본 `qwen2.5:3b`)
- `COMPOSE_FILE` — OS별 GPU override를 매번 `-f`로 안 치고 자동 적용 (위 "실행 — Docker" 참고)
- `output_language` — 출력 언어 고정(비우면 사용자 언어). 설정 웹 UI(`/settings`)에서 바꾸면 env 기본값보다 우선하며 컨테이너 재생성 후에도 유지됩니다.
- `AGENTS_DIR` — 운영자 지시문 폴더(기본 `/app/agents`, `./agents`가 read-only로 마운트됨). 아래 "운영자 지시문" 참고.

---

## 운영자 지시문 (`agents/*.md`)

`agents/` 폴더에 마크다운 파일을 넣으면 그 내용이 어시스턴트의 메인 답변 시스템 프롬프트 뒤에 그대로 추가됩니다. 페르소나(집사 성격)는 그대로 두고 규칙·말투·금칙어 등을 덧붙일 때 쓰세요.

- `agents/` 안의 모든 `*.md`를 **파일명 순서**로 이어 붙입니다. 순서를 정하려면 `00-tone.md`, `10-rules.md`처럼 숫자 접두사를 쓰세요.
- **매 답변마다 다시 읽습니다.** 파일을 저장하면 다음 발화부터 바로 반영되며, 재빌드/재시작이 필요 없습니다(폴더가 read-only로 마운트됨).
- 폴더가 없거나 비어 있으면 아무 일도 일어나지 않습니다(fail-open).
- `agents/example.md.sample`을 `rules.md` 등 `*.md`로 복사해서 시작하세요. `.sample` 파일은 로드되지 않습니다.

---

## 현재 상태 / 남은 작업

이 레포는 동작하는 **스캐폴드**입니다. 구조·명령·송출 백엔드·브릿지 연동은 완성되어 있고, 실제 토큰/모델/VNC 디스플레이를 붙여 런타임 검증이 필요한 부분이 남아 있습니다.

- [ ] 실제 디스코드 봇/버너 토큰으로 음성 송수신 end-to-end 검증
- [ ] faster-whisper(CUDA) + Piper 모델로 STT/TTS 실측
- [ ] 셀프봇 영상 송출 라이브러리 버전별 API 실연결(현재 v6 API 기준 작성)
- [ ] Ollama 모델 다운로드 및 두뇌 응답 품질 점검

---

## 크레딧

- 두뇌: [isair/jarvis](https://github.com/isair/jarvis) (라이선스는 [LICENSE](LICENSE) 참고)
- 디스코드 음성: [discord.js](https://discord.js.org) / [@discordjs/voice](https://github.com/discordjs/voice)
- 영상 송출: [@dank074/discord-video-stream](https://github.com/Discord-RE/Discord-video-stream)
