# ============================================================================
# Javis Bot — environment configuration
# Copy to `.env` and fill in.  Never commit your real `.env`.
# ============================================================================

# ---------------------------------------------------------------------------
# Discord bot (normal bot account) — voice I/O + slash commands
# ---------------------------------------------------------------------------
# OPTIONAL — leave BLANK to run in userbot/selfbot mode (a single user account
# does voice + broadcast; see DISCORD_SELFBOT_TOKEN below). When this is empty
# and a selfbot token is present, the app runs as a userbot automatically.
# Only fill this in if you specifically want the legacy normal-bot path.
# From https://discord.com/developers/applications  → your app
DISCORD_BOT_TOKEN=
DISCORD_APP_ID=
# The (single) server this bot serves. Guild-scoped commands appear instantly.
DISCORD_GUILD_ID=
# Voice channel used by the stream-test scripts (bot/scripts/stream-test).
DISCORD_VOICE_CHANNEL_ID=
# Optional text channel for posting conversation transcripts (blank = disabled).
DISCORD_TRANSCRIPT_CHANNEL_ID=

# ---------------------------------------------------------------------------
# Brain bridge (Python service in bridge/) — STT + reply engine + TTS
# ---------------------------------------------------------------------------
BRIDGE_URL=http://127.0.0.1:8765
BRIDGE_HOST=127.0.0.1
BRIDGE_PORT=8765
JARVIS_BRAIN_ENABLED=1
JARVIS_TTS_ENABLED=1
# faster-whisper device/compute. GPU by default (RTX 5050 / sm_120, verified).
# Falls back to CPU automatically if no GPU is passed to the container.
WHISPER_DEVICE=cuda
WHISPER_COMPUTE_TYPE=float16
# Optional explicit Piper voice model (.onnx). If empty, the jarvis default is used.
TTS_PIPER_MODEL_PATH=
# TTS engine: "edge" (default) uses Microsoft Edge TTS, a natural Korean neural
# voice. Set to "piper" for the offline English voice. NOTE: edge is ONLINE —
# reply text is sent to Microsoft's servers and needs internet.
TTS_ENGINE=edge
# Edge voice + speaking rate. Rate is a percentage (+45% ≈ 1.45×). Korean voices:
# ko-KR-HyunsuMultilingualNeural (M), ko-KR-InJoonNeural (M), ko-KR-SunHiNeural (F).
EDGE_TTS_VOICE=ko-KR-HyunsuMultilingualNeural
EDGE_TTS_RATE=+45%
# Neural-only by default: if synthesis fails the bridge returns no audio rather
# than speaking Korean through the English Piper voice. Set to 1 to allow the
# Piper fallback.
MELO_FALLBACK_PIPER=0

# ---------------------------------------------------------------------------
# Jarvis brain (Ollama-backed). In Docker these populate the rendered
# config (docker/jarvis-config.template.json). See src/jarvis/config.py.
# ---------------------------------------------------------------------------
# In docker-compose this is overridden to http://ollama:11434 automatically.
OLLAMA_BASE_URL=http://127.0.0.1:11434
# qwen2.5:3b — small non-reasoning instruct model. ~2.4GB, runs 100% on the GPU
# (the 8B offloads ~8% to CPU), warm voice turns ~2-4s vs ~5-7s on 8B. Clean
# Korean on factual/tool replies; can occasionally leak a trailing CJK phrase on
# free-form chit-chat. Swap back to qwen3:8b for the strongest tool-calling.
OLLAMA_CHAT_MODEL=qwen2.5:3b
# Model for the auxiliary small-model calls: intent judge, tool router, weather
# place extraction, query decomposition. These are classification/JSON calls,
# NOT the spoken answer, so a small fast model here cuts 2-3 big-model round
# trips per command without touching answer quality. BLANK uses the stack
# default qwen2.5:3b, which ollama-init pulls automatically. Set it equal to
# OLLAMA_CHAT_MODEL to run everything on one resident model instead (saves VRAM
# at the cost of slower routing when the chat model is large).
# NEVER set this LARGER than OLLAMA_CHAT_MODEL: the auxiliary calls would then
# run on the bigger, slower model and add latency to every command (the exact
# opposite of the split's purpose). Keep it <= the chat model, blank, or equal.
OLLAMA_INTENT_MODEL=
OLLAMA_EMBED_MODEL=nomic-embed-text
WHISPER_MODEL=small

# Lock every reply to one language, e.g. OUTPUT_LANGUAGE=Korean. Leave BLANK to
# keep the default behaviour of replying in whatever language the user wrote in.
# A fixed value also suppresses stray characters from other scripts (e.g. the
# occasional trailing CJK fragment small models leak on free-form chat).
OUTPUT_LANGUAGE=

