not-tonight/tools/gen/creds.py
m3ultra 4caa901df2 Four venues, four rooms: the ladder stops being one map with different numbers
The venue ladder has always listed four venues in data/venues.ts, but they
shared ONE floor map and differed only by difficulty knobs — so a promotion you
survived a whole week for looked exactly like the room you had just left.

- venueMap.ts grows a FloorLayout contract (map + props + lights + posts +
  probes + palette) and the newGrid() primitives every room is painted with.
  Voltage stays IN venueMap rather than moving to layouts/, so the new rooms can
  import primitives from it without closing an import cycle.
- Three new rooms in src/scenes/floor/layouts/: The Royal (horseshoe public bar,
  pool room, pokies corner, trough + two cubicles, a beer garden that is plainly
  the nicest room in the pub, and a DJ "corner" that is a folding table because
  this pub never built a booth), Elevate (open-air rooftop — most of the grid is
  sky, the DJ is on an unwalled plinth, you arrive by lift), ROOM (concrete
  warehouse, pillars you path around, central booth, loading-dock smoking area,
  twelve lights in the whole venue).
- Nothing about a room is a module singleton any more. FloorView, sweep and
  FloorDemoScene take the layout; the door picks its street plate by venue id.
- FloorDemoScene's six hardcoded station spots (TAPS_SPOT, DECKS_SPOT, ...) were
  Voltage's tile coordinates. At four venues a hardcoded (47,3) puts the bar
  shift inside The Royal's pool room, so each layout now names its own stations
  and the scene reads them from there.
- tests/floor/layoutInvariants.ts is an executable rulebook: perimeter holds,
  every walkable tile reachable from the entry, anchors on walkable ground,
  posts not sealed (a post on a sealed tile freezes the player for the night —
  that has shipped twice), probes reachable, props on-grid. Proved against
  Voltage BEFORE the new rooms were authored against it.

Dev route #floor:<venueId> boots the floor straight into one room, because
otherwise seeing ROOM means surviving three weeks of the ladder.

Gate: lint clean, build clean, 821 tests passing (was 784).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 19:51:54 +10:00

92 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""creds.py — resolve fleet credentials from on-disk .env files, in process.
The fleet rule is that secrets live in .env files per project and are READ FROM
DISK by whatever needs them; they never travel through a shell, a chat, a doc or
a commit. So this module takes a key name and hands back its value to the
caller's own process, and its failure mode prints the paths it TRIED — never a
value, never a fragment of one.
Candidate paths are ordered cheapest-first (this machine, then the fleet's
shared locations). Every asset script in tools/gen goes through here, which is
why the batch driver runs unmodified on ultra (Cloudflare creds) and on m3ultra
(MODELBEAST creds) without a flag.
"""
from __future__ import annotations
import os
from pathlib import Path
# Every .env on the fleet that has ever legitimately held a generation
# credential. Missing files are skipped silently — most machines have most of
# these missing, and that is normal, not an error.
CANDIDATES = (
"~/Documents/backnforth/.env",
"~/Documents/MODELBEAST/.env",
"~/Documents/MODELBEAST/data/agent.env",
"~/Documents/fluxgod-work/.env",
"~/Documents/not-tonight/.env",
)
_cache: dict[str, str] | None = None
def _load() -> dict[str, str]:
"""Merge every candidate .env, first file to define a key wins."""
global _cache
if _cache is not None:
return _cache
merged: dict[str, str] = {}
for cand in CANDIDATES:
path = Path(os.path.expanduser(cand))
if not path.is_file():
continue
try:
text = path.read_text(errors="ignore")
except OSError:
continue
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
# Tolerate `export FOO=bar` and quoted values — both appear on the fleet.
if key.startswith("export "):
key = key[len("export "):].strip()
value = value.strip().strip('"').strip("'")
if key and value and key not in merged:
merged[key] = value
# A real environment variable beats any file: that is how you override for
# a one-off without editing a shared .env.
for key in ("CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN", "MB_TOKEN"):
if os.environ.get(key):
merged[key] = os.environ[key]
_cache = merged
return merged
def get(key: str, *, required: bool = True) -> str:
"""Value for `key`, or exit with the paths tried (never the values)."""
value = _load().get(key, "")
if value:
return value
if not required:
return ""
tried = "\n ".join(CANDIDATES)
raise SystemExit(
f"credential {key!r} not found on this machine.\n"
f"looked in (plus the process environment):\n {tried}\n"
f"put it in one of those, or run this script on a machine that has it."
)
def have(key: str) -> bool:
return bool(_load().get(key))
if __name__ == "__main__":
# Diagnostic only: which credentials EXIST here. Booleans, never values.
for k in ("CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN", "MB_TOKEN"):
print(f"{k}: {'present' if have(k) else 'MISSING'}")