# ---------------------------------------------------------------------------
# PIG — environment
# Copy to .env and fill in. Never commit .env.
# ---------------------------------------------------------------------------

# --- Database ---------------------------------------------------------------
# PIG owns this database exclusively. Do not point it at a database shared with
# another application.
DATABASE_URL=postgres://pig:CHANGEME@localhost:5432/pig
#
# Compose only, and REQUIRED there: docker-compose.yml interpolates it with
# `${POSTGRES_PASSWORD:?…}`, so every compose command — including
# `docker compose config` — fails outright until it is set. It is also half of
# the DATABASE_URL compose builds for the containers, which is why running from
# source needs the line above and running in containers needs this one.
# Generate a fresh one; never reuse another service's.
POSTGRES_PASSWORD=CHANGEME
POSTGRES_USER=pig
POSTGRES_DB=pig

# --- Auth (Supabase) --------------------------------------------------------
# PIG uses Supabase for authentication ONLY. It stores no passwords and issues
# no sessions of its own; it verifies incoming JWTs against the project JWKS.
#
# IMPORTANT: authorization does NOT follow from having a Supabase account.
# A user must also have a row in PIG's `users` table. If this Supabase project
# is shared with another application, that application's users get nothing here
# until they are explicitly invited.
SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
SUPABASE_ANON_KEY=
# Service key is only needed for administrative user provisioning. Omit it and
# PIG runs fine in invite-only mode. Treat it as the most powerful secret here.
SUPABASE_SERVICE_KEY=

# --- Auth: on-premises (OIDC) ---------------------------------------------
# Set PIG_OIDC_ISSUER to authenticate against your own identity provider —
# Okta, Entra, Keycloak, Auth0, Authentik, Google Workspace, anything
# standards-compliant. It TAKES PRECEDENCE over the Supabase values above, so
# an on-prem install can leave those in place.
#
# PIG never sees a password. It verifies the token your provider issued and
# reads two things: a stable subject, and an email. Everything else — teams,
# roles, capabilities — is PIG's own data keyed on that subject, so users are
# provisioned in PIG by invite, not by your directory.
PIG_OIDC_ISSUER=
# Optional. Discovered from the issuer's /.well-known/openid-configuration when
# omitted. Set it to skip discovery entirely on an air-gapped network.
PIG_OIDC_JWKS_URI=
# STRONGLY recommended. Without it, a token your provider issued for ANY other
# application in the same tenant is accepted here as a PIG session.
PIG_OIDC_AUDIENCE=
# Comma-separated, in preference order. Defaults to email,preferred_username,upn
# which covers most providers; Entra sometimes needs upn first.
PIG_OIDC_EMAIL_CLAIMS=

# --- Application ------------------------------------------------------------
PIG_PORT=8920
PIG_PUBLIC_URL=http://localhost:8920
NODE_ENV=development

# --- Learn videos, hosted by PIG ---------------------------------------------
# PIG serves its own Learn videos from disk, as a native <video> — no embed
# host, no iframe, and therefore nothing to add to the proxy's frame-src.
#
# PIG_MEDIA_DIR is where the application READS them. Running from source that
# is a path on this machine, relative to the repository root; in the container
# it is always /app/media and docker-compose sets it for you.
PIG_MEDIA_DIR=./media
#
# PIG_MEDIA_HOST_DIR is the HOST directory docker-compose bind-mounts there,
# read-only. Two names for the two sides of the mount, on purpose. It must
# exist before `compose up` — Docker creates a missing bind source as an empty
# root-owned directory, which serves 404s and cannot be written to without
# sudo. On the deployment host this is normally /opt/pig/media.
PIG_MEDIA_HOST_DIR=./media
#
# Filenames are CONTENT-ADDRESSED — `<slug>.<hash>.mp4` — because the files
# themselves are served without authentication while the listing behind
# /api/learn stays code-gated. The hash is what makes a URL unguessable. See
# deploy/README.md, "Learn videos", for the trade this makes and its cost.

# --- Deployment: which image to run -----------------------------------------
# Leave EMPTY to build from the working tree, which is what a development or
# self-hosted-from-source install wants. Set it to a published tag and
# scripts/deploy.sh pulls instead of building, and compose runs exactly that
# image for both the app and Piggy — never one version of each.
#
# Set automatically by scripts/autodeploy.sh; you only put it here to pin a
# specific release by hand.
#   PIG_IMAGE=git.karti.ai/pig/pig:release-2026-08-13
PIG_IMAGE=
# The loopback port the app is published on. TLS belongs to the proxy in front.
PIG_HOST_PORT=8920

