From f40651b34d26d64a85abb5197e80badd937de395 Mon Sep 17 00:00:00 2001 From: Gene Johnson Date: Thu, 19 Mar 2026 13:11:24 -0700 Subject: [PATCH] feat: Enable and configure figment mode via new CLI flags, update documentation, and improve Cairo library detection on macOS. --- README.md | 20 +++++++++----------- engine/app.py | 8 ++++++++ engine/config.py | 2 ++ engine/figment_render.py | 12 ++++++++++++ engine/scroll.py | 16 +++++++++------- 5 files changed, 40 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1e1d9f3..062998d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ python3 mainline.py # news stream python3 mainline.py --poetry # literary consciousness mode python3 mainline.py -p # same python3 mainline.py --firehose # dense rapid-fire headline mode +python3 mainline.py --figment # enable periodic SVG glyph overlays +python3 mainline.py --figment-interval 30 # figment every 30 seconds (default: 60) python3 mainline.py --refresh # force re-fetch (bypass cache) python3 mainline.py --no-font-picker # skip interactive font picker python3 mainline.py --font-file path.otf # use a specific font file @@ -80,6 +82,7 @@ All constants live in `engine/config.py`: | `FRAME_DT` | `0.05` | Frame interval in seconds (20 FPS) | | `GRAD_SPEED` | `0.08` | Gradient sweep speed (cycles/sec, ~12s full sweep) | | `FIREHOSE_H` | `12` | Firehose zone height (terminal rows) | +| `FIGMENT_INTERVAL` | `60` | Seconds between figment appearances (set by `--figment-interval`) | | `NTFY_TOPIC` | klubhaus URL | ntfy.sh JSON stream endpoint | | `NTFY_RECONNECT_DELAY` | `5` | Seconds before reconnecting after a dropped SSE stream | | `MESSAGE_DISPLAY_SECS` | `30` | How long an ntfy message holds the screen | @@ -129,15 +132,11 @@ Update `NTFY_TOPIC` in `engine/config.py` to point at your own topic. Figment mode periodically overlays a full-screen SVG glyph on the running ticker — flickering through a reveal → hold (strobe) → dissolve cycle, colored with a randomly selected theme gradient. -**Enable it** by setting the plugin's config at runtime (no CLI flag yet): +**Enable it** with the `--figment` flag: -```python -from engine.effects.registry import get_registry -plugin = get_registry().get("figment") -if plugin: - plugin.config.enabled = True - plugin.config.params["interval_secs"] = 60 # seconds between appearances - plugin.config.params["display_secs"] = 4.5 # duration of each appearance +```bash +uv run mainline.py --figment # glyph every 60 seconds (default) +uv run mainline.py --figment --figment-interval 30 # every 30 seconds ``` **Figment assets** live in `figments/` — drop any `.svg` file there and it will be picked up automatically. The bundled set contains Mayan and Aztec glyphs. Figments are selected randomly, avoiding immediate repeats, and rasterized into half-block terminal art at display time. @@ -159,7 +158,7 @@ Built-in commands: `TRIGGER`, `SET_INTENSITY`, `SET_INTERVAL`, `SET_COLOR`, `STO **System dependency:** Figment mode requires the Cairo C library (`brew install cairo` on macOS) in addition to the `figment` extras group: ```bash -uv sync --extras figment # adds cairosvg +uv sync --extra figment # adds cairosvg ``` --- @@ -287,7 +286,7 @@ Requires Python 3.10+ and [uv](https://docs.astral.sh/uv/). ```bash uv sync # minimal (no mic, no figment) uv sync --extras mic # with mic support (sounddevice + numpy) -uv sync --extras figment # with figment mode (cairosvg + system Cairo) +uv sync --extra figment # with figment mode (cairosvg + system Cairo) uv sync --all-extras # all optional features uv sync --all-extras --group dev # full dev environment ``` @@ -343,7 +342,6 @@ Pre-commit hooks run lint automatically via `hk`. - **Parallax secondary column** — a second, dimmer, faster-scrolling stream of ambient text at reduced opacity on one side ### Cyberpunk Vibes -- **Figment CLI flag** — expose `--figment` and `--figment-interval N` to enable figment mode from the command line without code changes - **Figment intensity wiring** — `config.intensity` currently stored but not yet applied to reveal/dissolve speed or strobe frequency - **ntfy figment trigger** — built-in `NtfyFigmentTrigger` that listens on a dedicated topic to fire figments on demand - **Keyword watch list** — highlight or strobe any headline matching tracked terms (names, topics, tickers) diff --git a/engine/app.py b/engine/app.py index 10eea6d..8ba2803 100644 --- a/engine/app.py +++ b/engine/app.py @@ -413,6 +413,14 @@ def main(): if config.FIREHOSE: boot_ln("Firehose", "ENGAGED", True) + if config.FIGMENT: + try: + from effects_plugins.figment import FigmentEffect # noqa: F401 + + boot_ln("Figment", f"ARMED [{config.FIGMENT_INTERVAL}s interval]", True) + except (ImportError, OSError): + boot_ln("Figment", "UNAVAILABLE — run: brew install cairo", False) + time.sleep(0.4) slow_print(" > STREAMING...\n") time.sleep(0.2) diff --git a/engine/config.py b/engine/config.py index c5ce46c..8ca8191 100644 --- a/engine/config.py +++ b/engine/config.py @@ -196,6 +196,8 @@ MODE = ( else "news" ) FIREHOSE = "--firehose" in sys.argv +FIGMENT = "--figment" in sys.argv +FIGMENT_INTERVAL = _arg_int("--figment-interval", 60) # seconds between appearances # ─── NTFY MESSAGE QUEUE ────────────────────────────────── NTFY_TOPIC = "https://ntfy.sh/klubhaus_terminal_mainline/json" diff --git a/engine/figment_render.py b/engine/figment_render.py index 4b8b696..0b9e0ea 100644 --- a/engine/figment_render.py +++ b/engine/figment_render.py @@ -7,8 +7,20 @@ Follows the same pixel-pair approach as engine/render.py for OTF fonts. from __future__ import annotations +import os +import sys from io import BytesIO +# cairocffi (used by cairosvg) calls dlopen() to find the Cairo C library. +# On macOS with Homebrew, Cairo lives in /opt/homebrew/lib (Apple Silicon) or +# /usr/local/lib (Intel), which are not in dyld's default search path. +# Setting DYLD_LIBRARY_PATH before the import directs dlopen() to those paths. +if sys.platform == "darwin" and not os.environ.get("DYLD_LIBRARY_PATH"): + for _brew_lib in ("/opt/homebrew/lib", "/usr/local/lib"): + if os.path.exists(os.path.join(_brew_lib, "libcairo.2.dylib")): + os.environ["DYLD_LIBRARY_PATH"] = _brew_lib + break + import cairosvg from PIL import Image diff --git a/engine/scroll.py b/engine/scroll.py index d297b3d..1bb90a2 100644 --- a/engine/scroll.py +++ b/engine/scroll.py @@ -55,14 +55,16 @@ def stream(items, ntfy_poller, mic_monitor, display: Display | None = None): frame_number = 0 # Figment overlay (optional — requires cairosvg) - try: - from effects_plugins.figment import FigmentEffect - from engine.effects.registry import get_registry + figment = None + if config.FIGMENT: + try: + from effects_plugins.figment import FigmentEffect - _fg_plugin = get_registry().get("figment") - figment = _fg_plugin if isinstance(_fg_plugin, FigmentEffect) else None - except ImportError: - figment = None + figment = FigmentEffect() + figment.config.enabled = True + figment.config.params["interval_secs"] = config.FIGMENT_INTERVAL + except (ImportError, OSError): + pass while True: if queued >= config.HEADLINE_LIMIT and not active: