Files
brain-of-reese/.env.example
T
ducoterra fe55be0c35
Build and Push Containers / build-and-push (push) Successful in 1m50s
feat(brand): configurable app name — BOR_APP_NAME drives /api/config + the frontend brand layer
One env var (BOR_APP_NAME, default "Brain of Reese") now drives the app's
display name everywhere (TODO.md L12 — owner ask: "a way to customize the
name for 'Brain of'. Should be an env var."). The existing app_name setting
is the source of truth (phase locked decision — no new variable, no rename);
with the variable unset the app is byte-identical to before.

Endpoint (A10 public/stateless, no secrets):
  GET /api/config → exactly {app_name, version} (app/api/config.py, the
  health.py pattern; registered before the static mount). Integration tests:
  anonymous 200, default values, a Settings override follows, key set is
  exactly two keys — no other setting may leak in later.

Frontend brand layer (A11 — runtime fetch, static templates stay static):
  assets/brand.js — a CLASSIC script, first on all six pages, so its top
  level runs at parse time: window.BOR_BRAND = "Brain of Reese"
  synchronously (the default renders immediately, no blank flash), then a
  no-store fetch of /api/config applies the name — document.title (global
  replace), every .brand-text (a name starting "Brain of " keeps the bold
  split Brain of <strong>rest</strong>, any other name renders plain; the
  operator-controlled name is HTML-escaped before innerHTML), a TreeWalker
  over text nodes (script/style rejected — page source never rewritten),
  and the aria-label/placeholder/meta-content attributes. Fetch failure
  keeps the default + console.warn (the loadHealth house style).
  app.js (status labels, typing label, elapsed-hint aria, tool labels) and
  document.js (viewer titles) read window.BOR_BRAND at CALL time via
  brand() — a label set after the fetch lands carries the configured name.
  Containerfile: esbuild minify line for brand.js (classic, like markdown.js);
  the phase-33 ?v= cache-busting picks the new asset ref up automatically.

E2E (A16 — one story, one file, isolated): test_configurable_brand.py boots
a SECOND app instance (same DB/mock-LLM/admin-auth env block, port APP_PORT+1,
BOR_APP_NAME="Brain of Testy") — the shared conftest server keeps the
default name so every other suite's title/label assertions stay untouched —
and asserts /api/config on both instances, the index title/brand/greeting/
#messages aria-label, the sources + login page titles, and one pre-token
chat turn (think out loud marker) whose #send-status reads "Brain of Testy
is thinking"; the no-op regression pins the shared server's default bytes.

Docs: .env.example App section + README configuration reference — what it
affects (titles, header brand, status labels, aria text), the default, the
bold-split rendering rule.

Gates: 695 unit+integration passed, app/ coverage 99% (>90%), story E2E
green in isolation (two consecutive runs), brand-string suites (smoke,
shared header, header consistency, chat persistence) green, ruff + pyright
clean.
2026-08-27 02:24:16 -04:00

81 lines
4.3 KiB
Bash

# Brain of Reese — environment configuration
# Copy to `.env` and adjust: cp .env.example .env
# (`.env` is gitignored; never commit secrets.)
# --- App ---
BOR_ENVIRONMENT=development
# BOR_APP_NAME=Brain of Reese # display name on all pages — titles, header brand, status labels, aria text (phase 39)
# BOR_LOG_LEVEL=INFO
# BOR_STATIC_DIR=frontend # dev default; container sets /app/static
# --- Database (matches `podman compose` db service) ---
BOR_DATABASE_URL=postgresql+psycopg://reese:reese@localhost:5432/brain_of_reese
# --- LLM (self-hosted, OpenAI-compatible "aipi") ---
BOR_LLM_BASE_URL=https://aipi.reeseapps.com/v1
BOR_LLM_API_KEY= # falls back to $AIPI_KEY, then "not-needed"
BOR_LLM_CHAT_MODEL=turbo
BOR_LLM_EMBED_MODEL=embed
BOR_LLM_SUMMARY_MODEL=lite # one-shot completions: document summaries (phase 30), KB overview (phase 31)
BOR_EMBEDDING_DIM=768 # verified 2026-08 via scripts/llm_probe.py
BOR_STREAM_THINKING=1 # stream the model's thinking as `thinking` SSE events (0 to suppress)
# --- RAG tuning ---
BOR_TOP_N_DOCS=2
BOR_RELEVANCE_THRESHOLD=0.62 # answer when best cosine >= this OR an FTS hit; else honest deflection
BOR_MAX_OUTPUT_TOKENS=32768 # max answer length in tokens (answers must not be cut off)
BOR_STEERING_MAX_CHARS=8000 # char budget for the <tuning> (steering notes) prompt section
BOR_SUMMARY_MAX_CHARS=12000 # cap on document content sent to the lite summary model (phase 30)
BOR_KB_OVERVIEW_MAX_CHARS=4000 # char budget for the <knowledge_base> prompt section (phase 31)
BOR_OVERVIEW_INPUT_MAX_CHARS=40000 # cap on the document list sent to the lite model for the KB outline (phase 31)
BOR_CHUNK_TARGET_CHARS=2000
BOR_CHUNK_OVERLAP_CHARS=200
BOR_EMBED_BATCH_SIZE=16
# --- Hybrid retrieval (vector + Postgres FTS, RRF-fused) ---
BOR_HYBRID_VECTOR_CANDIDATES=100 # cosine list width for the fusion
BOR_HYBRID_LEXICAL_CANDIDATES=30 # FTS list width for the fusion
BOR_RRF_K=60 # Reciprocal Rank Fusion damping constant
# --- Agent document tools (phase 37: grounded turns may list + read) ---
BOR_AGENT_LIST_CALLS=1 # per-turn list_documents opportunities (0 disables the tool)
BOR_AGENT_READ_CALLS=1 # per-turn read_document opportunities (0 disables the tool)
# --- Import scope (A9 formats; may only narrow, never widen) ---
# BOR_IMPORT_EXTENSIONS=md,markdown,txt,yaml,yml,json,py
# BOR_SUGGESTIONS=["How is my Kubernetes cluster set up?"] # JSON list of onboarding chips
# --- Import sources (git; phase 28, admin-managed since phase 35) ---
# Comma-separated git repo URLs; import_docs clones each (first run) or
# pulls it (subsequent runs) into BOR_SOURCES_DIR/<repo-name>/ and indexes
# the result. Auth via URL (e.g. an https token) or SSH keys.
#
# Phase 35 (owner permission 2026-08-26): the PRIMARY way to manage the
# list is the admin Git sources page (http://localhost:8000/git-sources.html)
# — rows stored in Postgres (git_sources table, migration 0006). This
# variable is the EMPTY-TABLE FALLBACK: it only applies while the admin
# list is empty; once the page has stored any source, this variable is
# ignored (the page is the source of truth). Empty table + empty variable
# + no local rows = no sources (import_docs falls back to --source / the
# old ~/Homelab + ~/Deployments defaults; the UI Sync button fails loudly
# with "no sources configured (git or local)").
#
# Phase 38: this env fallback is GIT-ONLY. Local directory sources (an
# existing, non-git directory on the server) have NO env var — the DB is
# the local-source registry: add them on the same admin page (the
# "Add a local directory" form, kind 'local' + path, migration 0007).
# BOR_GIT_SOURCES=https://github.com/user/homelab.git,https://github.com/user/deployments.git
# BOR_SOURCES_DIR=~/bor-sources
# --- Admin & sign-in (single-admin password login; BOTH required) ---
# The app refuses to start while either is empty (names the missing
# variable(s) — README "Admin & sign-in"). Generate the secret with:
# python -c 'import secrets;print(secrets.token_hex(32))'
BOR_ADMIN_PASSWORD=
BOR_SESSION_SECRET=
# BOR_SESSION_MAX_AGE=43200 # signed-cookie lifetime, seconds (default 12 h, sliding)
# --- Debugging (0/1 — 1 enables attach-on-demand debugpy on port 5678) ---
DEBUGPY=0
# DEBUGPY_PORT=5678