Files
BLIGHT--CUE/README.md
Spencer a80f945701 Add CLAUDE.md with versioned changelog, update README
- Created CLAUDE.md with instructions to update changelog and README on
  every commit, version number (v1.2.0), and full changelog from v1.0.0
- Updated README: document-scope BLIGHT:: syntax, processing order,
  two-line failure format, complete_document() in AI provider section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:45:01 -05:00

288 lines
7.9 KiB
Markdown

# 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: <your instruction here>
```
**Document-scope trigger** — replaces the entire file with the AI's rewritten version:
```
BLIGHT:: <your instruction here>
```
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
<!-- BLIGHT_FAILED: your original instruction -->
<!-- BLIGHT_ERROR: <error message> -->
```
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 <this-repo-url>
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 <gitea-container-name> 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://<gateway-ip>: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.