[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-836":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":9,"rankLanguage":9,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":9,"pushedAt":9,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":15,"starSnapshotCount":15,"syncStatus":13,"lastSyncTime":28,"discoverSource":29},836,"boop-agent","raroque\u002Fboop-agent","raroque","iMessage personal agent: choose Claude Agent SDK (Claude Code) or Codex app-server runtime (Codex\u002FChatGPT), with memory, sub-agents, automations, integrations.",null,"TypeScript",846,195,2,4,0,7,13,65,21,10.88,"MIT License",false,"main",[],"2026-06-12 02:00:19","\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fboop.gif\" alt=\"Boop\" width=\"220\" \u002F>\n\u003C\u002Fp>\n\n# Boop\n\nAn iMessage-based personal agent built on top of the [Claude Agent SDK](https:\u002F\u002Fdocs.claude.com\u002Fen\u002Fapi\u002Fagent-sdk\u002Foverview).\n\n📺 **Watch the walkthrough:** [YouTube — How I built Boop](https:\u002F\u002Fyoutu.be\u002FZpmKjDDbqHs)\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fimessage.jpg\" alt=\"Boop replying inside iMessage\" width=\"320\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Boop in action — text it like a person, get back an answer with full context.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n> **This is a starting point, not a finished product.**\n> It's the architecture I built for my own personal agent, opened up as a template so you can take it, text-enable your own Claude, and extend it however you want. Integrations are plugged in via [Composio](https:\u002F\u002Fcomposio.dev\u002F?utm_source=chris&utm_medium=youtube&utm_campaign=collab) — drop in an API key and connect Gmail, Slack, GitHub, Linear, Notion, and ~1000 others straight from the debug dashboard.\n\n```\n iMessage  →  Sendblue webhook  →  Interaction agent  →  Sub-agents (per task)\n                                          │                    │\n                                          ▼                    ▼\n                                    Memory store  ←──  Integrations (your MCP tools)\n```\n\nBuilt on:\n- [Claude Agent SDK](https:\u002F\u002Fgithub.com\u002Fanthropics\u002Fclaude-agent-sdk-typescript) — the loop, tool use, sub-agents, MCP\n- [Composio](https:\u002F\u002Fcomposio.dev\u002F?utm_source=chris&utm_medium=youtube&utm_campaign=collab) — integrations layer. One API key = Gmail, Slack, GitHub, Linear, Notion, Stripe, Supabase, + ~1000 more with hosted OAuth\n- [Sendblue](https:\u002F\u002Fsendblue.com\u002F?utm_source=raroque) — iMessage in\u002Fout (free on their agent plan)\n- [Convex](https:\u002F\u002Fconvex.link\u002Fchrisraroque) — real-time database for memory, agents, drafts\n- Your [Claude Code](https:\u002F\u002Fclaude.com\u002Fcode?ref=chrisraroque) subscription — no separate Anthropic API key required\n\n---\n\n## What you get\n\n- **iMessage in \u002F iMessage out** via Sendblue (with typing indicators and webhook dedup).\n- **Sendblue CLI integration** — `npm run dev` auto-registers the inbound webhook for you every restart (no re-pasting into the dashboard when free ngrok rotates your URL).\n- **Dispatcher + workers** pattern: a lean interaction agent decides what to do, spawns focused sub-agents that actually do the work.\n- **Pure dispatcher** — the interaction agent has only memory + spawn + automation + draft tools. Web access, files, and integrations are explicitly denied to it; sub-agents get `WebSearch` \u002F `WebFetch` \u002F the integrations.\n- **Tiered memory** (short \u002F long \u002F permanent) with post-turn extraction, decay, and cleaning.\n- **Vector search** for recall when you add an embeddings key (Voyage or OpenAI) — falls back to substring.\n- **Memory consolidation** — a daily 3-phase adversarial pipeline (proposer → adversary → judge) that merges duplicates, resolves contradictions, and prunes noise. Proposer and judge on Sonnet; adversary on Haiku for cheap skepticism. Runs every 24h by default, also triggerable manually via `POST \u002Fconsolidate`.\n- **Automations** — the agent can schedule recurring work from a text (\"every morning at 8 summarize my calendar\") and push results back to iMessage.\n- **Draft-and-send** — any external action stages a draft first; the agent only commits when the user confirms.\n- **Heartbeat + retry** — stuck agents auto-fail, debug dashboard can retry.\n- **Composio-powered integrations** — one API key unlocks 1000+ toolkits. Connect Gmail, Slack, GitHub, Linear, Notion, Drive, HubSpot, etc. with a click from the debug dashboard. Composio handles OAuth + token refresh.\n- **Debug dashboard** (React + Vite) with a Boop mascot — Dashboard (spend + tokens + agent status), Agents (timeline + integration logos), Automations, Memory (table + force-directed graph), Events, Connections.\n- **Convex** for persistence — real-time, typed, free tier.\n- **Uses your Claude Code subscription** — no separate Anthropic API key required.\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fagents-view.jpg\" alt=\"Agents view in the Boop debug dashboard\" width=\"900\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Agents tab — every spawned sub-agent with status, cost, tokens, turns, runtime, and the integrations it touched.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fautomations.jpg\" alt=\"Automations view in the Boop debug dashboard\" width=\"900\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Automations tab — schedule recurring jobs from a text (\"every morning at 8 summarize my calendar\") and watch them run.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fmemory-graph.jpg\" alt=\"Memory graph in the Boop debug dashboard\" width=\"900\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Memory tab — force-directed graph of clustered memories across short, long, and permanent tiers. Tabular view also available.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fconnections.jpg\" alt=\"Connections view in the Boop debug dashboard\" width=\"900\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Connections tab — Composio toolkits with OAuth handled for you. Click Connect and the agent can use it on the next message.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\n---\n\n## Heads up before you use this\n\n- **This was never meant to be open-sourced.** I built it for personal use and decided to share the architecture after enough people asked. It's not a product.\n- **Not optimized for cost or security.** Use at your own risk. Review the code, set your own budgets, and don't trust it with anything you wouldn't trust yourself with.\n- **I'm open to PRs for optimizations** — performance, bug fixes, DX improvements, new example integrations, better docs.\n\n---\n\n## Why is it named Boop?\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fluna.jpeg\" alt=\"Luna\" width=\"220\" \u002F>\n  \u003Cbr>\n  \u003Csub>\u003Cem>Luna, the inspiration.\u003C\u002Fem>\u003C\u002Fsub>\n\u003C\u002Fp>\n\nBoop is meant to be a proactive agent — one that nudges you over iMessage with reminders, drafts, and little follow-ups. A small \"boop\" whenever it has something for you.\n\nAnd it's named after my dog, Luna, who gives plenty of them.\n\n---\n\n## A note on the native iOS app\n\nI'm working on open-sourcing the native iOS app I originally built for this. The rewrite is taking much longer to get right than I'd hoped, but it will happen. I don't personally use it anymore — but enough people have asked, and I want to make it happen.\n\nIf you want to see what it looked like before I transitioned to an iMessage-based agent, here's [the walkthrough on YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_h2EnRfxMQE).\n\n---\n\n## Prerequisites\n\nYou need accounts for these. Keep the tabs open — setup will ask for credentials from each.\n\n> **You should be able to get away with the free plan for each service (except Claude Code), and I'm working to secure discounts for you guys on the pro plans. If you work at any of these companies, please reach out!**\n\n| Service | Why | Free? | Discount code |\n|---|---|---|---|\n| [Claude Code](https:\u002F\u002Fclaude.com\u002Fcode?ref=chrisraroque) | Powers the agent. Install it, sign in once, the SDK uses your session. | Subscription required | Working on getting one (if you work here, please reach out!) |\n| [Sendblue](https:\u002F\u002Fsendblue.com\u002F?utm_source=raroque) | iMessage bridge. Get a number, grab API keys. | Free on their agent plan | `RAROQUE20` — 20% off for 6 months (helpful if you plan to commercialize) |\n| [Convex](https:\u002F\u002Fconvex.link\u002Fchrisraroque) | Database + realtime. | Free tier is plenty | Working on getting one (in touch with them 👀) |\n| [Composio](https:\u002F\u002Fcomposio.dev\u002F?utm_source=chris&utm_medium=youtube&utm_campaign=collab) | Integrations — one API key unlocks ~1000 toolkits. Optional if you just want chat + memory + automations without third-party access. | Free tier covers personal use | `CHRISXCOMPOSIO` — 1 month free on starter plan |\n| [ngrok](https:\u002F\u002Fngrok.com?ref=chrisraroque) or similar | Expose your local port so Sendblue can reach it. | Free tier works | Working on getting one (if you work here, please reach out!) |\n\n**Custom integrations welcome.** Composio covers the common catalog, but you're free to add your own MCP servers under `server\u002Fintegrations\u002F` and register them in `server\u002Fintegrations\u002Fregistry.ts` — the dispatcher treats them the same as Composio-backed ones (just named toolkits the execution agent can spawn against). Useful for in-house APIs, local tools, or anything Composio doesn't ship.\n\n---\n\n## Quickstart\n\n```bash\n# 1. Clone + install\ngit clone https:\u002F\u002Fgithub.com\u002Fraroque\u002Fboop-agent.git\ncd boop-agent\nnpm install\n\n# 2. Install Claude Code (one-time, global) and sign in\nnpm install -g @anthropic-ai\u002Fclaude-code\nclaude  # sign in, then Ctrl-C to exit\n\n# 3. Interactive setup — writes .env.local, creates Convex deployment\nnpm run setup\n\n# 4. Install ngrok (one-time) and authorize it\nbrew install ngrok\n# or grab from https:\u002F\u002Fngrok.com\u002Fdownload\nngrok config add-authtoken \u003Cyour-token>   # free at https:\u002F\u002Fdashboard.ngrok.com\n\n# 5. Start everything with one command — server, Convex, debug UI, and ngrok\nnpm run dev\n```\n\n`npm run dev` prints color-prefixed output from all four processes and shows a banner with your ngrok webhook URL once the tunnel is live.\n\n```\nPublic URL:        https:\u002F\u002F\u003Cabc123>.ngrok.app\nSendblue webhook:  https:\u002F\u002F\u003Cabc123>.ngrok.app\u002Fsendblue\u002Fwebhook\n```\n\nOn free ngrok, **the webhook auto-registers with Sendblue every boot** — no manual paste needed. For stable URLs (ngrok reserved or Cloudflare Tunnel), set the webhook once in the dashboard.\n\nText your Sendblue-provisioned number from a **different** phone. The agent replies.\n\n> **⚠ ngrok free plan gives you a new URL every time.** That means every time you restart `npm run dev`, your Sendblue webhook URL is dead until you paste the new one in.\n>\n> If you're going to run this for more than a quick demo, **strongly recommend one of:**\n> - **ngrok paid plan** — gives you a reserved domain that stays the same forever\n> - **[Cloudflare Tunnel](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fcloudflare-one\u002Fconnections\u002Fconnect-networks\u002F)** — free, stable subdomain, a bit more setup\n> - Any other tunnel with a static URL (Tailscale Funnel, localtunnel reserved, etc.)\n>\n> If you use a non-ngrok tunnel, point it at `localhost:3456` yourself — `npm run dev` will still run the rest, just ignore its ngrok output and use your tunnel's URL.\n\n> **Gotcha:** `SENDBLUE_FROM_NUMBER` must be your Sendblue-provisioned number (the one people text TO), not your personal cell. Sendblue's API requires it, and misconfiguring it returns either \"missing required parameter: from_number\" or \"Cannot send messages to self\".\n>\n> **Fix in one command:** `npm run sendblue:sync` pulls the right number from the Sendblue CLI and writes it to `.env.local`.\n\n---\n\n## How the Sendblue integration works\n\nBoop uses the [Sendblue CLI](https:\u002F\u002Fgithub.com\u002Fsendblue-api\u002Fsendblue-cli) (`@sendblue\u002Fcli`) to eliminate almost all manual dashboard work. Three NPM scripts wrap it:\n\n| Command | What it does |\n|---|---|\n| `npm run setup` | Interactive. Offers to run `sendblue login` \u002F `sendblue setup` and pulls `api_key_id` + `api_secret_key` from `sendblue show-keys` into `.env.local`. |\n| `npm run sendblue:sync` | Runs `sendblue lines`, parses your provisioned phone number, and writes `SENDBLUE_FROM_NUMBER` to `.env.local` in E.164 format. Run this anytime your number changes or got set wrong. |\n| `npm run sendblue:webhook -- \u003Curl>` | Runs `sendblue webhooks list`, removes stale ngrok\u002Ftunnel hooks, and adds `\u003Curl>` as a `type=receive` inbound webhook. Called automatically by `npm run dev`. |\n\n### The `npm run dev` lifecycle\n\n```\n 1. Preflight: confirm convex\u002F_generated\u002F exists (else prompt to run setup).\n 2. Spawn four children in parallel, each with a prefixed log stream:\n       server │   (tsx watch server\u002Findex.ts)\n       convex │   (npx convex dev — pushes schema + functions)\n       debug  │   (vite dev server on :5173)\n       ngrok  │   (if installed AND no static URL) exposes :PORT\n 3. Wait for all four readiness signals:\n       server → \"listening on :PORT\"\n       convex → \"Convex functions ready\"\n       debug  → \"Local:  http:\u002F\u002Flocalhost:5173\u002F\"\n       ngrok  → tunnel URL visible at http:\u002F\u002F127.0.0.1:4040\n 4. Auto-register the webhook (FREE ngrok only, not reserved domains):\n       webhook │ [webhook] removed stale https:\u002F\u002Fold.ngrok-free.app\u002Fsendblue\u002Fwebhook\n       webhook │ [webhook] registered https:\u002F\u002Fnew.ngrok-free.app\u002Fsendblue\u002Fwebhook (type=receive)\n 5. Show the banner with dashboard + public URL + your Sendblue number.\n```\n\nThe banner will look like:\n\n```\n════════════════════════════════════════════════════════════════════\n  Boop is ready — ngrok tunnel is live  (webhook auto-registered).\n\n  🐶 Debug dashboard (click me):   http:\u002F\u002Flocalhost:5173\n  🌐 Public URL:                   https:\u002F\u002Fabc123.ngrok-free.app\n  📮 Sendblue webhook (inbound):   https:\u002F\u002Fabc123.ngrok-free.app\u002Fsendblue\u002Fwebhook\n  📱 Text this Sendblue number:    +13053369541  (from a DIFFERENT phone)\n════════════════════════════════════════════════════════════════════\n```\n\n### When auto-register fires vs when it doesn't\n\n| Setup | Auto-register fires? | Why |\n|---|---|---|\n| Free ngrok (default) | **Yes**, every boot | URL rotates; dashboard would be stale otherwise |\n| Reserved `NGROK_DOMAIN` | No | URL is stable; configure once in Sendblue dashboard |\n| Static `PUBLIC_URL` (Cloudflare Tunnel etc.) | No | Same reason |\n| `SENDBLUE_AUTO_WEBHOOK=false` | No | Manual opt-out |\n\n### What you'll see in the server logs during a conversation\n\nWhen someone texts your Sendblue number, expect this sequence in your terminal:\n\n```\nserver │ [turn a3f21d] ← +14155551234: \"what's on my calendar today?\"\nserver │ [turn a3f21d] tool: recall({\"query\":\"calendar today\"})\nserver │ [turn a3f21d] tool: spawn_agent({\"integrations\":[\"google-calendar\"],\"task\":\"Pull today's events\"})\nserver │ [agent 9e82c1] spawn: google-calendar [google-calendar] — \"Pull today's events\"\nserver │ [agent 9e82c1] tool: list_events\nserver │ [agent 9e82c1] done (completed, 2.1s, in\u002Fout tokens 1234\u002F567)\nserver │ [turn a3f21d] → reply (3.4s, 140 chars): \"Light day — just your 2pm with Sarah...\"\nserver │ [sendblue] → sent 140 chars to +14155551234\n```\n\nPer-line anatomy:\n\n- **`[turn xxxxxx]`** — one iMessage round trip. Same id across `←` (incoming) → tool calls → `→ reply` → `[sendblue] sent`.\n- **`[agent xxxxxx]`** — a spawned execution agent. Shows `spawn`, each `tool:` it invokes, and `done` with timing + token counts.\n- **`[sendblue]`** — outbound send results. If Sendblue rejects, the error body is logged with a hint about the likely cause (from_number mismatch, self-send, etc.).\n\nThe same events are written to Convex (`messages`, `executionAgents`, `agentLogs`, `memoryEvents` tables) and streamed to the debug dashboard in real time.\n\n### When to re-run each Sendblue script\n\n- **First time \u002F after losing `.env.local`** → `npm run setup` (walks through Sendblue + Convex together)\n- **Phone number looks wrong in the banner** → `npm run sendblue:sync`\n- **Webhook went stale in the dashboard and auto-register is off** → `npm run sendblue:webhook -- https:\u002F\u002Fyour-url.example.com\u002Fsendblue\u002Fwebhook`\n\n### Disabling auto-register\n\nAdd to `.env.local`:\n\n```\nSENDBLUE_AUTO_WEBHOOK=false\n```\n\n`npm run dev` will still show you the webhook URL in the banner so you can paste it yourself.\n\nVisit `http:\u002F\u002Flocalhost:5173` for the debug dashboard (chat, agents, memory, events). You can also chat from the dashboard's Chat tab without Sendblue.\n\n**This is the full first-run.** You now have a working agent that chats, remembers, and schedules reminders. Enable integrations (Gmail, Calendar, Notion, Slack) when you want more — see the next section.\n\n---\n\n## Architecture in 30 seconds\n\n```\n┌─────────────┐    webhook     ┌─────────────────────┐\n│   iMessage  │ ─────────────► │ Sendblue → \u002Fwebhook │\n└─────────────┘                └──────────┬──────────┘\n                                          │\n                                          ▼\n                          ┌────────────────────────────┐\n                          │    Interaction agent       │\n                          │    (dispatcher only)       │\n                          │  • recall \u002F write_memory   │\n                          │  • spawn_agent(...)        │\n                          └────────┬────────┬──────────┘\n                                   │        │\n                   ┌───────────────┘        └──────────────┐\n                   ▼                                       ▼\n           ┌───────────────┐                      ┌──────────────┐\n           │   Memory      │                      │  Execution   │\n           │ (Convex)      │                      │  agent(s)    │\n           │ + cleaning    │                      │  + integrations│\n           └───────────────┘                      └──────────────┘\n```\n\n- **Interaction agent** (`server\u002Finteraction-agent.ts`) is the front door. It reads the user's message + recent history, optionally calls `recall`, writes memories, creates automations, and decides whether to answer directly or spawn a sub-agent.\n- **Execution agent** (`server\u002Fexecution-agent.ts`) is spawned per task. It loads only the integrations named in the spawn call and returns a tight answer.\n- **Memory** (`server\u002Fmemory\u002F`) handles writes, recall, post-turn extraction, and daily cleaning. Stored in Convex.\n- **Automations** (`server\u002Fautomations.ts`) poll every 30s for due jobs, spawn an execution agent to run them, and push results back to the user.\n- **Integrations** are provided by [Composio](https:\u002F\u002Fcomposio.dev\u002F?utm_source=chris&utm_medium=youtube&utm_campaign=collab). The dispatcher names toolkits by slug (`spawn_agent(integrations: [\"gmail\"])`); `server\u002Fcomposio.ts` opens a toolkit-scoped Composio session per spawn and wraps its tools as an MCP server. No per-integration code to write.\n\nDeep dive: [ARCHITECTURE.md](.\u002FARCHITECTURE.md). Adding your own tools: [INTEGRATIONS.md](.\u002FINTEGRATIONS.md).\n\n---\n\n## Skills\n\nSkills are reusable playbooks — `SKILL.md` files under `.claude\u002Fskills\u002F` that teach the execution agent how to do a specific kind of task (write a YouTube script, draft a cold email, plan a trip, etc.).\n\n**How the Agent SDK handles them:** every `.claude\u002Fskills\u002F*\u002FSKILL.md` is loaded when the execution agent boots, and each skill's `description` gets injected into the agent's system prompt along with an instruction to pick the relevant one for the current task. You do **not** select skills per spawn — the agent picks based on which description matches. Only descriptions load upfront; the full SKILL.md body is pulled into context only when the agent actually invokes the skill, so adding more skills is cheap.\n\nThe SDK is pretty smart about picking the right skill as long as your `description` is specific and front-loads the trigger phrases (\"Use when the user asks to write a video script, turn research into a YouTube video…\"). Vague descriptions = missed invocations.\n\nWiring (in `server\u002Fexecution-agent.ts`):\n- `settingSources: [\"project\"]` — tells the SDK to load `.claude\u002Fskills\u002F`\n- `\"Skill\"` in `allowedTools` — enables the Skill tool\n\nOnly the **execution agent** loads skills. The dispatcher (interaction-agent) stays in SDK isolation mode, so it never sees them — which is correct, because the dispatcher should never do work, only route.\n\n**To add a skill:** create `.claude\u002Fskills\u002F\u003Ckebab-name>\u002FSKILL.md`:\n\n```yaml\n---\nname: youtube-script-writer\ndescription: Write a tight, retention-focused YouTube script from a topic or outline. Use when the user asks for a video script, wants to turn research into a video, or needs a hook rewritten.\n---\n\n\u003Cinstructions the agent follows when this skill is invoked>\n```\n\nThere's a soft budget (~15k chars by default, via `SLASH_COMMAND_TOOL_CHAR_BUDGET`) for the combined skill-description block in context — if you end up with many skills, keep descriptions sharp so none get truncated.\n\nExample included: `.claude\u002Fskills\u002Fyoutube-script-writer\u002F`.\n\n---\n\n## Using your Claude Code subscription\n\nThe Claude Agent SDK reuses the credentials Claude Code writes to your machine when you sign in. You do not need an `ANTHROPIC_API_KEY`.\n\n- Install once: `npm install -g @anthropic-ai\u002Fclaude-code`\n- Run `claude` in a terminal, sign in.\n- That's it — the SDK finds the session automatically.\n\nIf you'd prefer an API key (e.g. for a deployed server), set `ANTHROPIC_API_KEY` in `.env.local` and the SDK will use it instead.\n\n---\n\n## Environment variables\n\nEverything lives in `.env.local` (auto-created by `npm run setup`). See `.env.example` for the full list.\n\n| Var | Required | Notes |\n|---|---|---|\n| `CONVEX_URL` \u002F `VITE_CONVEX_URL` | yes | Convex deployment URL. Written by `npx convex dev`. |\n| `SENDBLUE_API_KEY` \u002F `SENDBLUE_API_SECRET` | yes | From your Sendblue dashboard. |\n| `SENDBLUE_FROM_NUMBER` | yes | Your Sendblue-provisioned number. |\n| `BOOP_MODEL` | no | Default `claude-sonnet-4-6`. Used as the fallback when no runtime override is set. The user can switch the model at runtime from iMessage (\"use opus\", \"switch to sonnet\") via the `set_model` self-tool — that override is stored in the Convex `settings` table and takes precedence over this env var. |\n| `BOOP_UPSTREAM_CHECK` | no | Set to `false` to disable the new-version banner on `npm run dev`. Default: on. |\n| `PORT` | no | Default `3456`. |\n| `PUBLIC_URL` | no | Base URL used in the Sendblue webhook. Composio handles its own OAuth callbacks on `platform.composio.dev`, so this is just for inbound iMessage. |\n| `VOYAGE_API_KEY` **or** `OPENAI_API_KEY` | optional | Unlocks vector recall. Falls back to substring. |\n| `COMPOSIO_API_KEY` | optional | Enables integrations. Without it, plain chat + memory + automations still work. Get one at [app.composio.dev\u002Fdevelopers](https:\u002F\u002Fapp.composio.dev\u002Fdevelopers?utm_source=chris&utm_medium=youtube&utm_campaign=collab). |\n| `COMPOSIO_USER_ID` | optional | Stable user id Composio keys connections under. Defaults to `boop-default`. |\n| `ANTHROPIC_API_KEY` | optional | Bypass the Claude Code subscription. |\n\n---\n\n## Integrations, via Composio\n\nBoop outsources 3rd-party service integrations to [Composio](https:\u002F\u002Fcomposio.dev\u002F?utm_source=chris&utm_medium=youtube&utm_campaign=collab). One API key unlocks ~1000 toolkits (Gmail, Slack, GitHub, Linear, Notion, Drive, Stripe, Supabase, HubSpot, Salesforce, Granola, and so on). Composio hosts the OAuth apps, manages token refresh, and exposes every toolkit as a set of Claude-ready tools. Boop never sees an access token.\n\n### Quickstart\n\n1. Grab an API key at [app.composio.dev\u002Fdevelopers](https:\u002F\u002Fapp.composio.dev\u002Fdevelopers?utm_source=chris&utm_medium=youtube&utm_campaign=collab).\n2. Add it to `.env.local`:\n   ```\n   COMPOSIO_API_KEY=sk-comp-...\n   ```\n3. `npm run dev`.\n4. Open the debug dashboard → **Connections** tab. You'll see a curated list of ~20 cards. For each one: click **Connect**, authenticate on Composio's hosted page, done — Composio ships managed OAuth for every curated toolkit. (If you add a custom toolkit that needs your own OAuth app, the card flips to a \"Set up →\" state pointing at `platform.composio.dev\u002Fauth-configs` — rare, but supported.)\n\nAfter a successful connect, the agent can use that toolkit immediately — no restart.\n\n### How it wires in\n\nBoop keeps the dispatcher \u002F executor split intact. Composio sits under the executor:\n\n```\ninteraction-agent:  spawn_agent(task, integrations: [\"gmail\", \"slack\"])\n                              │\n                              ▼\nexecution-agent:    for each slug, open a Composio session scoped to that toolkit:\n                      composio.create(BOOP_USER, { toolkits: [\"gmail\"] })\n                      session.tools()          ← returns only Gmail tools\n                              │\n                              ▼\n                    createSdkMcpServer({ name: \"gmail\", tools })\n                              │\n                              ▼\n                    Sub-agent sees mcp__gmail__GMAIL_*  — nothing else.\n```\n\nKey properties:\n\n- **Per-spawn tool scope.** The dispatcher picks which toolkits the sub-agent sees. Tens of tools per spawn, not thousands, so context stays tight and the agent stays fast.\n- **Toolkit slug = integration name.** `spawn_agent(integrations: [\"linear\"])` works for any toolkit you've connected. Unknown slugs just log a warning and are skipped.\n- **No tokens on our side.** Every tool call runs through Composio's proxy. If Composio goes down, integrations go down — but your server never holds user OAuth tokens.\n- **Multi-account per toolkit.** Connect a second Gmail (work + personal) — each gets its own connection row you can alias. The dispatcher picks up all active connections for the slug.\n- **Identity resolution.** Connection cards show the real account email (e.g. `chris@aloa.co`) resolved by calling the toolkit's own \"who am I\" tool through Composio (`GMAIL_GET_PROFILE`, etc.). Alias per connection if you want a friendlier label.\n\n### Adding toolkits beyond the curated list\n\nThe ~20 toolkit catalog is hand-picked in `server\u002Fcomposio.ts:CURATED_TOOLKITS`. To surface another:\n\n```ts\n\u002F\u002F server\u002Fcomposio.ts\nexport const CURATED_TOOLKITS: CuratedToolkit[] = [\n  \u002F\u002F …existing entries…\n  { slug: \"airtable\", displayName: \"Airtable\", authMode: \"managed\" },\n];\n```\n\n`authMode: \"managed\"` is correct for virtually every toolkit Composio ships today. Use `\"byo\"` only if Composio doesn't have a hosted OAuth app for that toolkit. If you guess wrong, the UI's auth-config fallback banner catches it and points you at the right dashboard page.\n\n### Cost tracking\n\nEvery execution agent's `total_cost_usd` comes straight from the Claude Agent SDK's `result` message (authoritative, matches Anthropic's billing). You'll see real dollar amounts in the Dashboard tab's Cost tile and per-agent cards.\n\nEvery LLM call — dispatcher turn, execution-agent run, memory extraction, consolidation (proposer \u002F adversary \u002F judge) — also writes a row to the `usageRecords` table with per-layer tokens (including cache read\u002Fwrite) and cost. `usageRecords:summary` gives you totals by source so you can see which layer is actually burning the bill. Each row reports the model the caller requested, not the model-routing the SDK did internally.\n\n### A note on runaway cost\n\nBoop's `query()` calls don't currently set `maxTurns` or `maxBudgetUsd`. Those are hard stops the SDK exposes — set them and the agent aborts once the threshold hits, with whatever partial result it has.\n\nKept as-is intentionally for a single-user personal agent: every task is scoped tight (spawned by the dispatcher with a specific task string + a small integration list), integrations are Composio-scoped per spawn so the tool surface stays small, and the existing 15-minute heartbeat (`server\u002Fheartbeat.ts`) marks any long-running agent as `failed` and aborts it. In practice execution agents complete in under 60 seconds.\n\nIf you deploy Boop in a higher-throughput setting, or hand it integrations that allow looping (webhooks, scrapers), you probably want to set `maxTurns: 20` and `maxBudgetUsd: 2.00` on the `query()` call in `server\u002Fexecution-agent.ts` as a belt-and-suspenders cap.\n\n### Keeping it in sync\n\nDeeper dive — auth modes, toolkit scoping internals, multi-account flow, per-connection identity: [INTEGRATIONS.md](.\u002FINTEGRATIONS.md).\n\nUpgrade path when upstream ships changes: run `\u002Fupgrade-boop` inside `claude` (the skill under `.claude\u002Fskills\u002Fupgrade-boop\u002F`) — previews diffs, backs up, merges, surfaces `[BREAKING]` CHANGELOG entries. See [CONTRIBUTING.md](.\u002FCONTRIBUTING.md) for contribution rules + the CHANGELOG \u002F migration-skill conventions.\n\n---\n\n## Project layout\n\n```\nboop-agent\u002F\n├── server\u002F\n│   ├── index.ts                   # Express + WS + HTTP routes\n│   ├── sendblue.ts                # iMessage webhook, reply, typing indicator\n│   ├── interaction-agent.ts       # Dispatcher\n│   ├── execution-agent.ts         # Sub-agent runner\n│   ├── automations.ts             # Cron loop\n│   ├── automation-tools.ts        # create\u002Flist\u002Ftoggle\u002Fdelete MCP\n│   ├── draft-tools.ts             # save_draft \u002F send_draft \u002F reject_draft MCP\n│   ├── heartbeat.ts               # Stale-agent sweep\n│   ├── consolidation.ts           # 3-phase adversarial pipeline (proposer → adversary → judge)\n│   ├── usage.ts                   # aggregateUsageFromResult helper (shared cost aggregation)\n│   ├── embeddings.ts              # Voyage \u002F OpenAI wrapper\n│   ├── composio.ts                # Composio SDK wrapper (session + toolkit scoping)\n│   ├── composio-routes.ts         # \u002Fcomposio\u002F* HTTP routes for the Debug UI\n│   ├── broadcast.ts               # WS fanout\n│   ├── convex-client.ts           # Convex HTTP client\n│   ├── memory\u002F\n│   │   ├── types.ts\n│   │   ├── tools.ts               # write_memory \u002F recall (vector + substring)\n│   │   ├── extract.ts             # Post-turn extraction\n│   │   └── clean.ts               # Decay + archive + prune\n│   └── integrations\u002F\n│       ├── registry.ts            # Integration loader\n│       └── composio-loader.ts     # Registers each connected Composio toolkit\n├── convex\u002F\n│   ├── schema.ts\n│   ├── messages.ts\n│   ├── memoryRecords.ts\n│   ├── agents.ts\n│   ├── automations.ts\n│   ├── consolidation.ts\n│   ├── conversations.ts\n│   ├── drafts.ts\n│   ├── memoryEvents.ts\n│   ├── usageRecords.ts            # Append-only per-call cost log\n│   └── sendblueDedup.ts\n├── debug\u002F                         # Dashboard: Dashboard \u002F Agents \u002F Automations \u002F Memory \u002F Events \u002F Connections\n├── scripts\u002F\n│   ├── setup.ts                   # Interactive setup CLI\n│   ├── dev.mjs                    # One-command orchestrator (server + convex + vite + ngrok)\n│   ├── preflight.mjs              # Checks convex\u002F_generated exists before booting\n│   ├── sendblue-sync.mjs          # Pulls phone number from `sendblue lines`\n│   └── sendblue-webhook.mjs       # Registers inbound webhook via Sendblue CLI\n├── README.md           ← you are here\n├── ARCHITECTURE.md\n└── INTEGRATIONS.md\n```\n\n---\n\n## Upgrading\n\nBoop is a fork-and-own template. You customize your copy freely — system prompts, memory thresholds, extra tools — and pull upstream fixes in on your own schedule.\n\nThe intended path is **Claude Code-driven**, modeled on NanoClaw:\n\n```bash\nclaude                 # inside your repo\n\u002Fupgrade-boop\n```\n\n`\u002Fupgrade-boop` is a skill in `.claude\u002Fskills\u002Fupgrade-boop\u002FSKILL.md`. It:\n\n1. Refuses to run with a dirty working tree.\n2. Creates a timestamped rollback tag.\n3. Previews upstream changes bucketed by area (core \u002F integrations \u002F UI \u002F schema \u002F scripts \u002F docs).\n4. Merges (or cherry-picks, or rebases — your choice).\n5. Runs `npm install` + `npm run typecheck`.\n6. Parses `CHANGELOG.md` for `[BREAKING]` entries and offers to run the referenced migration skills.\n7. Prints a rollback hash + any env-var additions you should copy into `.env.local`.\n\nPlain git works too, if you'd rather:\n\n```bash\ngit remote add upstream https:\u002F\u002Fgithub.com\u002Fchris\u002Fboop-agent.git    # one-time\ngit fetch upstream\ngit merge upstream\u002Fmain      # or: git rebase upstream\u002Fmain\n```\n\n### New-version notifications\n\nEvery time you run `npm run dev`, a small background check (`scripts\u002Fcheck-upstream.mjs`) asks your `upstream` remote if there are new commits. If there are, you'll see a banner up top with the count and a reminder to run `\u002Fupgrade-boop`. If you're up to date, or the check fails for any reason (offline, no `upstream` remote, timeout), it stays silent.\n\nBehavior at a glance:\n\n- `upstream` set, new commits → banner with the count\n- `upstream` set, up to date → silent\n- No `upstream` remote, on a fork → one-line hint on adding it\n- No `upstream` remote, on the canonical repo → silent (you *are* upstream)\n\nTo turn it off:\n\n- **Env var:** add `BOOP_UPSTREAM_CHECK=false` to `.env.local`\n- **Or comment it out:** the call lives in `scripts\u002Fdev.mjs` — the `spawn(\"node\", [\"scripts\u002Fcheck-upstream.mjs\"], ...)` block. Delete or comment that block and the check never runs.\n\n### CHANGELOG\n\nEvery release lists additions under [CHANGELOG.md](.\u002FCHANGELOG.md), with `[BREAKING]` prefixes for anything that requires action. `\u002Fupgrade-boop` parses that format automatically.\n\n---\n\n## Troubleshooting\n\n**Agent doesn't reply.**\n- Check the server is running: `curl http:\u002F\u002Flocalhost:3456\u002Fhealth`\n- Check the Sendblue webhook is pointed at `\u003Cpublic-url>\u002Fsendblue\u002Fwebhook`\n- Watch server logs. Look for `[sendblue]` and `[interaction]` messages.\n\n**Convex errors \u002F `VITE_CONVEX_URL is not set`.**\n- Run `npx convex dev` manually. Ensure `.env.local` has both `CONVEX_URL` and `VITE_CONVEX_URL`.\n\n**\"Could not find public function for X:Y\".**\n- `CONVEX_DEPLOYMENT` and `CONVEX_URL` in `.env.local` are pointing at different projects. `convex dev` pushes functions to `CONVEX_DEPLOYMENT` but the client reads from `CONVEX_URL`. Fix: make sure the URL has the same name as the deployment — `CONVEX_DEPLOYMENT=dev:foo-bar-123` → `CONVEX_URL=https:\u002F\u002Ffoo-bar-123.convex.cloud`. Re-running `npm run setup` now auto-syncs these.\n\n**Agent replies but can't use my integration.**\n- Check `COMPOSIO_API_KEY` is set in `.env.local`.\n- Check the toolkit shows as **Connected** in the Connections tab.\n- Watch server logs for `[composio] registered …` at boot and `[integrations] unknown integration: …` on spawn attempts.\n\n**I want to skip Sendblue for now.**\n- The server exposes `POST \u002Fchat` with `{ conversationId, content }` — curl or a tiny client can drive the agent directly, no iMessage required.\n\n**Claude SDK says no credentials.**\n- Run `claude` once and sign in, or set `ANTHROPIC_API_KEY` in `.env.local`.\n\n**\"Cannot send messages to self\" \u002F \"missing required parameter: from_number\".**\n- `SENDBLUE_FROM_NUMBER` is set to your personal cell instead of your Sendblue-provisioned number. Run `npm run sendblue:sync` to pull the correct number from `sendblue lines` and write it to `.env.local`.\n\n**\"Dashboard crashed\" in the debug UI.**\n- The ErrorBoundary caught something. Check the server logs (`server │` stream) and the browser console — both will have the real error. Most common cause: a new Convex function hasn't been deployed yet. Restart `npm run dev` so `convex dev` re-pushes.\n\n---\n\n## License\n\nMIT. Build whatever you want on top of this.\n","Boop 是一个基于 iMessage 的个人代理，构建在 Claude Agent SDK 之上。它具备预构建的多代理架构、强大的记忆系统、自动化处理和集成能力，支持通过 Sendblue 实现 iMessage 的双向通信，并利用 Composio 进行多种工具和服务的集成，如 Gmail、Slack 和 GitHub 等。该项目采用 TypeScript 开发，遵循 MIT 许可协议。Boop 适合需要通过自然语言与用户互动并执行任务的应用场景，比如个人助理、客户服务或智能聊天机器人等。其核心优势在于灵活的扩展性和定制化潜力，允许开发者根据具体需求调整和增强功能。","2026-06-11 02:39:41","CREATED_QUERY"]