Spaces:
Sleeping
Sleeping
Jordan Miller Cursor commited on
Commit ·
40dee71
1
Parent(s): 1961f74
Load cross-project shared.env before project .env for local dev.
Browse filesAuto-detect secrets at ~/.secrets/shared.env or ~/Downloads/shared.env so API keys stay outside the repo while docker-compose and dev-local.sh can opt in via SHARED_ENV_FILE.
Co-authored-by: Cursor <cursoragent@cursor.com>
- .env.example +13 -7
- .gitignore +2 -0
- README.md +22 -2
- backend/app/config.py +47 -41
- docker-compose.yml +3 -0
- scripts/dev-local.sh +12 -1
.env.example
CHANGED
|
@@ -1,24 +1,30 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
HANA_BASE_URL=https://hana.neonaialpha.com
|
| 3 |
HANA_USERNAME=guest
|
| 4 |
HANA_PASSWORD=password
|
| 5 |
-
# BrainForge/Security (4090-x1-3): OpenAI-compatible API key for direct vLLM
|
| 6 |
-
# NOT used for HANA /auth/login — see NeonGeckoCom/brainforge-webapp docker_overlay/config/config.yaml
|
| 7 |
HANA_KLATCHAT_PASSWORD=sk_klatchat2024
|
| 8 |
-
# Direct vLLM host (default matches demo 4090-x1-3)
|
| 9 |
# NEON_SECURITY_VLLM_BASE_URL=https://4090-x1-3.neonaiservices2.com/vllm0
|
| 10 |
|
| 11 |
-
# Neon vLLM direct access
|
| 12 |
VLLM_API_KEY=sk_n30nn30n
|
| 13 |
|
| 14 |
-
# Comparison providers
|
| 15 |
FIREWORKS_API_KEY=your-fireworks-api-key-here
|
| 16 |
TOGETHER_API_KEY=your-together-api-key-here
|
| 17 |
OPENAI_API_KEY=your-openai-api-key-here
|
| 18 |
GEMINI_API_KEY=your-gemini-api-key-here
|
|
|
|
| 19 |
|
| 20 |
# Orchestrator LLM (used to monitor conversation endings)
|
| 21 |
-
# Defaults to gpt-4o-mini via OpenAI if OPENAI_API_KEY is set
|
| 22 |
ORCHESTRATOR_MODEL=gpt-4o-mini
|
| 23 |
|
| 24 |
# App
|
|
|
|
| 1 |
+
# Project-local overrides. API keys and shared Neon credentials can live in
|
| 2 |
+
# a cross-project shared.env instead (see README). Values here override shared.env.
|
| 3 |
+
|
| 4 |
+
# Optional: explicit path to shared secrets (otherwise auto-detected at
|
| 5 |
+
# ~/.secrets/shared.env or ~/Downloads/shared.env)
|
| 6 |
+
# SHARED_ENV_PATH=/Users/you/Downloads/shared.env
|
| 7 |
+
|
| 8 |
+
# HANA API (Neon BrainForge) — usually provided by shared.env
|
| 9 |
HANA_BASE_URL=https://hana.neonaialpha.com
|
| 10 |
HANA_USERNAME=guest
|
| 11 |
HANA_PASSWORD=password
|
| 12 |
+
# BrainForge/Security (4090-x1-3): OpenAI-compatible API key for direct vLLM
|
|
|
|
| 13 |
HANA_KLATCHAT_PASSWORD=sk_klatchat2024
|
| 14 |
+
# Direct vLLM host (default matches demo 4090-x1-3; shared.env may use VLLM_BASE_URL)
|
| 15 |
# NEON_SECURITY_VLLM_BASE_URL=https://4090-x1-3.neonaiservices2.com/vllm0
|
| 16 |
|
| 17 |
+
# Neon vLLM direct access — usually provided by shared.env
|
| 18 |
VLLM_API_KEY=sk_n30nn30n
|
| 19 |
|
| 20 |
+
# Comparison providers — usually provided by shared.env
|
| 21 |
FIREWORKS_API_KEY=your-fireworks-api-key-here
|
| 22 |
TOGETHER_API_KEY=your-together-api-key-here
|
| 23 |
OPENAI_API_KEY=your-openai-api-key-here
|
| 24 |
GEMINI_API_KEY=your-gemini-api-key-here
|
| 25 |
+
MISTRAL_API_KEY=your-mistral-api-key-here
|
| 26 |
|
| 27 |
# Orchestrator LLM (used to monitor conversation endings)
|
|
|
|
| 28 |
ORCHESTRATOR_MODEL=gpt-4o-mini
|
| 29 |
|
| 30 |
# App
|
.gitignore
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
__pycache__/
|
| 2 |
*.pyc
|
| 3 |
.env
|
|
|
|
|
|
|
| 4 |
node_modules/
|
| 5 |
frontend/build/
|
| 6 |
.DS_Store
|
|
|
|
| 1 |
__pycache__/
|
| 2 |
*.pyc
|
| 3 |
.env
|
| 4 |
+
shared.env
|
| 5 |
+
**/.secrets/
|
| 6 |
node_modules/
|
| 7 |
frontend/build/
|
| 8 |
.DS_Store
|
README.md
CHANGED
|
@@ -67,10 +67,29 @@ displayed, or fed back to the orchestrator/summarizer/Credential builder.
|
|
| 67 |
|
| 68 |
## Quick Start (local development)
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
```bash
|
|
|
|
| 71 |
cp .env.example .env
|
| 72 |
-
# Edit .env with your API keys
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
# Backend
|
| 75 |
cd backend
|
| 76 |
pip install -r requirements.txt
|
|
@@ -86,7 +105,8 @@ npm start
|
|
| 86 |
|
| 87 |
```bash
|
| 88 |
cp .env.example .env
|
| 89 |
-
#
|
|
|
|
| 90 |
docker compose up --build
|
| 91 |
# Open http://localhost:7860
|
| 92 |
```
|
|
|
|
| 67 |
|
| 68 |
## Quick Start (local development)
|
| 69 |
|
| 70 |
+
### Shared secrets (`shared.env`)
|
| 71 |
+
|
| 72 |
+
This project supports a cross-project secrets file (same pattern as other Neon
|
| 73 |
+
Cursor repos). Place API keys in `shared.env` **outside the repo** — never
|
| 74 |
+
commit it. The backend loads it automatically before the project `.env`:
|
| 75 |
+
|
| 76 |
+
1. `SHARED_ENV_PATH` or `SHARED_ENV_FILE` if set in the shell
|
| 77 |
+
2. `~/.secrets/shared.env`
|
| 78 |
+
3. `~/Downloads/shared.env`
|
| 79 |
+
|
| 80 |
+
Project `.env` overrides any duplicate keys from `shared.env`.
|
| 81 |
+
|
| 82 |
```bash
|
| 83 |
+
# Optional: only project-specific overrides (orchestrator model, CORS, etc.)
|
| 84 |
cp .env.example .env
|
|
|
|
| 85 |
|
| 86 |
+
# Or use the dev script (auto-detects shared.env + starts both servers)
|
| 87 |
+
./scripts/dev-local.sh
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
Manual start:
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
# Backend
|
| 94 |
cd backend
|
| 95 |
pip install -r requirements.txt
|
|
|
|
| 105 |
|
| 106 |
```bash
|
| 107 |
cp .env.example .env
|
| 108 |
+
# Optional: point Docker at your shared secrets file
|
| 109 |
+
export SHARED_ENV_FILE="$HOME/Downloads/shared.env"
|
| 110 |
docker compose up --build
|
| 111 |
# Open http://localhost:7860
|
| 112 |
```
|
backend/app/config.py
CHANGED
|
@@ -1,14 +1,44 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
| 3 |
from pathlib import Path
|
| 4 |
|
| 5 |
-
from pydantic import AliasChoices, Field
|
| 6 |
-
from pydantic_settings import BaseSettings
|
| 7 |
|
| 8 |
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
| 9 |
_ENV_FILE = _PROJECT_ROOT / ".env"
|
| 10 |
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
class Settings(BaseSettings):
|
| 13 |
hana_base_url: str = "https://hana.neonaialpha.com"
|
| 14 |
hana_username: str = "guest"
|
|
@@ -21,7 +51,10 @@ class Settings(BaseSettings):
|
|
| 21 |
validation_alias=AliasChoices("HANA_KLATCHAT_PASSWORD", "HANA_PASSWORD_KLATCHAT"),
|
| 22 |
)
|
| 23 |
# Direct vLLM base (no /v1); matches brainforge-webapp docker config 4090-x1-3 host.
|
| 24 |
-
neon_security_vllm_base_url: str =
|
|
|
|
|
|
|
|
|
|
| 25 |
# Comma-separated model_id values to merge via get_personas when get_models omits them (needs HANA access)
|
| 26 |
hana_neon_model_supplement_ids: str = "BrainForge/Security@2026.03.18"
|
| 27 |
|
|
@@ -44,7 +77,17 @@ class Settings(BaseSettings):
|
|
| 44 |
|
| 45 |
cors_origins: str = "http://localhost:3000,http://localhost:3001,http://localhost:3002"
|
| 46 |
|
| 47 |
-
model_config =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
@property
|
| 50 |
def cors_origin_list(self) -> list[str]:
|
|
@@ -218,40 +261,3 @@ class Settings(BaseSettings):
|
|
| 218 |
|
| 219 |
|
| 220 |
settings = Settings()
|
| 221 |
-
|
| 222 |
-
# region agent log
|
| 223 |
-
def _agent_log_settings_env() -> None:
|
| 224 |
-
import json
|
| 225 |
-
import time
|
| 226 |
-
|
| 227 |
-
_path = r"c:\Users\dream\CCAI-Demo-FEAT_Config\debug-c86901.log"
|
| 228 |
-
try:
|
| 229 |
-
raw = _ENV_FILE.read_text(encoding="utf-8") if _ENV_FILE.is_file() else ""
|
| 230 |
-
data = {
|
| 231 |
-
"app": "LLMChats3",
|
| 232 |
-
"env_file": str(_ENV_FILE),
|
| 233 |
-
"env_file_exists": _ENV_FILE.is_file(),
|
| 234 |
-
"dotenv_line_HANA_KLATCHAT_PASSWORD": "HANA_KLATCHAT_PASSWORD" in raw,
|
| 235 |
-
"dotenv_line_HANA_PASSWORD_KLATCHAT": "HANA_PASSWORD_KLATCHAT" in raw,
|
| 236 |
-
"settings_hana_password_klatchat_nonempty": bool((settings.hana_password_klatchat or "").strip()),
|
| 237 |
-
}
|
| 238 |
-
with open(_path, "a", encoding="utf-8") as f:
|
| 239 |
-
f.write(
|
| 240 |
-
json.dumps(
|
| 241 |
-
{
|
| 242 |
-
"sessionId": "c86901",
|
| 243 |
-
"hypothesisId": "H1",
|
| 244 |
-
"location": "LLMChats3/config.py:settings",
|
| 245 |
-
"message": "env_binding",
|
| 246 |
-
"data": data,
|
| 247 |
-
"timestamp": int(time.time() * 1000),
|
| 248 |
-
}
|
| 249 |
-
)
|
| 250 |
-
+ "\n"
|
| 251 |
-
)
|
| 252 |
-
except Exception:
|
| 253 |
-
pass
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
_agent_log_settings_env()
|
| 257 |
-
# endregion
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import os
|
| 4 |
from pathlib import Path
|
| 5 |
|
| 6 |
+
from pydantic import AliasChoices, Field, field_validator
|
| 7 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 8 |
|
| 9 |
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
| 10 |
_ENV_FILE = _PROJECT_ROOT / ".env"
|
| 11 |
|
| 12 |
|
| 13 |
+
def _shared_env_candidates() -> list[Path]:
|
| 14 |
+
"""Locations for the cross-project shared.env (never committed)."""
|
| 15 |
+
explicit = (os.getenv("SHARED_ENV_PATH") or os.getenv("SHARED_ENV_FILE") or "").strip()
|
| 16 |
+
if explicit:
|
| 17 |
+
return [Path(explicit).expanduser()]
|
| 18 |
+
return [
|
| 19 |
+
Path.home() / ".secrets" / "shared.env",
|
| 20 |
+
Path.home() / "Downloads" / "shared.env",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _resolve_shared_env() -> Path | None:
|
| 25 |
+
for candidate in _shared_env_candidates():
|
| 26 |
+
if candidate.is_file():
|
| 27 |
+
return candidate
|
| 28 |
+
return None
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _settings_env_files() -> tuple[str, ...]:
|
| 32 |
+
"""Shared secrets first; project .env overrides."""
|
| 33 |
+
files: list[str] = []
|
| 34 |
+
shared = _resolve_shared_env()
|
| 35 |
+
if shared:
|
| 36 |
+
files.append(str(shared))
|
| 37 |
+
if _ENV_FILE.is_file():
|
| 38 |
+
files.append(str(_ENV_FILE))
|
| 39 |
+
return tuple(files)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
class Settings(BaseSettings):
|
| 43 |
hana_base_url: str = "https://hana.neonaialpha.com"
|
| 44 |
hana_username: str = "guest"
|
|
|
|
| 51 |
validation_alias=AliasChoices("HANA_KLATCHAT_PASSWORD", "HANA_PASSWORD_KLATCHAT"),
|
| 52 |
)
|
| 53 |
# Direct vLLM base (no /v1); matches brainforge-webapp docker config 4090-x1-3 host.
|
| 54 |
+
neon_security_vllm_base_url: str = Field(
|
| 55 |
+
default="https://4090-x1-3.neonaiservices2.com/vllm0",
|
| 56 |
+
validation_alias=AliasChoices("NEON_SECURITY_VLLM_BASE_URL", "VLLM_BASE_URL"),
|
| 57 |
+
)
|
| 58 |
# Comma-separated model_id values to merge via get_personas when get_models omits them (needs HANA access)
|
| 59 |
hana_neon_model_supplement_ids: str = "BrainForge/Security@2026.03.18"
|
| 60 |
|
|
|
|
| 77 |
|
| 78 |
cors_origins: str = "http://localhost:3000,http://localhost:3001,http://localhost:3002"
|
| 79 |
|
| 80 |
+
model_config = SettingsConfigDict(
|
| 81 |
+
env_file=_settings_env_files(),
|
| 82 |
+
env_file_encoding="utf-8",
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
@field_validator("neon_security_vllm_base_url", mode="before")
|
| 86 |
+
@classmethod
|
| 87 |
+
def _strip_vllm_v1_suffix(cls, value: object) -> object:
|
| 88 |
+
if isinstance(value, str):
|
| 89 |
+
return value.rstrip("/").removesuffix("/v1")
|
| 90 |
+
return value
|
| 91 |
|
| 92 |
@property
|
| 93 |
def cors_origin_list(self) -> list[str]:
|
|
|
|
| 261 |
|
| 262 |
|
| 263 |
settings = Settings()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker-compose.yml
CHANGED
|
@@ -7,6 +7,9 @@ services:
|
|
| 7 |
# or the environment so local devs can sidestep port conflicts.
|
| 8 |
- "${CCAI_HOST_PORT:-7860}:7860"
|
| 9 |
env_file:
|
|
|
|
|
|
|
|
|
|
| 10 |
- .env
|
| 11 |
environment:
|
| 12 |
CORS_ORIGINS: "http://localhost:7860,http://localhost:3000"
|
|
|
|
| 7 |
# or the environment so local devs can sidestep port conflicts.
|
| 8 |
- "${CCAI_HOST_PORT:-7860}:7860"
|
| 9 |
env_file:
|
| 10 |
+
# Optional cross-project secrets file (set SHARED_ENV_FILE in shell).
|
| 11 |
+
- path: ${SHARED_ENV_FILE}
|
| 12 |
+
required: false
|
| 13 |
- .env
|
| 14 |
environment:
|
| 15 |
CORS_ORIGINS: "http://localhost:7860,http://localhost:3000"
|
scripts/dev-local.sh
CHANGED
|
@@ -8,9 +8,20 @@ echo "Starting CCAI-Vibe-Demo local dev (hot reload enabled)"
|
|
| 8 |
echo "Project root: $ROOT"
|
| 9 |
echo ""
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
if [[ ! -f "$ROOT/.env" ]]; then
|
| 12 |
cp "$ROOT/.env.example" "$ROOT/.env"
|
| 13 |
-
echo "Created .env from .env.example —
|
| 14 |
fi
|
| 15 |
|
| 16 |
if [[ ! -f "$ROOT/frontend/.env.development" ]]; then
|
|
|
|
| 8 |
echo "Project root: $ROOT"
|
| 9 |
echo ""
|
| 10 |
|
| 11 |
+
# Cross-project secrets (shared.env) — loaded by backend/app/config.py before .env
|
| 12 |
+
if [[ -n "${SHARED_ENV_PATH:-}" && -f "${SHARED_ENV_PATH}" ]]; then
|
| 13 |
+
echo "Using shared secrets: $SHARED_ENV_PATH"
|
| 14 |
+
elif [[ -f "$HOME/.secrets/shared.env" ]]; then
|
| 15 |
+
export SHARED_ENV_PATH="$HOME/.secrets/shared.env"
|
| 16 |
+
echo "Using shared secrets: $SHARED_ENV_PATH"
|
| 17 |
+
elif [[ -f "$HOME/Downloads/shared.env" ]]; then
|
| 18 |
+
export SHARED_ENV_PATH="$HOME/Downloads/shared.env"
|
| 19 |
+
echo "Using shared secrets: $SHARED_ENV_PATH"
|
| 20 |
+
fi
|
| 21 |
+
|
| 22 |
if [[ ! -f "$ROOT/.env" ]]; then
|
| 23 |
cp "$ROOT/.env.example" "$ROOT/.env"
|
| 24 |
+
echo "Created .env from .env.example — project overrides only; API keys can live in shared.env."
|
| 25 |
fi
|
| 26 |
|
| 27 |
if [[ ! -f "$ROOT/frontend/.env.development" ]]; then
|