# Comma-separated emails granted platform-admin rights.
# Every address listed here MUST already have an account. An address listed but
# unregistered is a standing offer of admin to whoever claims it first.
PIG_ADMIN_EMAILS=

# Invite code gating self-serve profile creation. Rotate freely.
PIG_INVITE_CODE=

# Encrypts the credentials an admin types into the settings UI — the Notion and
# Google OAuth secrets in particular, which the API refuses to accept without
# it. Base64-encoded 32 bytes, and NOT interchangeable with any other secret
# here:
#
#   openssl rand -base64 32
#
# Rotating it does not re-encrypt what is already stored; anything written
# under the old key has to be entered again.
PIG_SETTINGS_ENCRYPTION_KEY=

# --- Prime Intellect API key ------------------------------------------------
# ONE key, two consumers, and it is worth knowing both before you scope it:
#
#   - the API syncs GPU availability into `inventory_listings` from
#     api.primeintellect.ai;
#   - Piggy calls models on api.pinference.ai, which bills the same account.
#
# Mint it at https://app.primeintellect.ai/dashboard/tokens with the NARROWEST
# scope that works: `Availability -> Read`, plus inference if Piggy is on. PIG
# never provisions infrastructure and must not hold a key that could. Set an
# expiry.
#
# Piggy accepts PIGGY_INFERENCE_API_KEY as an alias for this value, so a .env
# written before Piggy moved onto Prime Inference keeps working untouched. They
# are the same key now; set one of them, not two different ones.
#
# SET IT OR COMMENT IT OUT — do not leave it blank once Piggy is on. Blank is
# harmless to the API, which treats it as absent, but the piggy container is
# handed the empty string and Piggy's config refuses it:
#
#   Invalid Piggy configuration:
#     PRIME_API_KEY: String must contain at least 1 character(s)
#
# ...followed by a crash loop. The same applies in reverse to the alias below.
# Measured, not theorised: an empty line is not an absent one.
PRIME_API_KEY=
PRIME_API_BASE=https://api.primeintellect.ai
# Rate limits are undocumented upstream; the sync backs off empirically.
PRIME_SYNC_ENABLED=false
PRIME_SYNC_INTERVAL_MINUTES=30

# --- Piggy (the in-app agent) ----------------------------------------------
# Piggy is a Prime Agent session — Prime Intellect's own agent harness, run as
# a library inside PIG — holding PIG's CRM tools and NOTHING else. The harness
# is constructed with every built-in tool disabled and an explicit allowlist on
# top, so the model has no shell, no filesystem and no Python; the running tool
# list is compared with the allowlist at session start, and a mismatch is a
# startup failure rather than a surprise.
#
# It drains a leased queue, serves chat on an authenticated internal listener,
# and — new, and the reason the settings below matter — it can WRITE to the CRM.
# Generate one internal token and give the same value to API + Piggy. Never
# publish the Piggy listener or put this token in a URL.
#
# THREE keys turn the agent on, and all three are required together:
#
#   PIGGY_ENABLED=true         the API offers the chat surface
#   PRIME_API_KEY              the model credential (see above; Piggy also
#                              accepts the legacy PIGGY_INFERENCE_API_KEY)
#   PIGGY_INTERNAL_TOKEN       32+ characters, the same value for API and Piggy
#
# (PIGGY_INTERNAL_URL is the fourth thing the API needs, and docker-compose.yml
# already sets it to http://piggy:8931. Only a Piggy running outside Compose
# needs the line below.)
#
# Under Compose that is the whole configuration: scripts/deploy.sh reads
# PIGGY_ENABLED from this file and adds `--profile piggy` to the pull, the
# build, the `up` and the rollback, so the agent ships with the app rather than
# being started by hand and then quietly left on an old image. Everything else
# below has a working default and exists to be lowered.
#
# EVERY Piggy setting is read from this environment ONCE, at Piggy's boot. None
# of it is admin-selectable at runtime: changing a budget, a mode or the DEFAULT
# model means editing this file and restarting the container. The one thing a
# user picks for themselves is which model answers a given conversation, and
# even that is a choice between the five in apps/piggy/src/agent/models.json —
# a file in the image, not a setting here.
PIGGY_ENABLED=false
# The legacy spelling of PRIME_API_KEY, kept as an alias so a deployment that
# predates the harness swap keeps starting. Set PRIME_API_KEY above instead and
# leave this COMMENTED OUT; uncomment it only if that is the name your host
# already has, and then comment PRIME_API_KEY out.
#
# Commented rather than blank, and that is the whole point of the line. A blank
# `PIGGY_INFERENCE_API_KEY=` is passed to the container as the empty string,
# which fails Piggy's minimum-length check *even when PRIME_API_KEY is set
# correctly* — so the agent crash-loops with a message about the key you did
# not use. With neither name set, the error is the honest one:
# "PRIME_API_KEY ... is required."
# PIGGY_INFERENCE_API_KEY=
# Required to turn the agent on. 32 characters minimum; anything shorter is
# refused at boot rather than accepted as weak.
#   openssl rand -hex 32
PIGGY_INTERNAL_TOKEN=
# Where the API reaches the chat server. Under Compose this is set for you to
# http://piggy:8931; set it here only when running Piggy from source.
PIGGY_INTERNAL_URL=http://127.0.0.1:8931

