{"id":44921888,"url":"https://github.com/ysm-dev/wachi","last_synced_at":"2026-04-19T03:03:49.357Z","repository":{"id":339020286,"uuid":"1160146470","full_name":"ysm-dev/wachi","owner":"ysm-dev","description":"Subscribe any link and get notified on change","archived":false,"fork":false,"pushed_at":"2026-04-11T08:13:27.000Z","size":22135,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T10:29:03.480Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ysm-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-17T15:45:12.000Z","updated_at":"2026-04-11T08:13:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ysm-dev/wachi","commit_stats":null,"previous_names":["ysm-dev/wachi"],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/ysm-dev/wachi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysm-dev%2Fwachi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysm-dev%2Fwachi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysm-dev%2Fwachi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysm-dev%2Fwachi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ysm-dev","download_url":"https://codeload.github.com/ysm-dev/wachi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysm-dev%2Fwachi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31992822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-18T03:18:51.672Z","updated_at":"2026-04-19T03:03:49.345Z","avatar_url":"https://github.com/ysm-dev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wachi\n\n[![npm version](https://img.shields.io/npm/v/wachi)](https://www.npmjs.com/package/wachi)\n[![CI](https://github.com/ysm-dev/wachi/actions/workflows/release.yml/badge.svg)](https://github.com/ysm-dev/wachi/actions)\n[![license](https://img.shields.io/npm/l/wachi)](https://github.com/ysm-dev/wachi/blob/main/LICENSE)\n\n**Monitor RSS feeds and get notified on new content.**\n\nwachi 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.\n\n- **Zero config for RSS** -- point at a blog, wachi finds the feed\n- **90+ notification services** -- Slack, Discord, Telegram, email, and [more](https://github.com/caronc/apprise/wiki)\n- **Stateless by design** -- `wachi check` is a one-shot command, perfect for cron\n- **No interactive prompts** -- built for automation and AI agents\n\n## Install\n\n```bash\n# ephemeral run\nnpx wachi@latest --help\nbunx wachi@latest --help\n\n# persistent global install\nnpm i -g wachi\nbun install -g wachi\n\n# standalone binary (macOS/Linux)\ncurl -fsSL https://raw.githubusercontent.com/ysm-dev/wachi/main/install.sh | sh\n\n# standalone binary (Windows PowerShell)\npowershell -ExecutionPolicy Bypass -c \"irm https://raw.githubusercontent.com/ysm-dev/wachi/main/install.ps1 | iex\"\n\n# homebrew\nbrew tap ysm-dev/tap \u0026\u0026 brew install wachi\n```\n\n## Quick Start\n\n```bash\n# 1. Create a named channel and subscribe a URL (auto-discovers RSS)\nwachi sub -n main -a \"slack://xoxb-token/channel\" \"https://blog.example.com\"\n\n# 2. Check for new content (run this on a schedule)\nwachi check\n\n# That's it. New posts get pushed to your Slack channel.\n```\n\n## How It Works\n\n```\nwachi sub -n \u003cname\u003e [-a \u003capprise-url\u003e] \u003curl\u003e\n      │\n      ▼\n  Is it RSS? ───yes───▶ Store as RSS subscription\n      │no\n      ▼\n  Auto-discover RSS ───found───▶ Store URL + discovered feed\n  (link tags, common paths)\n```\n\nOn `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.\n\n## Commands\n\n```\nwachi sub -n \u003cname\u003e \u003curl\u003e         Subscribe a URL to a named channel\n  -a, --apprise-url \u003curl\u003e         Required when creating a new channel\n  -e, --send-existing             Send all current items on next check (skip baseline)\n\nwachi unsub -n \u003cname\u003e [url]       Unsubscribe a URL or remove entire channel\n\nwachi ls                          List all channels and subscriptions\n\nwachi check                       Check all subscriptions for changes\n  -n, --name \u003cname\u003e               Check specific channel only\n  -p, --concurrency \u003cnumber\u003e      Max concurrent checks (default: 10)\n  -d, --dry-run                   Preview without sending or recording\n\nwachi test -n \u003cname\u003e              Send a test notification\n\nwachi upgrade                     Update a persistent wachi install\n```\n\n`wachi upgrade` follows the original install method:\n\n- npm global -\u003e `npm install -g wachi@latest`\n- bun global -\u003e `bun install -g wachi@latest`\n- Homebrew -\u003e `brew upgrade wachi`\n- standalone binary -\u003e downloads the latest GitHub Release and replaces the current binary\n\nEphemeral runs via `npx` and `bunx` are not persistent installs, so they are not upgraded in place. Re-run them with `@latest` instead.\n\n**Global flags:** `--json` / `-j` for machine-readable output, `--verbose` / `-V` for detailed logs, `--config` / `-C` for custom config path.\n\n## Examples\n\n```bash\n# Blog (auto-discovers RSS)\nwachi sub -n main -a \"slack://xoxb-token/channel\" \"https://blog.example.com\"\n\n# GitHub releases RSS feed\nwachi sub -n alerts -a \"discord://webhook-id/token\" \"https://github.com/ysm-dev/wachi/releases.atom\"\n\n# Add another subscription to an existing channel name\nwachi sub -n main \"https://example.com/changelog\"\n\n# YouTube channel\nwachi sub -n media -a \"tgram://bot-token/chat-id\" \"https://youtube.com/@channel\"\n\n# URL without https:// (auto-prepended)\nwachi sub -n main \"blog.example.com\"\n\n# Send all existing items on next check (no baseline)\nwachi sub -n alerts -e \"https://github.com/ysm-dev/wachi/releases.atom\"\n\n# Dry-run: see what would be sent\nwachi check -d\n\n# Check specific channel only\nwachi check -n main\n\n# Run every 5 minutes with crnd\ncrnd \"*/5 * * * *\" wachi check\n\n# System cron\ncrontab -e\n# */5 * * * * wachi check\n```\n\n## Notifications\n\nwachi 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).\n\nEach new item is sent as a separate message:\n\n```\nhttps://blog.example.com/post/new-feature\n\nNew Feature: Faster Builds with Incremental Compilation\n```\n\nTest a saved channel anytime:\n\n```bash\nwachi test -n main\n```\n\n## Configuration\n\nConfig lives at `~/.config/wachi/config.yml` (XDG-compliant, default). Auto-created on first `wachi sub`.\n\n`wachi` reads config in this order: `config.yml` -\u003e `config.jsonc` -\u003e `config.json`.\n\n```yaml\n# Channels and subscriptions (managed by wachi sub/unsub)\nchannels:\n  - name: \"main\"\n    apprise_url: \"slack://xoxb-token/channel\"\n    subscriptions:\n      - url: \"https://blog.example.com\"\n        rss_url: \"https://blog.example.com/feed.xml\"\n```\n\nEach channel entry requires `name`. Names must be unique (case-insensitive).\n\nAll fields are optional with sensible defaults. An empty config file is valid.\n\n| Variable | Purpose |\n|----------|---------|\n| `WACHI_APPRISE_URL` | Override notification destination for ALL channels |\n| `WACHI_ARCHIVE_ACCESS_KEY` | Optional Internet Archive access key for authenticated Wayback submissions |\n| `WACHI_ARCHIVE_SECRET_KEY` | Optional Internet Archive secret key for authenticated Wayback submissions |\n| `WACHI_CONFIG_PATH` | Custom config file path |\n| `WACHI_DB_PATH` | Custom database path |\n| `WACHI_NO_ARCHIVE` | Set to `1` to disable auto-archiving of notified URLs |\n| `WACHI_NO_AUTO_UPDATE` | Set to `1` to disable auto-update |\n\nNotified item URLs are archived to the Wayback Machine by default. If archive keys are unset,\n`wachi` falls back to the anonymous save endpoint. Set both archive keys for authenticated POST\nsubmissions and higher Wayback limits.\n\nFor `x.com` / `twitter.com` items, `wachi` archives the transformed notification URL (for example\n`fixupx.com`) because Wayback currently blocks direct archiving of the original X URL.\n\n## Design\n\n- **Stateless checks** -- `wachi check` is a one-shot command. Bring your own scheduler (cron, crnd, systemd, launchd)\n- **Dedup, not state** -- items tracked by `sha256(link + title + channel)`. If the hash exists, it was already sent\n- **No interactive prompts** -- ever. Errors tell you exactly what to set and where (What / Why / Fix pattern)\n- **Baseline seeding** -- on subscribe, all current items are pre-seeded so your channel isn't flooded\n- **SQLite WAL mode** -- safe concurrent reads. Two cron jobs won't conflict\n- **Atomic config writes** -- write to temp, then rename. No corruption from concurrent access\n- **JSON envelope** -- `--json` on all commands returns `{\"ok\": true, \"data\": {...}}` or `{\"ok\": false, \"error\": {\"what\", \"why\", \"fix\"}}`\n\n## Development\n\n```bash\nbun install\nbun run src/index.ts --help\n\n# Quality checks\nbun run lint          # Biome v2\nbun run typecheck     # tsgo\nbun test              # Bun test runner\nbun run knip          # Dead code detection\n\n# Database migrations\nbun run db:generate\n```\n\n### Tech Stack\n\n| Component | Choice |\n|-----------|--------|\n| Runtime | Bun (`bun:sqlite`, `bun build --compile`) |\n| Type checker | tsgo (`@typescript/native-preview`) |\n| CLI | [citty](https://github.com/unjs/citty) |\n| Database | [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) + bun:sqlite |\n| HTTP | [ofetch](https://github.com/unjs/ofetch) |\n| RSS | [rss-parser](https://github.com/rbren/rss-parser) |\n| Notifications | [apprise](https://github.com/caronc/apprise) via uvx |\n| Linter | [Biome](https://biomejs.dev/) v2 |\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysm-dev%2Fwachi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fysm-dev%2Fwachi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysm-dev%2Fwachi/lists"}