Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ DISCORD_CHANNEL_ID=
STATUSPAGE_BASE_URL=https://status.atlassian.com
# Multi-monitor config overrides the two fields above when set. Accepts Statuspage.io and
# incident.io URLs. Optional per-entry "provider" field: "statuspage" (default) or "incidentio".
# STATUSPAGE_MONITORS_JSON=[{"id":"atlassian","channelId":"123456789012345678","baseUrl":"https://status.atlassian.com","label":"Atlassian"},{"id":"openai","channelId":"234567890123456789","baseUrl":"https://status.openai.com","label":"OpenAI","provider":"incidentio"}]
STATUSPAGE_MONITORS_JSON=
# MONITORS_JSON=[{"id":"atlassian","channelId":"123456789012345678","baseUrl":"https://status.atlassian.com","label":"Atlassian"},{"id":"openai","channelId":"234567890123456789","baseUrl":"https://status.openai.com","label":"OpenAI","provider":"incidentio"}]
# Legacy alias `STATUSPAGE_MONITORS_JSON` is still honored for backwards
# compatibility but emits a deprecation warning at startup; prefer MONITORS_JSON.
MONITORS_JSON=
POLL_INTERVAL_MS=180000
POST_EXISTING_UPDATES_ON_START=false
ENABLE_STATUS_COMMAND=true
Expand Down
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Instructions for AI coding agents working on this project. **All agents MUST rea

## Project Overview

statuspage-discord is a Bun-based Discord bot that polls public status pages (Statuspage.io and incident.io are supported) and posts incident updates as threaded conversations in Discord. It supports multiple monitors, runtime monitor management, and persistent state.
Squawk is a Bun-based Discord bot that polls public status pages (Statuspage.io and incident.io are supported) and posts incident updates as threaded conversations in Discord. It supports multiple monitors, runtime monitor management, and persistent state.

The repo was previously named `statuspage-discord`. The legacy `STATUSPAGE_MONITORS_JSON` env var is still honored as a deprecated alias for `MONITORS_JSON`.

## Tech Stack

Expand Down Expand Up @@ -122,7 +124,7 @@ Every command handler follows:

See `.env.example` for the full list. Key ones:
- `DISCORD_TOKEN`, `DISCORD_APPLICATION_ID` (required)
- `STATUSPAGE_MONITORS_JSON` or `DISCORD_CHANNEL_ID` + `STATUSPAGE_BASE_URL`
- `MONITORS_JSON` or `DISCORD_CHANNEL_ID` + `STATUSPAGE_BASE_URL` (legacy `STATUSPAGE_MONITORS_JSON` still honored with deprecation warning)
- `POLL_INTERVAL_MS` (default 60000)
- `ENABLE_*_COMMAND` feature flags (all default true, includes `ENABLE_CLEANUP_COMMAND`)
- `APP_VERSION` (optional, auto-set in Docker builds via build arg, falls back to `package.json` version)
Expand Down
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# statuspage-discord
# Squawk

A Bun-based Discord bot that:

Expand Down Expand Up @@ -26,27 +26,31 @@ bun dev # Watch mode (or `bun start` for production)
docker compose up -d # Production deployment with Docker Compose
```

A prebuilt image is available at `ghcr.io/anthonybaldwin/statuspage-discord:main`.
A prebuilt image is available at `ghcr.io/anthonybaldwin/squawk:latest`.

## Documentation

Full docs live in the [wiki](https://github.com/anthonybaldwin/statuspage-discord/wiki):
Full docs live in the [wiki](https://github.com/anthonybaldwin/squawk/wiki):

| Page | Description |
|------|-------------|
| [Architecture](https://github.com/anthonybaldwin/statuspage-discord/wiki/Architecture) | System design, data flow, and module structure |
| [Configuration](https://github.com/anthonybaldwin/statuspage-discord/wiki/Configuration) | Environment variables, multi-monitor setup, feature flags |
| [Commands](https://github.com/anthonybaldwin/statuspage-discord/wiki/Commands) | All slash commands with usage and permissions |
| [Incident Lifecycle](https://github.com/anthonybaldwin/statuspage-discord/wiki/Incident-Lifecycle) | How incidents are tracked from creation to resolution or removal |
| [State Management](https://github.com/anthonybaldwin/statuspage-discord/wiki/State-Management) | Persistence format, migration, and locking |
| [API Integration](https://github.com/anthonybaldwin/statuspage-discord/wiki/API-Integration) | Supported providers, endpoints, and how to add a new provider |
| [Deployment](https://github.com/anthonybaldwin/statuspage-discord/wiki/Deployment) | Docker, Docker Compose, CI/CD, and production notes |
| [Development](https://github.com/anthonybaldwin/statuspage-discord/wiki/Development) | Local setup, tooling, and contribution guide |
| [Contributing](https://github.com/anthonybaldwin/statuspage-discord/wiki/Contributing) | How to contribute, code conventions, and documentation rules |
| [Architecture](https://github.com/anthonybaldwin/squawk/wiki/Architecture) | System design, data flow, and module structure |
| [Configuration](https://github.com/anthonybaldwin/squawk/wiki/Configuration) | Environment variables, multi-monitor setup, feature flags |
| [Commands](https://github.com/anthonybaldwin/squawk/wiki/Commands) | All slash commands with usage and permissions |
| [Incident Lifecycle](https://github.com/anthonybaldwin/squawk/wiki/Incident-Lifecycle) | How incidents are tracked from creation to resolution or removal |
| [State Management](https://github.com/anthonybaldwin/squawk/wiki/State-Management) | Persistence format, migration, and locking |
| [API Integration](https://github.com/anthonybaldwin/squawk/wiki/API-Integration) | Supported providers, endpoints, and how to add a new provider |
| [Deployment](https://github.com/anthonybaldwin/squawk/wiki/Deployment) | Docker, Docker Compose, CI/CD, and production notes |
| [Development](https://github.com/anthonybaldwin/squawk/wiki/Development) | Local setup, tooling, and contribution guide |
| [Contributing](https://github.com/anthonybaldwin/squawk/wiki/Contributing) | How to contribute, code conventions, and documentation rules |

## Notes

- The bot uses public APIs only — Statuspage.io's v2 API (`<base-url>/api/v2/...`) or incident.io's widget proxy (`<base-url>/proxy/<host>`) — so a public page URL is all you need.
- For development, setting `DISCORD_GUILD_ID` makes slash-command registration update faster than global commands.
- On first startup, the bot seeds current incident-update IDs without posting them unless `POST_EXISTING_UPDATES_ON_START=true`.
- The bot needs Send Messages, Embed Links, Create Public Threads, and Manage Messages permissions.

## Previously known as

This project was originally named `statuspage-discord`. The new name reflects that it covers more than just Statuspage.io. The legacy `STATUSPAGE_MONITORS_JSON` env var is still honored (with a deprecation warning) — prefer `MONITORS_JSON` going forward.
4 changes: 2 additions & 2 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ services:
build: .
env_file: .env
volumes:
- statuspage_data:/app/data
- squawk_data:/app/data
restart: unless-stopped

volumes:
statuspage_data:
squawk_data:
2 changes: 1 addition & 1 deletion docs/wiki/API-Integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Current probe order:
1. **incident.io** — probed first because many incident.io pages also expose a Statuspage-compatible `/api/v2/` shim, but the shim returns empty update bodies and a truncated history. Probing incident.io first ensures we use the richer native widget API when available.
2. **Statuspage.io** — fallback for pages that are not on incident.io.

Monitors loaded from `data/monitors.json` or `STATUSPAGE_MONITORS_JSON` that pre-date multi-provider support default to `statuspage` for backwards compatibility.
Monitors loaded from `data/monitors.json` or `MONITORS_JSON` that pre-date multi-provider support default to `statuspage` for backwards compatibility.

## Statuspage.io Adapter

Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

statuspage-discord is a Bun/TypeScript application with bot logic in `src/index.ts` (~1700 lines, single file by design) and provider-specific API adapters in `src/providers/` (one small file per provider). It connects to Discord via [discord.js](https://discord.js.org/) and polls supported public status page APIs on a timer.
squawk is a Bun/TypeScript application with bot logic in `src/index.ts` (~1700 lines, single file by design) and provider-specific API adapters in `src/providers/` (one small file per provider). It connects to Discord via [discord.js](https://discord.js.org/) and polls supported public status page APIs on a timer.

## Data Flow

Expand Down
4 changes: 2 additions & 2 deletions docs/wiki/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You must configure monitors using **one** of these two approaches:
### Option A: Multi-Monitor (Recommended)

```env
STATUSPAGE_MONITORS_JSON=[{"id":"atlassian","channelId":"123456789","baseUrl":"https://status.atlassian.com","label":"Atlassian"},{"id":"openai","channelId":"987654321","baseUrl":"https://status.openai.com","label":"OpenAI","provider":"incidentio"}]
MONITORS_JSON=[{"id":"atlassian","channelId":"123456789","baseUrl":"https://status.atlassian.com","label":"Atlassian"},{"id":"openai","channelId":"987654321","baseUrl":"https://status.openai.com","label":"OpenAI","provider":"incidentio"}]
```

Each monitor object requires:
Expand All @@ -37,7 +37,7 @@ DISCORD_CHANNEL_ID=123456789
STATUSPAGE_BASE_URL=https://status.atlassian.com
```