# Operator instruction folder: every *.md in this dir is appended to the main
# reply LLM's system prompt (filename order), re-read each turn so edits apply
# without a rebuild/restart. ./agents is bind-mounted here read-only; only
# change this to relocate the folder inside the container. See README "운영자 지시문".
AGENTS_DIR=/app/agents

# ---------------------------------------------------------------------------
# Docker desktop (VNC) — used only by the container image
# ---------------------------------------------------------------------------
# Host ports the container publishes the VNC + noVNC servers on. Defaults match
# the compose file (5901 / 6080); override if the host already uses them.
VNC_PORT=5901
NOVNC_PORT=6080
# VNC viewer password (max 8 chars effective). Watch the screen at localhost:5901.
# Also used by the broadcast keepalive: TigerVNC only refreshes its framebuffer
# while a VNC client is attached, so the stream keeps a tiny client connected to
# avoid a choppy (~1.5 fps) capture. Must match the VNC server's password. If
# unset, the keepalive falls back to the obfuscated passwd file (VNC_PASSWD_FILE,
# default ~/.config/tigervnc/passwd).
VNC_PASSWORD=javis123
# VNC_PASSWD_FILE=/home/claude/.config/tigervnc/passwd
# Auto-opened page in the in-container Chrome.
CHROME_START_URL=about:blank

# ---------------------------------------------------------------------------
# Screen-share + browser mode.
#   true  = the bot may go Live (screen-share the VNC desktop) and drive the
#           on-screen browser for real-time info (search / play / read screen).
#   false = no screen share; voice only, real-time info via the Gemini API.
STREAM_BROWSER=true
# Optional: profile dir for browser-based Google search in plain text turns
# (no active broadcast). When set, the search helper opens Chrome against this
# profile instead of a fresh anonymous one. Sign that profile into Google once
# (run a real Chrome with --user-data-dir=<this path> and log in) so Google
# treats later searches as a returning user and does not serve the bot-detection
# page. Leave blank to use an ephemeral headless session (works only where
# Google does not challenge it). Use a DEDICATED dir, not your everyday Chrome
# profile, to avoid the "profile in use" lock while Chrome is open.
CHROME_USER_DATA_DIR=
# Gemini auth for real-time info when STREAM_BROWSER=false.
#   oauth  = use the Gemini CLI with a Google-account login (no API key).
#            Install once: npm i -g @google/gemini-cli ; then run `gemini` and
#            "Sign in with Google". Uses the CLI's built-in web-search grounding.
#            NOTE (2026-06): Google is blocking personal Google accounts on this
#            path ("This client is no longer supported for Gemini Code Assist for
#            individuals"). Workspace/org accounts may still work; personal
#            accounts should use apikey below instead.
#   apikey = REST path; needs GEMINI_API_KEY below
#            (get one at https://aistudio.google.com/app/apikey). Recommended for
#            personal Google accounts now that individual OAuth login is blocked.
# Either way, real-time search fail-opens to DDG/Brave/Wikipedia if Gemini is
# unavailable, so this is optional, not required.
GEMINI_AUTH=oauth
GEMINI_API_KEY=
GEMINI_MODEL=gemini-2.0-flash
# OAuth login source for Docker. The container mounts this into ~/.gemini.
# Default (blank) = ./docker/gemini-oauth (project-local, cross-platform). Seed
# it once: cp -r ~/.gemini/. docker/gemini-oauth/  (copy the whole login state).
# Or point at an existing host login instead, e.g. GEMINI_OAUTH_DIR=~/.gemini
GEMINI_OAUTH_DIR=

# ---------------------------------------------------------------------------
# VNC screen broadcast
#   selfbot    = real live "Go Live" stream (needs a USER/burner token; ToS risk)
#   novnc      = share a noVNC browser link (safe, real-time, not native)
#   screenshot = periodic screenshots to the channel (safe, low fps)
#   none       = disabled
# ---------------------------------------------------------------------------
STREAM_BACKEND=selfbot

# The VNC desktop runs on X display :1 (see docs/vnc-xfce-setup.md)
VNC_DISPLAY=:1
VNC_RESOLUTION=1920x1080
# 1080p60 broadcast. 8 Mbps suits 60fps (YouTube-style 1080p60 sits ~8-12 Mbps);
# drop to 30/4000 for a lighter stream. Max bitrate is 1.5x this value.
VNC_FRAMERATE=60
VNC_BITRATE_KBPS=8000

