Jordan Miller Cursor commited on
Commit
40dee71
·
1 Parent(s): 1961f74

Load cross-project shared.env before project .env for local dev.

Browse files

Auto-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>

Files changed (6) hide show
  1. .env.example +13 -7
  2. .gitignore +2 -0
  3. README.md +22 -2
  4. backend/app/config.py +47 -41
  5. docker-compose.yml +3 -0
  6. scripts/dev-local.sh +12 -1
.env.example CHANGED
@@ -1,24 +1,30 @@
1
- # HANA API (Neon BrainForge)
 
 
 
 
 
 
 
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 (same as HF Space API_KEY in brainforge-webapp).
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
- # Edit .env with your API keys
 
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 = "https://4090-x1-3.neonaiservices2.com/vllm0"
 
 
 
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 = {"env_file": str(_ENV_FILE), "env_file_encoding": "utf-8"}
 
 
 
 
 
 
 
 
 
 
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 — add your API keys if needed."
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