https://github.com/ysm-dev/wachi
Subscribe any link and get notified on change
https://github.com/ysm-dev/wachi
Last synced: 2 months ago
JSON representation
Subscribe any link and get notified on change
- Host: GitHub
- URL: https://github.com/ysm-dev/wachi
- Owner: ysm-dev
- Created: 2026-02-17T15:45:12.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-11T08:13:27.000Z (2 months ago)
- Last Synced: 2026-04-11T10:29:03.480Z (2 months ago)
- Language: TypeScript
- Size: 21.1 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# wachi
[](https://www.npmjs.com/package/wachi)
[](https://github.com/ysm-dev/wachi/actions)
[](https://github.com/ysm-dev/wachi/blob/main/LICENSE)
**Monitor RSS feeds and get notified on new content.**
wachi monitors RSS feeds for new content and pushes notifications to 90+ services via [apprise](https://github.com/caronc/apprise). It auto-discovers RSS feeds when available.
- **Zero config for RSS** -- point at a blog, wachi finds the feed
- **90+ notification services** -- Slack, Discord, Telegram, email, and [more](https://github.com/caronc/apprise/wiki)
- **Stateless by design** -- `wachi check` is a one-shot command, perfect for cron
- **No interactive prompts** -- built for automation and AI agents
## Install
```bash
# ephemeral run
npx wachi@latest --help
bunx wachi@latest --help
# persistent global install
npm i -g wachi
bun install -g wachi
# standalone binary (macOS/Linux)
curl -fsSL https://raw.githubusercontent.com/ysm-dev/wachi/main/install.sh | sh
# standalone binary (Windows PowerShell)
powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/ysm-dev/wachi/main/install.ps1 | iex"
# homebrew
brew tap ysm-dev/tap && brew install wachi
```
## Quick Start
```bash
# 1. Create a named channel and subscribe a URL (auto-discovers RSS)
wachi sub -n main -a "slack://xoxb-token/channel" "https://blog.example.com"
# 2. Check for new content (run this on a schedule)
wachi check
# That's it. New posts get pushed to your Slack channel.
```
## How It Works
```
wachi sub -n [-a ]
│
▼
Is it RSS? ───yes───▶ Store as RSS subscription
│no
▼
Auto-discover RSS ───found───▶ Store URL + discovered feed
(link tags, common paths)
```
On `wachi check`, each subscription is fetched and compared against a dedup table. New items trigger notifications via apprise. Old items are skipped. That's it.
## Commands
```
wachi sub -n Subscribe a URL to a named channel
-a, --apprise-url Required when creating a new channel
-e, --send-existing Send all current items on next check (skip baseline)
wachi unsub -n [url] Unsubscribe a URL or remove entire channel
wachi ls List all channels and subscriptions
wachi check Check all subscriptions for changes
-n, --name Check specific channel only
-p, --concurrency Max concurrent checks (default: 10)
-d, --dry-run Preview without sending or recording
wachi test -n Send a test notification
wachi upgrade Update a persistent wachi install
```
`wachi upgrade` follows the original install method:
- npm global -> `npm install -g wachi@latest`
- bun global -> `bun install -g wachi@latest`
- Homebrew -> `brew upgrade wachi`
- standalone binary -> downloads the latest GitHub Release and replaces the current binary
Ephemeral runs via `npx` and `bunx` are not persistent installs, so they are not upgraded in place. Re-run them with `@latest` instead.
**Global flags:** `--json` / `-j` for machine-readable output, `--verbose` / `-V` for detailed logs, `--config` / `-C` for custom config path.
## Examples
```bash
# Blog (auto-discovers RSS)
wachi sub -n main -a "slack://xoxb-token/channel" "https://blog.example.com"
# GitHub releases RSS feed
wachi sub -n alerts -a "discord://webhook-id/token" "https://github.com/ysm-dev/wachi/releases.atom"
# Add another subscription to an existing channel name
wachi sub -n main "https://example.com/changelog"
# YouTube channel
wachi sub -n media -a "tgram://bot-token/chat-id" "https://youtube.com/@channel"
# URL without https:// (auto-prepended)
wachi sub -n main "blog.example.com"
# Send all existing items on next check (no baseline)
wachi sub -n alerts -e "https://github.com/ysm-dev/wachi/releases.atom"
# Dry-run: see what would be sent
wachi check -d
# Check specific channel only
wachi check -n main
# Run every 5 minutes with crnd
crnd "*/5 * * * *" wachi check
# System cron
crontab -e
# */5 * * * * wachi check
```
## Notifications
wachi uses [apprise](https://github.com/caronc/apprise) for delivery -- Slack, Discord, Telegram, Email, Pushover, Gotify, ntfy, and [90+ more](https://github.com/caronc/apprise/wiki).
Each new item is sent as a separate message:
```
https://blog.example.com/post/new-feature
New Feature: Faster Builds with Incremental Compilation
```
Test a saved channel anytime:
```bash
wachi test -n main
```
## Configuration
Config lives at `~/.config/wachi/config.yml` (XDG-compliant, default). Auto-created on first `wachi sub`.
`wachi` reads config in this order: `config.yml` -> `config.jsonc` -> `config.json`.
```yaml
# Channels and subscriptions (managed by wachi sub/unsub)
channels:
- name: "main"
apprise_url: "slack://xoxb-token/channel"
subscriptions:
- url: "https://blog.example.com"
rss_url: "https://blog.example.com/feed.xml"
```
Each channel entry requires `name`. Names must be unique (case-insensitive).
All fields are optional with sensible defaults. An empty config file is valid.
| Variable | Purpose |
|----------|---------|
| `WACHI_APPRISE_URL` | Override notification destination for ALL channels |
| `WACHI_ARCHIVE_ACCESS_KEY` | Optional Internet Archive access key for authenticated Wayback submissions |
| `WACHI_ARCHIVE_SECRET_KEY` | Optional Internet Archive secret key for authenticated Wayback submissions |
| `WACHI_CONFIG_PATH` | Custom config file path |
| `WACHI_DB_PATH` | Custom database path |
| `WACHI_NO_ARCHIVE` | Set to `1` to disable auto-archiving of notified URLs |
| `WACHI_NO_AUTO_UPDATE` | Set to `1` to disable auto-update |
Notified item URLs are archived to the Wayback Machine by default. If archive keys are unset,
`wachi` falls back to the anonymous save endpoint. Set both archive keys for authenticated POST
submissions and higher Wayback limits.
For `x.com` / `twitter.com` items, `wachi` archives the transformed notification URL (for example
`fixupx.com`) because Wayback currently blocks direct archiving of the original X URL.
## Design
- **Stateless checks** -- `wachi check` is a one-shot command. Bring your own scheduler (cron, crnd, systemd, launchd)
- **Dedup, not state** -- items tracked by `sha256(link + title + channel)`. If the hash exists, it was already sent
- **No interactive prompts** -- ever. Errors tell you exactly what to set and where (What / Why / Fix pattern)
- **Baseline seeding** -- on subscribe, all current items are pre-seeded so your channel isn't flooded
- **SQLite WAL mode** -- safe concurrent reads. Two cron jobs won't conflict
- **Atomic config writes** -- write to temp, then rename. No corruption from concurrent access
- **JSON envelope** -- `--json` on all commands returns `{"ok": true, "data": {...}}` or `{"ok": false, "error": {"what", "why", "fix"}}`
## Development
```bash
bun install
bun run src/index.ts --help
# Quality checks
bun run lint # Biome v2
bun run typecheck # tsgo
bun test # Bun test runner
bun run knip # Dead code detection
# Database migrations
bun run db:generate
```
### Tech Stack
| Component | Choice |
|-----------|--------|
| Runtime | Bun (`bun:sqlite`, `bun build --compile`) |
| Type checker | tsgo (`@typescript/native-preview`) |
| CLI | [citty](https://github.com/unjs/citty) |
| Database | [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) + bun:sqlite |
| HTTP | [ofetch](https://github.com/unjs/ofetch) |
| RSS | [rss-parser](https://github.com/rbren/rss-parser) |
| Notifications | [apprise](https://github.com/caronc/apprise) via uvx |
| Linter | [Biome](https://biomejs.dev/) v2 |
## License
MIT