This creates a single monitor with ID `default`. If `STATUSPAGE_MONITORS_JSON` is set, these two variables are ignored.
This creates a single monitor with ID `default`. If `MONITORS_JSON` is set, these two variables are ignored.

### Runtime Monitors

Expand Down
6 changes: 3 additions & 3 deletions docs/wiki/Contributing.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Contributing

Thanks for your interest in contributing to statuspage-discord!
Thanks for your interest in contributing to squawk!

## Getting Started

1. **Open an issue** to discuss the change before starting work on anything non-trivial
2. **Fork the repo** and create a feature branch off `main`
3. **Set up your environment** — see the [Development](https://github.com/anthonybaldwin/statuspage-discord/wiki/Development) page for local setup
3. **Set up your environment** — see the [Development](https://github.com/anthonybaldwin/squawk/wiki/Development) page for local setup
4. **Make your changes**, ensuring they follow the conventions below
5. **Open a pull request** against `main`

Expand Down Expand Up @@ -101,4 +101,4 @@ gh pr merge --rebase --delete-branch

## Questions?

Open a [GitHub issue](https://github.com/anthonybaldwin/statuspage-discord/issues) or start a [discussion](https://github.com/anthonybaldwin/statuspage-discord/discussions).
Open a [GitHub issue](https://github.com/anthonybaldwin/squawk/issues) or start a [discussion](https://github.com/anthonybaldwin/squawk/discussions).
8 changes: 4 additions & 4 deletions docs/wiki/Deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ docker compose up -d

The `compose.yml` configures:
- `env_file: .env` for secret injection (never baked into the image)
- `statuspage_data` named volume mounted at `/app/data` for persistent state
- `squawk_data` named volume mounted at `/app/data` for persistent state
- `restart: unless-stopped` for automatic recovery

State survives container restarts, recreations, and image updates (e.g., via [Watchtower](https://containrrr.dev/watchtower/) or [WUD](https://github.com/fmartinou/whats-up-docker)).
Expand All @@ -20,21 +20,21 @@ State survives container restarts, recreations, and image updates (e.g., via [Wa
Build:

```bash
docker build -t statuspage-discord .
docker build -t squawk .
```

Run:

```bash
docker run --rm --env-file .env -v statuspage_data:/app/data statuspage-discord
docker run --rm --env-file .env -v squawk_data:/app/data squawk
```

## Prebuilt Image

A multi-arch image (ARM64 + AMD64) is published to GitHub Container Registry on every push to `main`:

```bash
docker pull ghcr.io/anthonybaldwin/statuspage-discord:latest
docker pull ghcr.io/anthonybaldwin/squawk:latest
```

Tags:
Expand Down
4 changes: 2 additions & 2 deletions docs/wiki/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
## Setup

```bash
git clone https://github.com/anthonybaldwin/statuspage-discord.git
cd statuspage-discord
git clone https://github.com/anthonybaldwin/squawk.git
cd squawk
bun install
cp .env.example .env
# Edit .env with your bot token and channel IDs
Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/Home.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# statuspage-discord
# squawk

A Bun-based Discord bot that monitors public status pages — [Statuspage.io](https://www.atlassian.com/software/statuspage) and [incident.io](https://incident.io/) are supported — and posts incident updates to Discord as threaded conversations.

Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/State-Management.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# State Management

The bot persists its state to JSON files in the `data/` directory. In Docker deployments, this directory is backed by a named volume (`statuspage_data`).
The bot persists its state to JSON files in the `data/` directory. In Docker deployments, this directory is backed by a named volume (`squawk_data`).

## Files

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "statuspage-discord",
"name": "squawk",
"version": "0.1.0",
"private": true,
"type": "module",
Expand Down
20 changes: 16 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const envSchema = z.object({
DISCORD_GUILD_ID: z.string().min(1).optional(),
DISCORD_CHANNEL_ID: z.string().min(1).optional(),
STATUSPAGE_BASE_URL: z.string().url().optional(),
// MONITORS_JSON is the canonical multi-monitor config (covers both
// Statuspage.io and incident.io). STATUSPAGE_MONITORS_JSON is the
// pre-rename alias — still honored, with a deprecation warning at
// startup. Either may be set; if both are set, MONITORS_JSON wins.
MONITORS_JSON: z.string().optional(),
STATUSPAGE_MONITORS_JSON: z.string().optional(),
POLL_INTERVAL_MS: z.coerce.number().int().positive().default(60_000),
POST_EXISTING_UPDATES_ON_START: booleanFromEnv.default("false"),
Expand All @@ -83,8 +88,15 @@ type RuntimeMonitorFile = {
};

function loadMonitors(env: z.infer<typeof envSchema>): MonitorConfig[] {
if (env.STATUSPAGE_MONITORS_JSON) {
const parsed = JSON.parse(env.STATUSPAGE_MONITORS_JSON) as unknown;
const rawMonitors = env.MONITORS_JSON ?? env.STATUSPAGE_MONITORS_JSON;
if (rawMonitors) {
if (!env.MONITORS_JSON && env.STATUSPAGE_MONITORS_JSON) {
console.warn(
"STATUSPAGE_MONITORS_JSON is deprecated; rename to MONITORS_JSON. " +
"The legacy name still works but will be removed in a future release.",
);
}
const parsed = JSON.parse(rawMonitors) as unknown;
return z.array(monitorSchema).parse(parsed);
}

Expand All @@ -99,7 +111,7 @@ function loadMonitors(env: z.infer<typeof envSchema>): MonitorConfig[] {
}

throw new Error(
"Configure either STATUSPAGE_MONITORS_JSON or both DISCORD_CHANNEL_ID and STATUSPAGE_BASE_URL.",
"Configure either MONITORS_JSON or both DISCORD_CHANNEL_ID and STATUSPAGE_BASE_URL.",
);
}

Expand Down Expand Up @@ -1821,7 +1833,7 @@ async function handleMonitorList(interaction: ChatInputCommandInteraction) {

if (monitors.length === 0) {
await interaction.editReply({
content: "No monitors configured. Use `/monitor add` or set `STATUSPAGE_MONITORS_JSON`.",
content: "No monitors configured. Use `/monitor add` or set `MONITORS_JSON`.",
});
return;
}
Expand Down