# Model and host for the QUEUE worker, which still calls the endpoint directly.
# Read by both the API (to display) and Piggy (to call).
PIGGY_MODEL=nvidia/nemotron-3-nano-30b-a3b
PIGGY_INFERENCE_BASE=https://api.pinference.ai/api/v1

# --- The agent itself -------------------------------------------------------
# All four have working defaults in apps/piggy/src/config.ts. Uncomment one only
# to change it, and read the note on PIGGY_AGENT_THINKING before you change the
# model — the two are related in a way that is not obvious from the outside.
#
# Which model the agent answers with when a user has expressed no preference in
# the picker. It must be one of the five in apps/piggy/src/agent/models.json:
# anything else is not registered with the harness and is rejected at boot,
# which is deliberate — the alternative is a model that 404s on the first turn.
# PIGGY_AGENT_MODEL=nvidia/nemotron-3-nano-30b-a3b
#
# What Piggy may do to the CRM. `confirm` is the shipped default and the one to
# run in production:
#
#   read_only   the pre-agent behaviour; Piggy answers and never writes
#   confirm     a write is PROPOSED as a card and applied when a person clicks
#   auto        Piggy writes directly, as the signed-in user
#
# Whatever this says, contracts, commitments, allocations and compliance records
# ALWAYS require a click — `auto` does not buy them. Every write runs as the
# calling user's own principal, so Piggy can never reach a record its user
# could not.
# PIGGY_AGENT_MODE=confirm
#
# How hard the model thinks before answering. Leave this alone unless you have
# changed the model, and read this paragraph if you have: it is the single
# setting most likely to make a working deployment look broken.
#
# The harness's own default is `medium`, tuned for a coding agent. On nemotron
# that produced 6,195 output tokens of reasoning and an EMPTY answer — the turn
# hit its token ceiling mid-thought and came back with finish_reason `length`.
# `low` was worse. `off` maps, for that model, to the endpoint's
# `reasoning_effort: none` and answered the same question correctly in 149
# output tokens.
#
# The mapping is PER MODEL and lives in `thinkingLevelMap` in
# apps/piggy/src/agent/models.json. A model with no entry (deepseek, opus,
# gpt-5.6) sends no reasoning parameter at `off` and gets the endpoint's own
# default, which may be verbose. So: empty answers, exhausted budgets and
# surprising bills after a model change are this setting, not a broken agent.
# PIGGY_AGENT_THINKING=off       # off | minimal | low | medium | high | xhigh | max
#
# Output tokens one agent turn may spend, reasoning included. Clamped down to
# the chosen model's own ceiling, so raising it cannot ask for more than the
# endpoint will return.
# PIGGY_AGENT_MAX_TOKENS=4096
#
# Where the harness keeps its state — the models.json it reads, and anything
# else it writes. docker-compose.yml pins it to /var/lib/piggy-agent, a
# directory the image creates owned by the unprivileged runtime user, and there
# is no reason to set it here for a Compose deployment.
#
# If you do set it, on a bare-metal install: it MUST NOT be the checkout or any
# directory holding code. The harness discovers extensions, skills and context
# files from its cwd, and Piggy points the harness's cwd here. The default is
# ~/.pig/piggy-agent for exactly that reason.
# PIGGY_AGENT_DIR=/var/lib/piggy-agent

