# daymug server configuration. Drop this file as `config.yml` next to
# the running binary for zero-env-var setup, or point DAYMUG_CONFIG /
# `--config` at any path you prefer.
#
# YAML is loaded once at startup; there is no hot-reload. Edit and run
# `scripts/build-local.sh restart` to apply changes.

server:
  # Listen address. Use 127.0.0.1:<port> when fronted by a reverse proxy on
  # the same host; switch to :<port> for direct exposure (be sure cookie_secure
  # is set when serving HTTPS).
  addr: "127.0.0.1:8090"

  # Externally-reachable base URL users open the app at, e.g. the domain your
  # reverse proxy serves. Strongly recommended: when set, push notifications
  # (e.g. Bark) carry a deep link so tapping the notification opens the
  # conversation in the phone's browser. If left empty, notifications are still
  # delivered but TAPPING THEM DOES NOTHING (no link is attached). No trailing
  # slash required.
  # public_url: "https://daymug.example.com"

# Note: the implicit "default CLI for new conversations" is derived from
# the first entry in `providers:` below — list the CLI you want as the
# default first. The previous `default_cli_type` (and legacy `cli_type`)
# top-level keys are no longer read; leaving them in an old YAML file is
# harmless but has no effect. A conversation whose provider is "codex"
# rejects /compact and the config-class slash commands (/config
# /permissions /agents /mcp /memory /hooks) with 400 — they have no codex
# equivalent.

# SQLite is hard-coded to <binary_dir>/data/database.db — no YAML knob,
# no env var. `daymug install` lays this out for you.

auth:
  # How long a successful login stays valid. Go duration syntax (e.g.
  # "720h" = 30 days). The cookie max-age tracks this value.
  session_ttl: 720h
  # Set true when serving over HTTPS so the session cookie's Secure flag
  # is set.
  cookie_secure: false
  # Username + password login. Disable when only OIDC should grant access.
  password_login_enabled: true

admin:
  # Usernames listed here are auto-promoted to is_admin=true on every
  # restart. Use this as a recovery hatch if you accidentally demote
  # everyone — drop your username here, restart, and you're an admin again.
  bootstrap_usernames:
    - admin

users:
  # Global root that holds every user's home directory. New human users
  # default to <default_home_root>/<username> as their work directory; an
  # admin can override the per-user path from the user edit form, but the
  # root itself is config-only — the admin UI shows it read-only. Leading
  # `~/` is expanded to the server user's home; the resolved value must be
  # an absolute path. Defaults to ~/.daymug/users when omitted.
  default_home_root: ~/.daymug/users

