# Filaxy Herald – Turn GitHub activity into build-in-public posts

> Source: <https://github.com/othmarodev/filaxy-herald>
> Published: 2026-06-24 18:23:19+00:00

**Your GitHub work → posts on X, automatically.**

Filaxy Herald watches your public repos. When you ship something, an AI writes a real
*build-in-public* post about it, makes a branded image, and sends it to your Telegram.
You tap ✅ — and it's live on X. You also get an `/announce`

command for private projects.

No "I pushed 3 commits" spam. Real posts. You approve every one. It runs itself.

`Self-hosted`

· `Runs free on Vercel`

· `MIT`

When you ship code, you get a message like this on Telegram. One tap and it's live — no dashboards, no copy-paste:

**↓ It really posts.** [See a real post it published on X →](https://x.com/Othmaro_dev/status/2069607575306330549)

[What it does](#what-it-does)[How it works (architecture)](#how-it-works-architecture)[Prerequisites](#prerequisites)[Setup — step by step](#setup--step-by-step)[Configuration](#configuration)[Usage](#usage)[Safety: it never posts secrets](#safety-it-never-posts-secrets)[What it costs](#what-it-costs)[Troubleshooting](#troubleshooting)[License](#license)

There are two ways posts get created:

**A — Automatic (your public repos).** Once a day, Herald reads your public GitHub
activity. If you shipped something worth sharing (a release, a new repo, or real
commits to `main`

), it drafts a post and sends it to you on Telegram for approval.
It posts **one suggestion per repo**, so you approve or discard each independently.

**B — Manual ( /announce, for anything — even private projects).** Send the bot a
message like

`/announce my-app: shipped a live dashboard with real-time charts`

. It
writes a polished post from your *description*(never from your code, so nothing private leaks). Attach a screenshot and it uses your real image instead of the generated card.

Every post goes through a **safety filter** (no API keys, secrets, or security bugs) and
**you approve it on Telegram** before it ever reaches X.

```
You ship code ─▶ GitHub
                   │  (public activity feed — read once a day, no private repos)
                   ▼
            ┌──────────────┐    "write the post"     ┌──────────┐
            │  The Engine  │ ──────────────────────▶ │  Claude  │
            │   (Vercel)   │ ◀────────────────────── │    AI    │
            └──────┬───────┘     the draft            └──────────┘
                   │
                   │  ① safety check (block secrets / bugs)
                   │  ② render a branded "ship card" image
                   │  ③ store the draft        ┌──────────┐
                   │ ─────────────────────────▶│  Upstash │ (memory)
                   ▼                            └──────────┘
              Telegram  ──(you tap ✅ Approve)──▶  X / Twitter  ──▶  your audience grows
```

**The Engine** is a few serverless functions on Vercel. A daily**cron** triggers the digest; a**webhook** receives your Telegram button taps.- It reads your activity from GitHub's
**public events feed**— no GitHub App, no webhook on GitHub's side, and private repos physically can't appear there. **Claude** writes the copy.**Upstash Redis** holds the pending draft between "drafted" and "approved".**Telegram** is your approval inbox.**X** is the destination.

| File | What it is |
|---|---|
`api/digest.ts` |
Daily cron: read activity → write → image → send to Telegram |
`api/telegram-webhook.ts` |
Handles your ✅/❌ taps and the `/announce` command → posts to X |
`api/announce.ts` |
Builds a post from your `/announce` description |
`lib/github.ts` |
Reads & filters your public activity |
`lib/compose.ts` |
Prompts Claude to write the post |
`lib/safety.ts` |
The content guardrail (regex + AI review) |
`lib/card-og.ts` |
Renders the branded image |
`lib/redis.ts` · `lib/telegram.ts` |
Memory + Telegram helpers |

You'll need free accounts on these (all have free tiers; two need a few dollars of prepaid credit):

| Service | Why | Cost |
|---|---|---|
|

[Telegram](https://telegram.org)[Anthropic](https://console.anthropic.com)**$5** prepaid lasts months[X / Twitter Developer](https://developer.x.com)**~$0.02/post**[Upstash](https://upstash.com)[Vercel](https://vercel.com)Also install [Node.js 18+](https://nodejs.org) and the
[Vercel CLI](https://vercel.com/docs/cli): `npm i -g vercel`

.

Then clone and install:

```
git clone https://github.com/othmarodev/filaxy-herald.git
cd filaxy-herald
npm install
cp .env.example .env.local
```

You'll fill `.env.local`

as you go through the steps below. **It's git-ignored — your
keys never get committed.**

This is where you'll approve posts.

- Open Telegram, search for
**@BotFather**(the one with the blue checkmark). - Send
`/newbot`

. Give it a name, then a username ending in`bot`

(e.g.`my_herald_bot`

). - BotFather replies with a
**token** like`8123456789:AAH...`

. →`TELEGRAM_BOT_TOKEN`

. - Now get your chat ID: search
**@userinfobot**, press** Start**. It replies with your`Id`

(a number). →`TELEGRAM_CHAT_ID`

. **Open your own bot**(`t.me/your_bot_username`

) and press**Start** once.*(A bot can't message you until you've messaged it first — skip this and you'll get "chat not found".)*

Test it:

```
npx tsx scripts/test-telegram.ts   # should send you a message
```

This writes your posts.

- Go to
[console.anthropic.com](https://console.anthropic.com), sign in. **API Keys**→** Create Key**→ name it`filaxy-herald`

. Copy the`sk-ant-...`

value (shown once). →`ANTHROPIC_API_KEY`

.**Plans & Billing**→** Buy credits**→ add**~$5**.*(The free "evaluation" plan has no usable credits — the key won't work until you add a little balance.)*

```
npx tsx scripts/test-compose.ts    # generates a real sample post
```

The fiddly one. Read carefully — there's one step that trips everyone.

- Go to
[developer.x.com](https://developer.x.com), sign in with the account you'll post from. Accept the**Free** developer plan. - You'll get a Project + an App. Open the App.
- 🚨
**Before generating any tokens:** open**User authentication settings → Set up**:** App permissions**→** Read and Write**.*(If you leave it on "Read", your tokens will be read-only and posting fails. This is THE gotcha.)***Type of App**→** Web App / Automated App or Bot**.** Callback URI**→`https://example.com/callback`

(placeholder is fine).**Website URL**→ your site (anything valid).- Save.

- Now go to
**Keys and tokens** and grab**four** values:**API Key**+** API Key Secret**→`X_API_KEY`

,`X_API_SECRET`

**Access Token**+** Access Token Secret**(Generate) →`X_ACCESS_TOKEN`

,`X_ACCESS_SECRET`

. Confirm it says**Read and Write** under the token. If it says "Read", fix step 3 and regenerate.

- The new X API is
**pay-per-use**. Add a few dollars of credits in the developer console so it can publish.

```
npx tsx scripts/test-x-auth.ts     # verifies the 4 keys (without posting)
```

The bot's short-term memory (holds a draft between "written" and "approved").

- Go to
[console.upstash.com](https://console.upstash.com), create a**Redis** database (free tier, pick a region). - In the database page, find the
**REST API** section. Copy:`UPSTASH_REDIS_REST_URL`

`UPSTASH_REDIS_REST_TOKEN`

```
vercel login
vercel link --yes          # creates the project
```

Add your environment variables to the Vercel project (Production). Either paste them in
the Vercel dashboard (**Project → Settings → Environment Variables**) or via CLI:

```
# for each variable: pipe the value from .env.local so it's never printed
grep '^TELEGRAM_BOT_TOKEN=' .env.local | cut -d= -f2- | vercel env add TELEGRAM_BOT_TOKEN production
# ...repeat for every var in .env.example...
```

Two extra variables to add:

```
# stops Vercel from downloading a browser it doesn't need in the cloud
printf '1' | vercel env add PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD production
# protects your cron endpoint (Vercel sends this automatically to the cron)
printf '%s' "$(openssl rand -hex 16)" | vercel env add CRON_SECRET production
# lets the webhook call the announce endpoint internally
printf '%s' "$(openssl rand -hex 16)" | vercel env add INTERNAL_SECRET production
```

Deploy:

```
vercel --prod
```

The daily cron is configured in `vercel.json`

(`0 15 * * *`

= 09:00 in UTC-6 / Costa Rica
— change it to your timezone).

So your button taps reach the cloud. Edit the URL in `scripts/set-webhook.ts`

to your
deployed domain, then:

```
npx tsx scripts/set-webhook.ts
```

Done. 🎉

All in `.env.local`

(and mirrored in your Vercel project). See `.env.example`

.

| Variable | What it is |
|---|---|
`TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` |
your approval bot + your chat |
`ANTHROPIC_API_KEY` |
Claude (writes posts + runs the AI safety check) |
`X_API_KEY` / `X_API_SECRET` / `X_ACCESS_TOKEN` / `X_ACCESS_SECRET` |
X OAuth 1.0a (Read+Write) |
`UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` |
memory |
`GITHUB_USER` |
the GitHub username to watch |
`ALLOWLIST` |
comma-separated `owner/repo` . Leave empty = all your public repos (skips your profile repo and forks). |
`CRON_SECRET` / `INTERNAL_SECRET` |
endpoint protection (random strings) |

**Automatic:** just ship code to your public repos. Each day at your cron time, Herald DMs you a draft per repo that had activity. Tap ✅ to post, ❌ to skip.**Manual:** message your bot`/announce <project>: <what you shipped>`

. Works for any project, public or private.**Attach a photo** to use a real screenshot instead of the generated card. Write in any language — the post comes out in English.

Every post — automatic or manual — runs through `lib/safety.ts`

before it can reach
approval:

**Regex backstop**— instantly blocks anything matching an API key, token, private key, Stripe/AWS/GitHub secret, or a security-bug disclosure.**AI reviewer**— Claude flags anything sensitive the regex might miss (secrets, vulnerabilities, private data, brand-damaging content). Fails closed (blocks on doubt).

And of course: it only ever reads your **public** GitHub feed, and `/announce`

posts from
your **description**, never your code. Private repos can't leak.

```
npx tsx scripts/test-safety.ts     # see the guardrail block fake secrets
```

| Item | Per post |
|---|---|
| X (publish) | ~$0.02 |
| Claude (writing + safety check) | < $0.01 |
| Branded image / screenshot | $0 |
| Hosting (Vercel Hobby) | $0 |
Total |
~2–3 cents |

One post a day ≈ **under $1/month**. A $5 X balance ≈ 250 posts. Set a spending limit in
the X console for peace of mind.

| Symptom | Fix |
|---|---|
Telegram: "chat not found" |
Open your bot and press Start once (step 1.5). |
Anthropic: "credit balance too low" |
Add ~$5 in Plans & Billing — the free plan has no credits. |
X: tokens are read-only / posting 403 |
Set Read and Write in User auth settings, then regenerate the access token. |
X: "CreditsDepleted" |
Add credits in the X developer console (pay-per-use). |
Webhook returns 500 / ERR_MODULE_NOT_FOUND |
Vercel Node functions need self-contained imports — keep `api/telegram-webhook.ts` dependency-light (it is, by design). |
Cron runs but nothing posts |
That's correct if you had no post-worthy public activity that day — it never invents posts. |
The `/announce` post links the wrong place |
Private projects link to your GitHub profile by design (never the private repo). |

[MIT](/othmarodev/filaxy-herald/blob/main/LICENSE) © Othmaro Fallas Rojas — [@Othmaro_dev](https://x.com/Othmaro_dev) ·
[github.com/othmarodev](https://github.com/othmarodev)

Built in public. This bot announces its own updates. 🤖
