# The Web — Neighborhood Pager (SMS LLM Incident Broadcast)
A single phone number + a local language model = a neighborhood emergency broadcast.
People text reports, the model fills in missing details, and when a report is complete, the system broadcasts an alert to everyone subscribed to that postcode (zip). No apps, no logins — just SMS.
## Features
* **Two run modes**
* **Demo** (default): no real SMS; you type simulated messages in the console.
* **Twilio**: real SMS via a Twilio number (A2P‑10DLC ready) as the terminal.
* **Join/leave with one text**
* Strict opt‑in pledge to join; STOP to leave.
* **LLM‑powered parsing (optional)**
* Asks for missing pieces (what / location / time / postcode), composes a concise public alert.
* Works without an LLM (fallback question flow).
* **Safety & sanity**
* 30‑min reporter cooldown after a successful broadcast.
* Minimal data stored locally in SQLite.
## Repository Layout
* Program.cs — app entry, mode selection (Demo/Twilio), config & wiring
* TwilioSender.cs — outbound SMS
* SmsInboxPoller.cs — inbound SMS polling (Twilio) or demo input
* MessageRouter.cs — command parsing (JOIN/STOP) and incident flow
* PledgeParser.cs — strict pledge text parsing
* SubscriberStore.cs — SQLite storage
## Requirements
* **.NET 8 SDK** (Windows/macOS/Linux)
* SQLite is bundled via Microsoft.Data.Sqlite (no separate install required)
* (Optional) **OpenAI‑compatible local model** server (e.g., LM Studio)
* (Optional for real SMS) **Twilio account** with an **SMS‑capable number**
* For US 10DLC messaging to arbitrary numbers, complete **A2P brand + campaign** registration and link your number to that campaign in Twilio.
Note: I can’t fetch the latest Twilio UI steps here; follow Twilio’s current docs for buying a number and A2P registration if needed.
## Quick Start — Demo Mode (no SMS)
Demo mode lets you simulate SMS by typing +E164> message lines directly in the console.
1. **Restore & run**
```bash
dotnet run
```
2. **Choose** 1) Demo (no SMS, type messages here) in the startup wizard.
3. **(Optional) LLM**
* If you have LM Studio (or similar) running, point to it when asked (see **Local LLM** below). Otherwise choose fallback.
4. **Try it**
```
+15550000001> I, Alex Doe, join the Web for 95129. I will receive alerts nearby and stand by the truth of what I report to the Web.
+15550000001> Fire near King's Street
+15550000001> Around 6pm
```
You’ll see the composed alert and simulated outbound sends in the console.
### Scripted demo (optional)
Create a file demo.txt:
\# demo.txt
+15550000001> I, Alex Doe, join the Web for 95129. I will receive alerts nearby and stand by the truth of what I report to the Web.
+15550000001> Garden Gate, smoke near the supermarket, 95129
+15550000001> Around 6pm
Run with an env var:
* **Windows (PowerShell)**
```powershell
setx DEMO_REPLAY "C:\path\to\demo.txt"
dotnet run
```
* **macOS/Linux (bash)**
```bash
export DEMO_REPLAY="/path/to/demo.txt"
dotnet run
```
In demo mode you always type with the
+E164>prefix so the app knows which “sender” is speaking.
## Quick Start — Real SMS (Twilio)
### 1) Twilio prerequisites
* **Account SID** and **Auth Token**
* An **SMS‑capable number** you own (format: +15551234567)
* Trial accounts can only text **verified** numbers and may hit daily limits. For production 10DLC, complete **A2P** steps in Twilio.
### 2) Configure environment variables
Set **both**
TWILIO\_FROMandHUB\_NUMBERto the **same** Twilio number you own. The app polls Twilio and filters inbound byHUB\_NUMBER.
**Windows (PowerShell)**
setx SMS\_PROVIDER "twilio"
setx TWILIO\_SID "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
setx TWILIO\_TOKEN "your\_auth\_token"
setx TWILIO\_FROM "+15551234567"
setx HUB\_NUMBER "+15551234567"
\# Optional local LLM
setx LLM\_BASE\_URL "http://127.0.0.1:1234" # root only; no /v1 here
setx LLM\_MODEL "qwen2.5-7b-instruct"
**macOS/Linux (bash)**
export SMS\_PROVIDER=twilio
export TWILIO\_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export TWILIO\_TOKEN=your\_auth\_token
export TWILIO\_FROM=+15551234567
export HUB\_NUMBER=+15551234567
\# Optional local LLM
export LLM\_BASE\_URL=http://127.0.0.1:1234 # root only; no /v1 here
export LLM\_MODEL=qwen2.5-7b-instruct
### 3) Run & select Twilio mode
dotnet run
Pick 2) Twilio SMS in the wizard (it will use your env vars or prompt for them).
### 4) Use from your phone
1. **Join** by texting this exact pledge (replace \[ ]) to your Twilio number:
```
I, [First Name] [Last Name], join the Web for [POSTCODE]. I will receive alerts nearby and stand by the truth of what I report to the Web.
```
2. **Report** freely (e.g., “crash near X at \~5pm 95129”). If anything’s missing, the bot asks **only** for the missing piece(s).
3. On completion, an \[ALERT] ... goes to everyone subscribed to that postcode (except the reporter).
4. **STOP** to leave.
## Local LLM (optional but recommended)
You can run fully without an LLM (fallback prompts), but a local model improves parsing and tone.
1. Start an OpenAI‑compatible server (e.g., LM Studio). Its root is usually http://127.0.0.1:1234. **Do not** include /v1 in the env var.
2. Set:
* LLM\_BASE\_URL → http://127.0.0.1:1234
* LLM\_MODEL → the model name LM Studio shows (e.g., qwen2.5-7b-instruct)
3. Logs:
* **Demo mode** hides raw JSON by default; set LOG\_LLM\_JSON=1 to show it.
* **Twilio mode** prints model JSON by default.
## How It Behaves
* **Join**
* If a number isn’t subscribed, the system expects the exact pledge below and will reply with instructions until it matches.
* On success: “Welcome to the Web for {postcode} …” and the number is stored in SQLite.
* **STOP**
* Unsubscribes immediately and replies with the join instructions again.
* **Reporting**
* The last 10 messages within 30 minutes from a reporter form the thread sent to the LLM.
* Missing fields → one concise follow‑up (what/location/time/postcode).
* Complete → broadcast a one‑line alert and start a 30‑min cooldown for that reporter.
**Alert style**
\[ALERT] {What} near {Location} at {Time}. (Report via The Web; call 911 for emergencies.)
## Environment Variables (reference)
| Name | Required | Mode | Description |
| -------------- | -------- | ------ | -------------------------------------------------- |
| SMS\_PROVIDER | yes | both | demo (default) or twilio |
| TWILIO\_SID | yes | twilio | Twilio Account SID |
| TWILIO\_TOKEN | yes | twilio | Twilio Auth Token |
| TWILIO\_FROM | yes | twilio | Your Twilio number in E.164 (e.g., +15551234567) |
| HUB\_NUMBER | yes | twilio | Same as TWILIO\_FROM; used to filter inbound |
| LLM\_BASE\_URL | no | both | OpenAI‑compatible base URL **without** /v1 |
| LLM\_MODEL | no | both | Model name as shown by your server |
| DEMO\_REPLAY | no | demo | Path to a demo.txt script of lines to replay |
| LOG\_LLM\_JSON | no | both | 1 to log raw model JSON in demo mode |
| NO\_WIZARD | no | both | 1 to skip startup wizard and use env vars only |
## Build a Stand‑Alone Executable (Yes, you can ship an .exe)
You can publish a single‑file, self‑contained binary for your target OS.
### Windows (x64)
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true
Output will land under:
./bin/Release/net8.0/win-x64/publish/TheWeb.exe
### macOS (Apple Silicon)
dotnet publish -c Release -r osx-arm64 --self-contained true -p:PublishSingleFile=true
Output:
./bin/Release/net8.0/osx-arm64/publish/TheWeb
### Linux (x64)
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true
Output:
./bin/Release/net8.0/linux-x64/publish/TheWeb
**Tip:** For smallest size, you can add
-p:PublishTrimmed=true, but only after you’ve verified runtime paths (SQLite & reflection) keep working.
Run the binary with your environment variables set (same as dotnet run).
## Data Storage
* SQLite file web.db is created alongside the executable and contains:
* subscribers(phone\_e164, first\_name, last\_name, postcode)
* processed\_inbound(sid, from\_e164, to\_e164, received\_utc) (prevents reprocessing the same Twilio message ID)
* No personal data beyond name/number/postcode is stored.
## Troubleshooting
* **Twilio trial or undelivered**
* Trial: messages only to verified numbers; strict limits apply.
* For broad US messaging: complete A2P 10DLC (brand + campaign) and link your number.
* **No inbound appears**
* Ensure HUB\_NUMBER equals your Twilio number in E.164 and matches TWILIO\_FROM.
* Check that your number is SMS‑capable and SID/token are correct.
* **LLM ignored**
* LLM\_BASE\_URL must be the **root** (no /v1). Ensure your server is running.
* **Never broadcasts**
* Provide clear what/location/time. If postcode is omitted in the report, the system falls back to the reporter’s subscribed postcode.
## Exact Texts (copy/paste)
**Join instructions (auto‑sent on mismatch or after STOP):**
To join the Web, copy this exactly and replace the \[ ]:
I, \[First Name] \[Last Name], join the Web for \[POSTCODE]. I will receive alerts nearby and stand by the truth of what I report to the Web.
**Leave:**
STOP
## License
Add a license file (MIT/Apache‑2.0/etc.).
## Acknowledgements
* **LM Studio** for an easy OpenAI‑compatible local server.
* **Twilio** for SMS APIs.