A tiny Eve agent that recaps last night and previews tonight every morning at 9am ET via iMessage (Linq). Silent when there's nothing to say.
agent/
├── instructions.md # voice — terse, opinionated, Mets POV
├── agent.ts # model config (Vercel AI Gateway)
├── channels/
│ ├── eve.ts # HTTP channel for dev QA (curl / TUI)
│ ├── linq.ts # iMessage via Linq (Vercel Connect)
│ └── daily-digest.ts # internal channel the 9am cron talks to
├── lib/
│ ├── live-monitor.ts # poll MLB + push scoring/final updates
│ └── live-subscriptions.ts
├── tools/
│ ├── get_mets_game.ts # MLB Stats API (Mets = team 121)
│ ├── get_mets_club.ts # standings, last-ten, streak, transactions
│ └── broadcast_daily_digest.ts
└── schedules/
├── daily.ts # 9:00 AM ET
└── live-monitor.ts # every minute while subs are active
The morning cron starts a turn on daily-digest. Howie calls get_mets_game and get_mets_club, then broadcast_daily_digest texts the digest to configured iMessage recipients. When there's a game today, the digest ends with a LIVE opt-in line. Users who reply LIVE get a reminder 10 min before first pitch, scoring plays, and the final. Reply STOP to cancel.
- Node 24.x
- A Vercel account (deployment, AI Gateway OIDC)
- A Linq line via
eve add channel/linq
npm installHowie uses Eve's first-class Linq channel. Vercel Connect holds the API key. The webhook is /eve/v1/linq.
This project already has connector linq/howie on the dedicated line +1 (205) 396-6998. To recreate that on a new project:
npx eve add channel/linq --yesComplete the browser Connect flow so Eve can create or link a Linq account and number.
Env vars:
IMESSAGE_RECIPIENTS=+1XXXXXXXXXX,+1YYYYYYYYYY # who gets the 9am digest (E.164)
IMESSAGE_ALLOW_FROM=+1XXXXXXXXXX # optional inbound allow list
LINQ_CONNECT_UID=linq/howie # optional; this is the default
Only numbers in IMESSAGE_ALLOW_FROM can text Howie back. If you omit it, Howie uses IMESSAGE_RECIPIENTS (then legacy IMESSAGE_RECIPIENT). There is no open-to-the-world default.
Manual use: text Howie at +12053966998. Example: "what's the Mets game tonight?"
Live opt-ins are stored in Upstash Redis (via the Vercel Marketplace). In local dev without Redis env vars, subscriptions fall back to .eve/live-subscriptions.json.
One-time setup from the project root (requires vercel login and vercel link first):
npm run setup:redisThat runs vercel integration add upstash/upstash-kv, provisions a Redis database, wires it to this project, and pulls env vars into .env.local. The integration sets KV_REST_API_URL and KV_REST_API_TOKEN (the SDK also accepts UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN).
To refresh env vars later:
npm run env:pullThe default model is openai/gpt-5.4-mini via the Vercel AI Gateway.
For local eve dev, set one of:
AI_GATEWAY_API_KEYfrom the Vercel AI Gateway, orvercel linkin this project (OIDC token pulled automatically)
On Vercel production, link the project and the gateway authenticates via OIDC — no API key in env.
Run these in order before deploying.
npm install
npm run typecheck
npm run buildeve build should succeed and list daily under schedules.
Start the dev server:
npm run devIn another terminal, trigger a session on the Eve HTTP channel:
curl -X POST http://127.0.0.1:3000/eve/v1/session \
-H 'content-type: application/json' \
-d '{"message":"Call get_mets_game for 2025-09-15 and summarize in one line."}'Copy the sessionId from the JSON response, then watch the stream:
curl -N http://127.0.0.1:3000/eve/v1/session/<sessionId>/streamConfirm the agent calls get_mets_game and returns a sensible recap.
This runs the same path production cron uses and texts the digest:
curl -X POST http://127.0.0.1:3000/eve/v1/dev/schedules/dailyResponse example:
{ "scheduleId": "daily", "sessionIds": ["..."] }Watch the stream for that session id. You should see tool calls to get_mets_game, then a short assistant message. Check your configured iMessage recipients for the digest.
To test a specific date pair without waiting for real calendar days, temporarily edit the prompt in agent/schedules/daily.ts with known game dates (e.g. a 2025 postseason date), re-run Step 3, then revert.
Trigger the schedule on a date when the Mets have no game yesterday or today (or edit the prompt to use two off-season dates). The agent should end without an assistant message — nothing should be sent.
curl http://127.0.0.1:3000/eve/v1/healthvercel linkIn the Vercel project Settings → Environment Variables, add for Production (and Preview if you want):
| Variable | Value |
|---|---|
IMESSAGE_RECIPIENTS |
comma-separated +1… digest recipients |
IMESSAGE_ALLOW_FROM |
optional inbound allow list; defaults to recipients |
LINQ_CONNECT_UID |
linq/howie (optional) |
KV_REST_API_URL |
auto-set by npm run setup:redis |
KV_REST_API_TOKEN |
auto-set by npm run setup:redis |
Do not commit .env. AI Gateway auth on Vercel is via OIDC after link — no gateway key required in prod.
vercel deploy --prodOr push to a Git-connected Vercel project.
In Vercel Settings → Cron Jobs, confirm jobs exist for:
0 13 * * *— daily digest (9:00 AM EDT)* * * * *— live monitor (polls MLB when users are subscribed)
In November when the US falls back to EST, change agent/schedules/daily.ts to 0 14 * * * and redeploy.
curl https://<your-app>/eve/v1/healthOptionally drive the live deployment with the Eve TUI:
npx eve dev https://<your-app>After 9am ET, check Observability → Cron Jobs and Logs in Vercel. Confirm the run started a session and the digest arrived on iMessage.
- Daylight saving. Cron is UTC.
0 13 * * *is 9am EDT (summer). In November, switch to0 14 * * *for 9am EST. - No skip risk. The recap runs at 9am the next morning, so last night's game is always Final.
- Scope. Howie only covers Mets baseball — off-topic messages get a short refusal before the model runs.
- Dedicated iMessage line. Howie texts from
+12053966998through Linq. Replies from unknown numbers are dropped. - Live updates. Reply
LIVEafter the morning digest (or anytime on game day). ReplySTOPto cancel. The monitor cron runs every minute but exits immediately when nobody is subscribed. - Beta. Eve is in public preview — expect framework changes.