# --- selfbot backend ---
# A THROWAWAY/burner Discord user account token. NEVER your main account.
# Using a selfbot violates Discord ToS and can get the account banned.
DISCORD_SELFBOT_TOKEN=
# Dedicated burner account for the Go-Live BROADCAST, separate from the
# conversation account above. REQUIRED in userbot mode for the broadcast to
# work: Discord allows only one voice presence per account, so the conversation
# and the broadcast cannot share one account (the broadcaster's voice connection
# never connects). Leave empty in normal-bot mode (the conversation runs on the
# bot account, so the selfbot account is already broadcast-only). Both burner
# accounts must be in the server. Use a second throwaway account, never a main.
DISCORD_STREAM_TOKEN=
# Hardware (NVENC) encode for the stream. 1 = use the GPU (recommended for
# 1080p60), 0 = software x264. Requires an NVIDIA GPU + ffmpeg built with nvenc.
STREAM_HW=1
# Capture desktop audio into the broadcast so the stream has sound. 1 = on,
# 0 = mute. Pulls the PipeWire/Pulse monitor of the default sink; override the
# source with STREAM_AUDIO_SOURCE (e.g. a specific "<sink>.monitor").
STREAM_AUDIO=1
STREAM_AUDIO_SOURCE=@DEFAULT_MONITOR@

# --- novnc backend ---
# e.g. http://192.168.10.9:6080/vnc.html  (websockify --web=/usr/share/novnc 6080 localhost:5901)
NOVNC_URL=

# --- screenshot backend ---
SCREENSHOT_INTERVAL_SEC=5

# ---------------------------------------------------------------------------
# Voice behaviour
# ---------------------------------------------------------------------------
# Silence (ms) that marks the end of an utterance before sending to the brain.
VOICE_SILENCE_MS=800

# ===========================================================================
# Split deployment & cross-platform (Ubuntu + Windows 11)
# ===========================================================================
# JARVIS_ROLE selects what this machine runs (see docker/run-if-role.sh):
#   full     (default) everything in one container
#   browser  ONLY the desktop + Chrome + control-server (driven over the LAN)
#   bot      ONLY the bot + bridge + TTS (drives a REMOTE browser)
JARVIS_ROLE=full

# --- GPU per OS: pick the matching compose override via COMPOSE_FILE ---
# IMPORTANT: the file separator is OS-specific. Linux/macOS use ":" (colon);
# Windows uses ";" (semicolon), because ":" is taken by the drive letter (C:).
# Using the wrong one makes Docker treat the whole string as a single missing
# filename ("...gpu-windows.yml: The system cannot find the file specified").
# Ubuntu / macOS (nvidia-container-toolkit / CDI):
#   COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml
# Windows 11 (Docker Desktop + WSL2 + NVIDIA) — note the ";" separator:
#   COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml
# Browser-only host (no GPU needed): leave COMPOSE_FILE unset (base only).
# Default below is the Linux form; Windows users must change ":" to ";" AND
# swap gpu-linux for gpu-windows. If unsure, comment this out and pass the
# files explicitly: docker compose -f docker-compose.yml -f <gpu-override> ...
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml

# --- Browser HOST (JARVIS_ROLE=browser) — e.g. this LAN machine ---
# Expose Chrome control to the internal network (no auth, internal only):
#   CDP_BIND=0.0.0.0
#   BROWSER_CONTROL_BIND=0.0.0.0
#   CDP_PUBLISH_BIND=0.0.0.0
# Defaults are loopback-only.

# --- BOT host (JARVIS_ROLE=bot) — e.g. your PC driving the remote browser ---
# Point the controlBrowser tool at the browser host's control-server:
#   BROWSER_CONTROL_URL=http://192.168.10.9:8777
# (Leave BROWSER_CONTROL_URL empty on full/browser layouts.)

# --- Models (tune per machine) ---
# OLLAMA_CHAT_MODEL=qwen2.5:7b   # quality (needs ~5GB VRAM + whisper small)
# OLLAMA_CHAT_MODEL=qwen2.5:3b   # speed (fits easily, faster on 8GB GPUs)
# WHISPER_MODEL=small            # small frees VRAM for a bigger LLM; medium=more accurate
# STT_BEAM_SIZE=5                # beam search (5) > greedy (1) for accuracy; lower for speed
# MELO_DEVICE=cuda               # cpu if no GPU on the bot host

# --- Settings web UI (http://localhost:8765/settings on the bot host) ---
# To reach it, expose the bridge to the host loopback:
#   BRIDGE_HOST=0.0.0.0
#   SETTINGS_PUBLISH_BIND=127.0.0.1   # 0.0.0.0 to allow LAN access (no auth)
# Change models / STT / TTS speed / language / LLM instructions live; "적용"
# restarts the bridge + TTS worker so changes take effect.