PIGGY_CHAT_HOST=127.0.0.1
PIGGY_CHAT_PORT=8931
# Only containers on a private network need this; never combine it with a
# published Piggy port. Compose sets it to true for the container, because the
# API calls Piggy across the Compose network.
PIGGY_CHAT_ALLOW_NON_LOOPBACK=false

# Tuning. COMMENTED OUT ON PURPOSE, and worth understanding before you
# uncomment one: an empty line here is not the same as an absent one. Compose
# passes `PIGGY_MAX_TOKENS=` through as the empty string, which coerces to 0 and
# fails Piggy's positive-integer check at boot. Leave a setting commented to get
# the default from apps/piggy/src/config.ts; give it a value or nothing at all.
#
# PIGGY_LEASE_SECONDS=300                 # queue lease, renewed at half the interval
# PIGGY_POLL_INTERVAL_MS=2000             # how often an idle worker looks for a task
# PIGGY_MAX_TOKENS=1024                   # per queued task
# PIGGY_CHAT_MAX_TOKENS=2048              # per interactive answer; tools return tables
# PIGGY_MAX_TURNS=4                       # model calls per chat turn, tool round trips included
# PIGGY_WORKER_ID=                        # defaults to hostname:pid; only set it if you run two
#
# Reasoning is off. The default model thinks aloud when asked to, reasoning
# tokens bill like any other, and the chat panel is on every page — so the
# volume is set by how often people type. Raise it to make the UI's reasoning
# panel reachable while chasing a wrong figure, not in normal operation.
# PIGGY_REASONING_EFFORT=none             # none | low | medium | high
#
# Model price in CENTS PER MILLION TOKENS, which is what makes the recorded cost
# of a run exact integer arithmetic. These are the published prices of the
# default model and must be changed with it: a stale price is worse than none,
# because it still looks like a measurement.
# PIGGY_PRICE_INPUT_CENTS_PER_MTOK=5
# PIGGY_PRICE_OUTPUT_CENTS_PER_MTOK=20

# --- Deployment: the release poller -----------------------------------------
# Only relevant on a host running scripts/autodeploy.sh. These belong in
# /etc/pig/autodeploy.env (read by the systemd unit), not here — they are
# listed here so the whole deployment surface is in one file to read.
#
# The registry credential is NOT an environment variable. It is a file, mode
# 0600, holding a pull-only token and nothing else:
#
#   /etc/pig/registry-token
#
# Mint it in Gitea as a token with `read:package` scope ONLY. A token that can
# write packages, or push to the repository, defeats the point: the reason CI
# cannot deploy to production is that no build-side credential should be able
# to change what production runs, and a write-capable token here reintroduces
# exactly that from the other end.
#
# PIG_REGISTRY_USER=pig-deploy                  # the Gitea user that owns the token
# PIG_REGISTRY=git.karti.ai
# PIG_IMAGE_REPO=pig/pig                        # Gitea lowercases the owner
# PIG_REGISTRY_TOKEN_FILE=/etc/pig/registry-token
# PIG_REPO_DIR=/opt/pig
# PIG_RELEASE_TAG_PREFIX=release-
#
# The public origin deploy.sh checks AFTER the container is healthy, to catch a
# proxy that is answering 200 with an empty body. Defaults to PIG_PUBLIC_URL
# above, then to the production origin.
# PIG_DEPLOY_PUBLIC_URL=https://primeintellectgrowth.com
# A string the real application always renders. Change it only if index.html's
# mount point changes.
# PIG_DEPLOY_PUBLIC_MARKER=<div id="root">

# --- Slack ------------------------------------------------------------------
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_APP_TOKEN=

# --- Buzz (https://github.com/block/buzz) -----------------------------------
# Buzz agents reach PIG through the MCP server, so no PIG-specific credential is
# required. These are only for PIG pushing notifications into a Buzz relay.
BUZZ_RELAY_URL=
NOTION_CLIENT_ID=
NOTION_CLIENT_SECRET=
NOTION_REDIRECT_URI=http://localhost:8920/api/imports/notion/oauth/callback
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Must use the PIG_PUBLIC_URL origin and exact /oauth/google/callback path.
GOOGLE_REDIRECT_URI=http://localhost:8920/oauth/google/callback
BUZZ_PRIVATE_KEY=
# Optional NIP-OA owner attestation JSON for an agent identity.
BUZZ_AUTH_TAG=