# Each provider entry is a credential pool the server can attach to a
# user. Operators bind a user to at most one provider per `type` — so the
# same user can have a `claude` provider for Claude conversations, a `codex`
# provider for Codex conversations, and a `mimo` provider for Xiaomi MiMo
# conversations side by side.
#
# Required fields per entry:
#   - name: unique within this file. The string "default" must appear at
#     least once (any type); it is the implicit fallback when a user has
#     no explicit binding for the conversation's CLI type.
#   - type: "claude" | "codex" | "mimo". Determines which backend handles
#     the conversation. `mimo` uses Claude Code against Xiaomi's
#     Anthropic-compatible endpoint.
#   - config_dir: on-disk credentials directory.
#       - For type=claude or type=mimo, an empty value falls back to the
#         OS user's ~/.claude.
#       - For type=codex an explicit path is REQUIRED — the loader rejects
#         an empty value at validation.
#   - models: optional provider-specific chat model list. The first entry
#     is the default for new conversations.
#   - summary_model: optional model for auto-title and /compact summaries.
#
# To add a Claude account:
#   1. Pick a path (anywhere readable by the server user) — say
#      ~/.claude-alt — and `mkdir -p` it.
#   2. Run `CLAUDE_CONFIG_DIR=~/.claude-alt claude /login`,
#      complete the auth flow.
#   3. Add a `providers:` entry with type=claude and config_dir pointing
#      at that path.
#
# To add a Codex account: declare a provider with type=codex and a
# config_dir, then log in one of two ways:
#   - From the admin UI: Settings → Codex login. It runs
#     `codex login --device-auth` server-side under that account's
#     CODEX_HOME and shows you a verification URL + device code to enter
#     in your own browser (no callback to the server is needed).
#   - From a shell: `CODEX_HOME=<config_dir> codex login` (or the
#     equivalent command for your codex CLI version).
#
# To add a Xiaomi MiMo account: declare a provider with type=mimo and set
# Claude Code's Anthropic-compatible env vars under `env:`. Token Plan keys
# normally use https://token-plan-cn.xiaomimimo.com/anthropic; pay-as-you-go
# API keys use https://api.xiaomimimo.com/anthropic.
#
# Legacy YAML keys still accepted by the loader for backward compatibility:
#   - top-level `claude_accounts` (instead of `providers`)
#   - per-entry `claude_config_dir` (instead of `config_dir`)
# Entries loaded through the legacy path are auto-tagged as type=claude.
providers:
  - name: default
    type: claude
    # How many concurrent agent jobs this provider can run. Excess jobs
    # queue up and clients see queue_status WS events with their position.
    max_concurrent: 2
    # Empty / omitted on type=claude = the server user's existing ~/.claude.
    config_dir: ""
    # Extra env vars passed to the child process. Useful for API-key based
    # accounts that bypass the credentials directory entirely
    # (ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY for claude-compatible
    # providers, OPENAI_API_KEY for codex).
    env: {}
  # Optional second account, e.g. for a higher-tier user / separate billing.
  # - name: alt
  #   type: claude
  #   max_concurrent: 1
  #   config_dir: ~/.claude-alt
  # Optional codex account. config_dir is required.
  # - name: default-codex
  #   type: codex
  #   max_concurrent: 1
  #   config_dir: ~/.codex
  #   # Optional: reduce Codex context inflation for accounts used mainly
  #   # for simple prompts or API chat. This disables DayMug's AGENTS.md
  #   # prompt bridge, Codex user config/rules loading, and fresh-session
  #   # persistence. Leave unset for full coding-agent behavior.
  #   env:
  #     DAYMUG_CODEX_MINIMAL_CONTEXT: "1"
  # Optional Xiaomi MiMo account, using Claude Code's Anthropic-compatible
  # environment variables.
  # - name: xiaomi
  #   type: mimo
  #   max_concurrent: 2
  #   config_dir: ~/.claude-mimo
  #   models:
  #     - mimo-v2.5-pro
  #     - mimo-v2.5
  #   summary_model: mimo-v2.5
  #   env:
  #     ANTHROPIC_BASE_URL: https://token-plan-cn.xiaomimimo.com/anthropic
  #     ANTHROPIC_AUTH_TOKEN: MIMO_API_KEY
  #     ANTHROPIC_MODEL: mimo-v2.5-pro
  #     ANTHROPIC_DEFAULT_SONNET_MODEL: mimo-v2.5-pro
  #     ANTHROPIC_DEFAULT_OPUS_MODEL: mimo-v2.5-pro
  #     ANTHROPIC_DEFAULT_HAIKU_MODEL: mimo-v2.5

# Tool whitelist surfaced in the admin user editor. Admins pick a subset
# per user; the runner passes the chosen names as --allowedTools to claude.
#
# Empty allowed_tools on a user row = no restriction (claude default).
tools:
  available:
    - name: Bash
      patterns:
        ["Bash(python:*)", "Bash(node:*)", "Bash(pnpm:*)", "Bash(git:*)"]
    - name: Read
    - name: Edit
    - name: Write
    - name: Glob
    - name: Grep
    - name: WebFetch
  # Pre-checked for new users created via the admin UI.
  default_for_new_user: [Read, Edit, Glob, Grep]

# Per-process isolation around each agent invocation.
#
#   type: noop   — no isolation; the agent inherits the server user's full
#                  filesystem reach (the historical behaviour).
#   type: bwrap  — Linux bubblewrap jail. Users whose sandbox_mode is
#                  "unrestricted" (set per-user from the admin Users page,
#                  individually or in bulk) run unwrapped; everyone else is
#                  confined to their work_dir — other users' files, the
#                  central DayMug DB, and host secrets are simply not mapped
#                  into the namespace. Requires the `bwrap` binary on PATH;
#                  the server refuses to start without it when enabled.
#
# network: keep host network for jailed agents (bwrap only).
# extra_ro_binds / extra_rw_binds: extra host paths every jailed agent may
# read (RO) or read-write (RW) on top of its work_dir, e.g. a shared assets
# directory. Absolute paths.
sandbox:
  enabled: false
  type: noop
  network: true
  extra_ro_binds: []
  extra_rw_binds: []

