{"id":49336174,"url":"https://github.com/olegiv/it-digest-bot","last_synced_at":"2026-04-27T01:01:35.689Z","repository":{"id":352317830,"uuid":"1214330281","full_name":"olegiv/it-digest-bot","owner":"olegiv","description":"Tiny Go service: Claude Code release watcher + daily AI news digest, posted to Telegram. systemd-timer-driven; single static binary.","archived":false,"fork":false,"pushed_at":"2026-04-26T04:56:44.000Z","size":149,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-26T06:25:25.335Z","etag":null,"topics":["ai-news","anthropic","claude-code","golang","news-aggregator","news-digest","rss","self-hosted","sqlite","static-binary","systemd","telegram-bot"],"latest_commit_sha":null,"homepage":"https://www.it-digest.info/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/olegiv.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-18T12:30:36.000Z","updated_at":"2026-04-26T04:56:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/olegiv/it-digest-bot","commit_stats":null,"previous_names":["olegiv/it-digest-bot"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/olegiv/it-digest-bot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olegiv%2Fit-digest-bot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olegiv%2Fit-digest-bot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olegiv%2Fit-digest-bot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olegiv%2Fit-digest-bot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/olegiv","download_url":"https://codeload.github.com/olegiv/it-digest-bot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olegiv%2Fit-digest-bot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32318417,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"ssl_error","status_checked_at":"2026-04-26T23:26:25.802Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["ai-news","anthropic","claude-code","golang","news-aggregator","news-digest","rss","self-hosted","sqlite","static-binary","systemd","telegram-bot"],"created_at":"2026-04-27T01:01:34.253Z","updated_at":"2026-04-27T01:01:35.613Z","avatar_url":"https://github.com/olegiv.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# it-digest-bot\n\n[![CI](https://github.com/olegiv/it-digest-bot/actions/workflows/ci.yml/badge.svg)](https://github.com/olegiv/it-digest-bot/actions/workflows/ci.yml) [![CodeQL](https://github.com/olegiv/it-digest-bot/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/olegiv/it-digest-bot/actions/workflows/github-code-scanning/codeql)\n\nPosts Claude Code and Go release announcements — plus daily AI news digests — to a Telegram channel. Single static Go binary, scheduled by **systemd timers** on plain Ubuntu. No Docker, no web server, no long-running daemon.\n\n## Architecture\n\n```\n  systemd timer (hourly)\n          │\n          ▼\n ┌─────────────────┐      HTTPS      ┌──────────────┐\n │  digest watch   │ ─────────────▶  │    npm       │   dist-tags.latest\n │ (one-shot run)  │ ◀─────────────  └──────────────┘\n │                 │      HTTPS      ┌──────────────┐\n │                 │ ─────────────▶  │    GitHub    │   latest release\n │                 │ ◀─────────────  │    API       │   notes\n │                 │                 └──────────────┘\n │                 │      HTTPS      ┌──────────────┐\n │                 │ ─────────────▶  │    go.dev    │   stable Go releases\n │                 │ ◀─────────────  │  dl + docs   │   + release history\n │                 │                 └──────────────┘\n │                 │      HTTPS      ┌──────────────┐\n │                 │ ─────────────▶  │   Telegram   │   sendMessage\n │                 │ ◀─────────────  │     Bot      │\n │                 │                 └──────────────┘\n │                 │                 ┌──────────────┐\n │                 │ ─ reads/writes─▶│    SQLite    │   releases_seen\n └─────────────────┘                 │  (file only) │   articles_seen\n                                     │              │   posts_log\n                                     └──────────────┘\n\n  systemd timer (08:00 Europe/Zurich)\n          │\n          ▼\n ┌─────────────────┐      HTTPS      ┌──────────────┐\n │  digest daily   │ ─────────────▶  │   6 feeds    │   parallel fetch (errgroup)\n │ (one-shot run)  │ ◀─────────────  │  (RSS/Atom)  │\n │                 │                 └──────────────┘\n │                 │      HTTPS      ┌──────────────┐\n │                 │ ─────────────▶  │  Anthropic   │   /v1/messages\n │                 │ ◀─────────────  │  (Claude)    │   rank + summarize\n │                 │                 └──────────────┘\n │                 │      HTTPS      ┌──────────────┐\n │                 │ ─────────────▶  │   Telegram   │   sendMessage × N\n │                 │ ◀─────────────  │     Bot      │   (split if \u003e 4096B)\n └─────────────────┘                 └──────────────┘\n```\n\nEach run exits on completion. journald stores the logs. Re-runs are idempotent because everything that mutates state is keyed off `releases_seen (package, version)` or `articles_seen.url_hash`.\n\n## Quick start (local dev)\n\n```bash\n# 1. build\nmake build\n\n# 2. copy and edit config\ncp config.example.toml config.toml\n\n# 3. set secrets in your shell\nexport TELEGRAM_BOT_TOKEN=123456:AA...\n# ANTHROPIC_API_KEY=sk-ant-...   (phase 2 only)\n\n# 4. render a fake release post to stdout (no network)\n./bin/digest post --dry-run --config config.toml\n\n# 5. apply SQLite migrations to a local db\n./bin/digest migrate --config config.toml\n\n# 6. do a real check against npm + GitHub + go.dev + Telegram\n./bin/digest watch --config config.toml\n```\n\n## Production install (Ubuntu 24.04 + systemd)\n\n```bash\n# On your dev machine\nmake build-linux-amd64\nscp bin/digest-linux-amd64 root@srv_prod:/usr/local/bin/digest\nssh root@srv_prod chmod 755 /usr/local/bin/digest\n\n# On the server (as root)\ncd /path/to/deploy\n./install.sh         # creates user, dirs, installs units, reloads systemd\n\n# Drop your TOML config\nsudo install -m 0640 -o root -g it-digest ./config.toml /etc/it-digest/config.toml\n\n# Write the env file with the secrets\nsudo install -m 0640 -o root -g it-digest /dev/null /etc/it-digest/env\nsudoedit /etc/it-digest/env\n#   TELEGRAM_BOT_TOKEN=...\n#   ANTHROPIC_API_KEY=...\n#   GITHUB_TOKEN=...      # optional, raises GitHub rate limit 60→5000 req/h\n\n# Apply schema\nsudo -u it-digest /usr/local/bin/digest migrate --config /etc/it-digest/config.toml\n\n# Start the timers\nsudo systemctl start it-digest-watch.timer\nsudo systemctl start it-digest-daily.timer\n\n# Watch\njournalctl -u it-digest-watch.service -f\njournalctl -u it-digest-daily.service -f\n```\n\n### Operations\n\nOnce the server is bootstrapped, five ops helpers live in `deploy/` and are also exposed as Makefile targets. Every helper resolves the target host from (in order): an explicit argument, the `HOST` env var, or `DEPLOY_HOST` — which is auto-loaded from `deploy/deploy.env` (gitignored). Copy `deploy/deploy.env.example` to `deploy/deploy.env` and set `DEPLOY_HOST` once; subsequent `make deploy`, `make status`, etc. work with no args. Bare hostnames default to `root@\u003chost\u003e`; prefix explicitly for a different user.\n\n| Makefile | Script | What it does |\n|---|---|---|\n| `make deploy` | `deploy/deploy.sh` | Run race tests (skip with `SKIP_TESTS=1`), build `bin/digest-linux-amd64`, preserve the current binary as `digest.prev`, scp the new one to `/usr/local/bin/digest`, chmod. Next timer fire uses the new binary. |\n| `make rollback` | `deploy/rollback.sh` | Swap `digest` ↔ `digest.prev` to revert a bad deploy. The failed binary is kept as `digest.failed` for inspection. |\n| `make backup` | `deploy/backup.sh` | `sqlite3 .backup` snapshot of `/var/lib/it-digest/state.db` → local `backups/state-\u003cUTC-stamp\u003e.db`. Requires `sqlite3` on the server. |\n| `make status` | `deploy/status.sh` | `systemctl list-timers` + binary version + last 20 journal lines for both services. |\n| `make dry-watch` | `deploy/dry-run.sh watch` | Run `digest watch --dry-run` on the server with the real env file loaded (no post, no DB write). |\n| `make dry-daily` | `deploy/dry-run.sh daily` | Run `digest daily --dry-run` on the server with the real env file loaded (no post, no DB write). |\n| `make uninstall` | `deploy/uninstall.sh` | Stop + disable timers, remove units, delete user + binary. Preserves `/etc/it-digest/` and `/var/lib/it-digest/`. Prompts unless given `--yes`. |\n\nA third systemd timer handles **daily server-side backups** automatically — no client interaction needed:\n\n| Component | Notes |\n|---|---|\n| `/usr/local/bin/it-digest-backup` | Script installed by `install.sh`. Runs `sqlite3 .backup` → gzip → `/var/backups/it-digest/state-\u003cUTC\u003e.db.gz`, prunes files older than `IT_DIGEST_BACKUP_KEEP_DAYS` (default 14). Requires `sqlite3` on the host. |\n| `it-digest-backup.service` | Oneshot, runs as `it-digest`, fully sandboxed (no network, read-only FS except `/var/backups/it-digest`). |\n| `it-digest-backup.timer` | `OnCalendar=*-*-* 03:00:00 Europe/Zurich`, `Persistent=true` — fires once a day, safely before the 08:00 daily digest. |\n\n`make backup HOST=x` (client-side) still works for ad-hoc local snapshots; the server-side timer is the automatic background safety net.\n\n## Subcommands\n\n| Command                 | What it does                                                       |\n|-------------------------|--------------------------------------------------------------------|\n| `digest watch`          | Check Claude Code and stable Go releases; post new versions.        |\n| `digest daily`          | Build and post the daily AI news digest via Claude.                |\n| `digest post --dry-run` | Render a sample release post to stdout without sending anything.   |\n| `digest migrate`        | Apply pending SQLite schema migrations.                            |\n| `digest config-check`   | Load and fully validate the config + env; exit nonzero on error.   |\n| `digest -version`       | Print version, commit, and build date.                             |\n\nAll subcommands accept `--config \u003cpath\u003e` (default: `config.toml`).\n\n## Configuration reference\n\nAll non-secret settings live in `config.toml`. See [`config.example.toml`](./config.example.toml) for a commented starter.\n\n| Section           | Key            | Required | Default                  | Notes |\n|-------------------|----------------|----------|--------------------------|-------|\n| `[telegram]`      | `channel`      | yes      | —                        | e.g. `@your_channel` |\n| `[telegram]`      | `admin_chat`   | no       | (falls back to `channel`) | Optional destination for OnFailure alerts |\n| `[database]`      | `path`         | yes      | —                        | SQLite file path |\n| `[claudecode]`    | `npm_package`  | yes      | `@anthropic-ai/claude-code` | |\n| `[claudecode]`    | `github_repo`  | yes      | `anthropics/claude-code` | |\n| `[llm]`           | `model`        | phase 2  | `claude-sonnet-4-6`      | |\n| `[llm]`           | `max_tokens`   | phase 2  | `1024`                   | |\n| `[log]`           | `level`        | no       | `info`                   | `debug` / `info` / `warn` / `error` |\n| `[log]`           | `format`       | no       | `json`                   | `json` / `text` |\n| `[[feed]]`        | `name`, `url`  | phase 2  | —                        | One block per feed |\n\nGo release monitoring has no TOML settings; `digest watch` reads official stable releases from `https://go.dev/dl/?mode=json`.\n\nSecrets come from the environment only (never the TOML):\n\n| Env var                | Required? | Purpose |\n|------------------------|-----------|---------|\n| `TELEGRAM_BOT_TOKEN`   | yes       | Any command that posts to Telegram |\n| `ANTHROPIC_API_KEY`    | yes (daily) | `digest daily` (phase 2) |\n| `GITHUB_TOKEN`         | optional  | Raises GitHub rate limit from 60→5000 req/hour; `public_repo` scope is enough |\n\n## Roadmap\n\n- [x] **Phase 1** — Release watchers (Claude Code via npm/GitHub, Go via go.dev → Telegram).\n- [x] **Phase 2** — Daily AI digest (RSS aggregation → Claude summarization → Telegram).\n\nBoth phases are fully implemented. The `daily` command fetches all configured feeds in parallel, dedupes against `articles_seen`, sends the 24h window to Claude via `POST /v1/messages` for ranking/summarization, renders a MarkdownV2 post grouped by source, and splits into chunks under Telegram's 4096-byte cap if needed.\n\nSee [CHANGELOG.md](./CHANGELOG.md) for release notes.\n\n## Development\n\n```bash\nmake help            # list Makefile targets\nmake all             # build the default local/dev binary\nmake build           # fast local/dev build\nmake build-prod      # optimized host production build\nmake build-linux-amd64  # optimized static linux/amd64 production build\nmake build-darwin-arm64 # optimized darwin/arm64 production build\nmake build-all-platforms # linux/amd64 + darwin/arm64 production builds\nmake install-tools   # golangci-lint + gofumpt at pinned versions\nmake test            # go test ./...\nmake test-race       # with -race\nmake coverage        # go test -cover ./...\nmake coverage-html   # write coverage.out + coverage.html\nmake lint            # golangci-lint\nmake check           # fmt-check + vet + lint + test\nmake deps            # go mod download\nmake tidy            # go mod tidy\nmake fmt             # gofumpt -w .\n```\n\nCI (GitHub Actions) runs build + vet + race tests + lint on every push and PR against `main`.\n\nTest coverage targets (spec): ≥ 70% on `internal/claudecode`, `internal/telegram`, `internal/config`. Current (all above target):\n\n| Package           | Coverage |\n|-------------------|----------|\n| claudecode        | ~82%     |\n| telegram          | ~79%     |\n| config            | ~77%     |\n| digest            | ~82%     |\n| llm               | ~79%     |\n| news              | ~91%     |\n\n## Contributing\n\nPRs welcome. Before opening one:\n\n1. `make check` and `make test-race` must pass.\n2. Keep commits logically scoped (one concern per commit).\n3. No `fmt.Println` or `log.Printf` — use `log/slog`.\n4. Errors wrapped with `fmt.Errorf(\"...: %w\", err)` — never bare.\n5. Context threaded through any function that does I/O.\n\n## License\n\nGPL-3.0. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folegiv%2Fit-digest-bot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Folegiv%2Fit-digest-bot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folegiv%2Fit-digest-bot/lists"}