# BLIGHT: CUE A module in the **BLIGHT** ecosystem. BLIGHT: CUE monitors Gitea repositories containing markdown files. When a push is received, it scans changed files for `BLIGHT:` trigger lines, sends the document to an AI model along with the instruction, and writes the result back to the file in-place — fully automated. --- ## How It Works 1. You write a `BLIGHT:` trigger anywhere in a markdown file and push it to Gitea. 2. Gitea sends a webhook POST to this server. 3. The server fetches the file, finds all `BLIGHT:` triggers, and processes them. 4. Each trigger is replaced with the AI's response, then the updated file is committed back automatically. ### Trigger Syntax Triggers are case-insensitive — `BLIGHT:`, `blight:`, `Blight::`, etc. all work. **Inline trigger** — replaces only the trigger line with the AI's response: ``` BLIGHT: ``` **Document-scope trigger** — replaces the entire file with the AI's rewritten version: ``` BLIGHT:: ``` Examples: ```markdown This paragraph discusses は vs が in Japanese grammar. BLIGHT: Explain the key differences between は and が based on the paragraph above. ## Next Section ``` ```markdown BLIGHT: Write a conclusion paragraph for this document. ``` ```markdown BLIGHT:: Spellcheck and lightly reformat this entire document. ``` ### Processing Order When a file contains multiple triggers: 1. All inline (`BLIGHT:`) triggers are processed first, in document order. 2. All document-scope (`BLIGHT::`) triggers are processed next, in document order — each one operates on the result of the previous. ### Failure Behavior If the AI call fails after 3 attempts, the trigger is replaced with: ```html ``` You can re-trigger processing by editing the file to restore the original trigger line and pushing again. --- ## Prerequisites - Python 3.10+ - Access to your Gitea instance (via Tailscale or local network) - A [Google Gemini API key](https://aistudio.google.com/apikey) - A Gitea personal access token with **repository read/write** permissions --- ## Installation ```bash # 1. Clone this repo onto the machine that will run the listener git clone cd BLIGHT--CUE # 2. Create and activate a virtual environment python3 -m venv .venv source .venv/bin/activate # 3. Install dependencies pip install -r requirements.txt # 4. Copy the example env file and fill in your values cp .env.example .env ``` --- ## Configuration Edit `.env` with your values: | Variable | Description | | ---------------- | ------------------------------------------------------------------- | | `GITEA_URL` | Base URL of your Gitea instance, no trailing slash | | `GITEA_TOKEN` | Personal access token from Gitea → Settings → Applications | | `GEMINI_API_KEY` | API key from [Google AI Studio](https://aistudio.google.com/apikey) | | `WEBHOOK_SECRET` | A secret string you choose — must match what you set in Gitea | | `WEBHOOK_PORT` | Port the listener binds to (default: `5010`) | --- ## Running ```bash python app.py ``` The server binds to `0.0.0.0:5010` (or your configured port). Keep it running as a service or in a screen/tmux session. For production use, run it as a systemd service so it starts automatically on boot and restarts on failure. **1. Create the service file:** ```bash sudo nano /etc/systemd/system/blight-cue.service ``` Paste the following, adjusting the paths and user to match your setup: ```ini [Unit] Description=BLIGHT: CUE webhook listener After=network.target [Service] User=artanis WorkingDirectory=/home/artanis/Documents/BLIGHT_CUE ExecStart=/home/artanis/Documents/BLIGHT_CUE/.venv/bin/python app.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` **2. Enable and start the service:** ```bash sudo systemctl daemon-reload sudo systemctl enable blight-cue sudo systemctl start blight-cue ``` **3. Check it's running:** ```bash sudo systemctl status blight-cue ``` **4. View logs:** ```bash journalctl -u blight-cue -f ``` --- ## Registering the Webhook in Gitea ### If Gitea runs in Docker Gitea containers cannot reach `localhost` on the host directly. The recommended approach is to use `host.docker.internal`, which Docker resolves to the host machine's IP. **1. Add `extra_hosts` to your Gitea `docker-compose.yml`:** ```yaml services: gitea: image: gitea/gitea:latest # ... your existing config ... extra_hosts: - "host.docker.internal:host-gateway" ``` **2. Restart Gitea:** ```bash docker compose down && docker compose up -d ``` **3. Find the host's IP from the container's perspective:** ```bash docker exec -it ip route show default ``` This returns something like `default via 172.20.0.1 dev eth0`. That gateway IP is the host's address reachable from the container — use it as the webhook target. > **Note:** `host.docker.internal` resolves to the default `docker0` bridge (`172.17.0.1`) on Linux, which may not be reachable if Gitea is on a custom Docker network. Using the gateway IP directly is more reliable. **4. Whitelist the IP in Gitea's config:** ```yaml environment: - GITEA__webhook__ALLOWED_HOST_LIST=172.20.0.1 # replace with your gateway IP ``` **5. Restart Gitea again** after adding the environment variable. > **Note:** If CUE is not running when Gitea sends a webhook, the delivery will fail. You can use the **Redeliver** button in the webhook's delivery history to retry without needing to push again. --- ### Registering the Webhook Do this for **each repository** you want BLIGHT: CUE to watch. 1. Open the repository in Gitea. 2. Go to **Settings** → **Webhooks** → **Add Webhook** → **Gitea**. 3. Set the fields: - **Target URL**: `http://:5010/webhook` (e.g. `http://172.20.0.1:5010/webhook`) - **HTTP Method**: POST - **Content Type**: `application/json` - **Secret**: the same value as `WEBHOOK_SECRET` in your `.env` - **Trigger On**: Push events only 4. Click **Add Webhook**, then use **Test Delivery** to verify connectivity. --- ## AI Provider BLIGHT: CUE currently uses **Google Gemini 2.5 Flash-Lite** — the most cost-effective stable Gemini model (~$0.10/$0.40 per million tokens input/output). ### Adding a New Provider All AI providers implement the `AIProvider` abstract base class in `ai/base.py`: ```python from abc import ABC, abstractmethod class AIProvider(ABC): def complete(self, document: str, instruction: str) -> str: """Return text to insert in place of an inline BLIGHT: trigger.""" ... def complete_document(self, document: str, instruction: str) -> str: """Return the full rewritten document for a BLIGHT:: trigger.""" ... ``` To add a new provider (e.g. OpenRouter): 1. Create `ai/openrouter.py` and implement both methods of `AIProvider`. 2. In `processor.py`, replace `GeminiProvider()` with your new class. --- ## Project Structure ``` Blight_Reader/ ├── app.py # Flask webhook server ├── processor.py # Trigger scanning and replacement logic ├── gitea_client.py # Gitea REST API wrapper ├── config.py # Environment config loader ├── ai/ │ ├── base.py # AIProvider abstract base class │ └── gemini.py # Gemini 2.5 Flash-Lite implementation ├── requirements.txt ├── .env.example └── README.md ``` --- ## Deployments | Machine | Path | Service | |---|---|---| | Homelab | `/home/artanis/Documents/BLIGHT_CUE` | `blight-cue.service` | --- ## Part of the BLIGHT Ecosystem BLIGHT: CUE is one module in a larger modular system called **BLIGHT**. Each module is independently deployable and communicates through Gitea repositories as the shared data layer.