# Optional Casdoor / generic-OIDC single sign-on. When `enabled: false` (the
# default) every other field below is ignored and only username + password
# login is available. When `enabled: true`, issuer / client_id / client_secret
# / redirect_uri are mandatory and validated at startup; failure to discover
# the issuer at boot is fatal so misconfiguration surfaces immediately.
#
# The flow is Authorization Code with state + nonce. The id_token is verified
# (signature + iss + aud + exp + nonce); userinfo is fetched server-side and
# fed into the access decision plus optional auto-provisioning. The local
# user is matched by email; create the row ahead of time via the admin UI
# (with `auto_provision: false`) or let the first login provision it.
#
# Casdoor application setup:
#   - Token format: JWT-Standard (small id_token, authorization data lives
#     on userinfo)
#   - Grant types: authorization_code + refresh_token
#   - Redirect URLs: must match `redirect_uri` below character-for-character
#   - Scopes: openid + profile + email (required to make groups/roles/
#     permissions show up on userinfo)
#   - Permission / Role / Group: pick whichever maps best to your access
#     control model and put the matching strings in `required_*` below
oidc:
  enabled: false

  # Issuer URL. Discovery hits <issuer>/.well-known/openid-configuration to
  # learn the authorize/token/userinfo/jwks endpoints. Don't hardcode paths.
  issuer: https://idp.example.com

  # Casdoor application credentials. Treat client_secret as a secret —
  # consider injecting through an env-var-templated config or moving config
  # under chmod 0600.
  client_id: ""
  client_secret: ""

  # Where Casdoor sends the browser after authentication. MUST equal the
  # public URL the SPA reaches the server on, plus /api/auth/oidc/callback.
  redirect_uri: https://daymug.example.com/api/auth/oidc/callback

  # OIDC scopes the IdP returns claims for. `profile` is required for
  # Casdoor to populate groups / roles / permissions on the userinfo
  # response — do not drop it.
  scopes: ["openid", "profile", "email"]

  # Claim names. Override only if you've configured Casdoor to put the
  # values somewhere other than the standard claim keys.
  group_claim: groups
  role_claim: roles
  permission_claim: permissions

  # Access policy. Each non-empty list adds a dimension; the user must
  # satisfy ≥1 entry in ≥1 dimension (OR across dimensions). All three
  # empty = any IdP-authenticated user is allowed in (gated only by
  # auto_provision below).
  required_groups: []
  required_roles: []
  required_permissions: []
    # Casdoor "Permission" route, recommended:
    # - daymug

  # Auto-provision a fresh local user the first time an OIDC identity
  # logs in with no matching local row. Username is derived from
  # preferred_username (then email local-part, then sub) with a numeric
  # suffix on collision; work_dir is rooted under default_home_root.
  # Set `false` to keep a closed allowlist — admins must pre-create every
  # user via the admin UI before they can log in.
  auto_provision: true

  # Label rendered on the SSO button on the login page. Matches the IdP
  # name your users will recognise.
  button_label: "Sign in with Casdoor"

# Self-upgrade: the admin UI exposes a "Check for updates" button that
# fetches the release manifest, downloads + sha256-verifies the matching
# binary, atomically swaps it, restarts the service, and auto-rolls back
# if the new binary fails its post-restart health check. The manifest URL
# itself is hard-coded server-side; only deployment-specific knobs live
# here, all optional.
upgrade:
  # systemd unit name to restart after a successful swap.
  service: daymug
  # "user" runs `systemctl --user restart <service>`, "system" drops the
  # --user flag (root systemd). Match your installation.
  service_mode: user
  # URL path the watchdog polls on localhost. The host:port half is
  # auto-derived from the running server's listen address, so only the
  # path goes here. If the path doesn't return 200 within health_timeout,
  # the watchdog restores the previous binary and restarts.
  health_path: "/api/health"
  health_timeout: 60s

# Per-day token usage rollup. The settings page (Settings → Usage)
# aggregates every claude turn into one row per (user, model, day);
# `timezone` controls which midnight the day boundary refers to. IANA
# names ("Asia/Shanghai", "America/New_York", "Europe/Berlin"), or "UTC"
# / "Local". Default is UTC.
#
# Switching the timezone after data exists doesn't migrate older rows:
# they keep the day label they were stamped with. Expect a one-day
# overlap of differently-bucketed rows on the day of the switch.
usage:
  timezone: "Asia/Shanghai"
