Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d856337
docs: add auto-deploy PR design spec
Apr 28, 2026
c30e00d
docs: add auto-deploy PR implementation plan
Apr 28, 2026
19a80dd
chore: run npm install to sync package-lock.json
Apr 28, 2026
6576bd1
feat(auth): add verifyHmac helper with timing-safe comparison
Apr 28, 2026
6b20a87
feat(auth): add requireAdmin JWT middleware with public allowlist
Apr 28, 2026
5a93bce
feat(sendblue): verify HMAC signature and phone whitelist on inbound …
Apr 28, 2026
b8bd372
chore(test): add pretest stub-generator so npm test works without con…
Apr 28, 2026
174af5a
feat(convex): add Convex Auth password provider
Apr 28, 2026
007543c
feat(convex): add bootstrap + setPassword admin actions
Apr 28, 2026
6f7e338
refactor: classify Convex functions as internal vs public+auth
Apr 28, 2026
aa5cf53
fix(convex): add internal twins for dual-use functions
Apr 28, 2026
6f5850c
feat(server): wire requireAdmin middleware, auth WS upgrade, serve de…
Apr 28, 2026
33594eb
feat(debug): wrap UI in ConvexAuthProvider and add login form
Apr 28, 2026
9d6fff6
feat(debug): add authed apiClient wrapper and migrate fetch calls
Apr 28, 2026
15cdb8b
feat(deploy): add multi-stage Dockerfile (node:22-slim, tsx runtime)
Apr 28, 2026
996fe5a
feat(deploy): add fly.toml (single machine, always-on)
Apr 28, 2026
43c923f
docs(env): document SENDBLUE_SIGNING_SECRET, BOOP_ADMIN_PASSWORD, CLA…
Apr 28, 2026
209562e
feat(ci): add deploy workflow (test → convex → bootstrap → fly → smoke)
Apr 28, 2026
3235790
feat(deploy): add interactive scripts/deploy.ts (mirrors setup.ts pat…
Apr 28, 2026
9d4714a
docs: add deploying.md and link from README
Apr 28, 2026
a8916a6
docs: add CHANGELOG entry + setup-deploy-auth migration skill
Apr 28, 2026
8b3330d
fix(auth): use CONVEX_SITE_URL for JWKS endpoint and JWT issuer
Apr 28, 2026
8e4e64a
fix(ws): hoist JWT verifier so JWKS cache survives across connections
Apr 28, 2026
ebe4b31
fix(deploy): push CONVEX_SITE_URL to Fly secrets
Apr 28, 2026
cb17c88
fix(auth): make bootstrap idempotent against concurrent invocations
Apr 28, 2026
043d962
fix(server): correct relative path to debug UI dist in production
Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions .claude/skills/setup-deploy-auth/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
name: setup-deploy-auth
description: One-time migration to enable single-user auth + Fly deploy. Installs new deps, pushes auth tables to Convex, generates the admin password, bootstraps the admin user, and prints next steps. Run after pulling the auto-deploy + auth upgrade.
---

# Set up deploy + auth

Migration triggered when pulling in the auto-deploy + auth changes. Installs deps, pushes the schema additions, generates and stores the admin password, bootstraps the admin user, and reconciles `.env.local` against `.env.example`. Idempotent — safe to re-run.

# Operating principles

- Never proceed with a dirty working tree. Refuse and ask the user to commit or stash.
- Lean on `npm`, `npx convex`, `git`. Don't hand-edit generated files.
- Idempotent: each step is safe to re-run. The user might Ctrl-C halfway and resume.
- Surface errors clearly. If a step fails, stop and report — don't silently continue.

# Step 0: Preflight

Run:
- `git status --porcelain`

If output is non-empty, tell the user to commit or stash and stop.

Verify the new files exist (these came in with the upgrade):
- `convex/auth.ts`
- `convex/auth.config.ts`
- `convex/users.ts`
- `server/auth.ts`

If any are missing the merge didn't bring them in — stop and tell the user.

# Step 1: Install new deps

Run:
- `npm install`

Picks up `jose`, `@convex-dev/auth`, `@auth/core`, and any other dep moves from the new `package.json`.

# Step 2: Push schema additions to Convex

The upgrade adds `authTables` from `@convex-dev/auth/server` to the schema (`users`, `authAccounts`, `authSessions`, etc.). Push them:

- `npx convex dev --once`

Output should end with "Convex functions ready". If the user is offline or doesn't want to push right now, they can skip — but Step 4 (bootstrap) will fail until the schema is pushed.

# Step 3: Generate the admin password

Check if `BOOP_ADMIN_PASSWORD` is already set in `.env.local`:
- `grep -E '^BOOP_ADMIN_PASSWORD=.+' .env.local`

If a non-empty value exists, ask whether to keep it or generate a new one.

To generate a strong password:
- `openssl rand -base64 24`

Store the chosen value as `$BOOP_PASSWORD` for the remaining steps. Print it once for the user to record (they'll need it to log into the dashboard).

# Step 4: Set the password in Convex env

Run:
- `npx convex env set BOOP_ADMIN_PASSWORD <value>`

The `bootstrap` action reads this from `process.env` inside Convex.

# Step 5: Bootstrap the admin user

Run:
- `npx convex run users:bootstrap`

Expected output:
- First run: `{ created: true }`
- Re-run: `{ created: false, reason: "user already exists" }`

If the action throws "BOOP_ADMIN_PASSWORD is not set", Step 4 didn't take effect — re-run it and try again.

# Step 6: Reconcile `.env.local` against `.env.example`

The upgrade added new keys to `.env.example`. Find ones missing from `.env.local`:

```
comm -23 \
<(grep -oE '^[A-Z_][A-Z0-9_]*=' .env.example | sort -u) \
<(grep -oE '^[A-Z_][A-Z0-9_]*=' .env.local 2>/dev/null | sort -u)
```

For each missing key, append a blank line to `.env.local` and tell the user what they need to set:

| Key | When to set | Where to get |
|---|---|---|
| `SENDBLUE_SIGNING_SECRET` | Required for any non-localhost deploy. Without it, the webhook accepts unsigned requests in local dev only. | Sendblue dashboard → Webhook Settings → Signing Secret |
| `BOOP_ADMIN_PASSWORD` | Set to the value from Step 3. (For local dev, the dashboard reads this from Convex env, not `.env.local` — but storing it locally helps you remember.) | Step 3 of this skill |
| `CLAUDE_CODE_OAUTH_TOKEN` | Optional. Use this on deployed forks if you'd rather use your Claude subscription than `ANTHROPIC_API_KEY`. | Run `claude setup-token` locally |

Don't fill values automatically. The user needs to choose what's relevant for their setup.

# Step 7: Print next steps

If the user is running boop locally (not deployed):
- "Run `npm run dev`. Visit `http://localhost:5173`. Log in with the password from Step 3."
- "iMessage flow keeps working — `/sendblue/webhook` is allowlisted."

If the user is deploying (or planning to):
- "Run `npm run deploy` — interactive script that creates a Fly app, generates secrets, configures Convex/Sendblue/GitHub Actions, and ships the first deploy."
- "Read `docs/deploying.md` for the full walkthrough and operational notes (annual `CLAUDE_CODE_OAUTH_TOKEN` rotation, password rotation, single-replica constraint)."

Print rollback info: "If something broke, the `/upgrade-boop` rollback tag (printed at upgrade end) reverses everything in this skill plus the upgrade itself."

# Idempotency notes

- `npm install` — idempotent.
- `convex dev --once` — pushes schema; idempotent if schema unchanged.
- `convex env set` — overwrites existing value (intentional — Step 3 may have generated a new password).
- `users:bootstrap` — returns "user already exists" if already bootstrapped (the action's first check).
- `.env.local` reconciliation — only appends missing keys; never overwrites values.

A user running this skill twice in a row gets:
- Step 1: no change
- Step 2: no schema diff to push
- Step 3: prompted to keep existing or regenerate
- Step 4: env value possibly updated
- Step 5: "user already exists" (or recreated if Step 3 regenerated and Step 4 stored new)
- Step 6: no missing keys to append
- Step 7: prints again
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
node_modules
debug/dist
debug/node_modules
.env
.env.local
.env.*.local
.git
.github
.claude
.cursor
.idea
.vscode
docs
assets
*.md
tests
__tests__
**/*.test.ts
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# Convex will create a project and auto-populate your .env.local.
CONVEX_DEPLOYMENT=
CONVEX_URL=
# CONVEX_SITE_URL hosts /.well-known/jwks.json for Convex Auth JWT
# verification (the .convex.site domain). `npx convex dev` sets this for
# you; in deploy it's derived from CONVEX_URL if absent.
CONVEX_SITE_URL=
VITE_CONVEX_URL=

# ---- Sendblue (iMessage bridge) ----
Expand All @@ -15,12 +19,23 @@ SENDBLUE_API_KEY=
SENDBLUE_API_SECRET=
SENDBLUE_FROM_NUMBER=

# ---- Sendblue webhook signing ----
# Get this from your Sendblue dashboard under Webhook Settings → Signing Secret.
# Required when running on a public URL — the webhook handler verifies every
# incoming request's HMAC-SHA256 signature against this secret.
SENDBLUE_SIGNING_SECRET=

# ---- Claude model ----
# Uses your Claude Code subscription automatically — no separate API key needed.
# Override with ANTHROPIC_API_KEY if you want to bypass the subscription.
BOOP_MODEL=claude-sonnet-4-6
# ANTHROPIC_API_KEY=

# When deploying to a server, prefer CLAUDE_CODE_OAUTH_TOKEN (subscription)
# over ANTHROPIC_API_KEY. Generate one locally with `claude setup-token`,
# paste it as a Fly secret. Token lasts 1 year, then regenerate.
# CLAUDE_CODE_OAUTH_TOKEN=

# ---- Upgrade notifications ----
# On `npm run dev`, Boop checks your `upstream` remote for new commits and
# prints a banner with the commit count + a reminder to run `/upgrade-boop`.
Expand Down Expand Up @@ -52,6 +67,13 @@ PUBLIC_URL=http://localhost:3456
# paste it into the dashboard. Set to "false" to disable this behavior.
# SENDBLUE_AUTO_WEBHOOK=true

# ---- Boop dashboard / admin auth (deployment only) ----
# The single password for the dashboard and admin endpoints when deployed.
# `npm run deploy` will offer to auto-generate a 32-char random value.
# Set as both a Fly secret AND a Convex env var (the dashboard auth
# verifies via Convex; the bootstrap action reads from Convex env).
BOOP_ADMIN_PASSWORD=

# ---- Composio (1000+ integrations, zero-code) ----
# Get an API key at https://app.composio.dev/developers.
# Once set, connect toolkits (Gmail, Slack, GitHub, Linear, …) from the
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy

on:
push:
branches: [main]
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- run: npm ci

- name: Run unit tests
run: npm test

- name: Push Convex backend
run: npx convex deploy --yes
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}

- name: Bootstrap admin user (idempotent)
# CONVEX_DEPLOY_KEY scopes the call to the production deployment
# automatically. If the Convex CLI version in use rejects this,
# add `--prod` explicitly.
run: npx convex run users:bootstrap
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}

- uses: superfly/flyctl-actions/setup-flyctl@master

- name: Deploy to Fly
run: flyctl deploy --remote-only --app "$FLY_APP_NAME"
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_APP_NAME: ${{ secrets.FLY_APP_NAME }}

- name: Smoke test
run: |
for i in {1..30}; do
if curl -fsS "https://${FLY_APP_NAME}.fly.dev/health"; then
exit 0
fi
sleep 5
done
echo "health check failed after 150s"
exit 1
env:
FLY_APP_NAME: ${{ secrets.FLY_APP_NAME }}
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,26 @@ Format:

---

## Unreleased — Composio integration layer
## Unreleased

### Auto-deploy + single-user auth

- **[BREAKING]** Express admin endpoints (`/chat`, `/consolidate`, `/agents/:id/cancel`, `/agents/:id/retry`, `/composio/*`) now require a valid Convex Auth JWT (`Authorization: Bearer <jwt>`). The dashboard handles this automatically via the new login form. Direct API callers must obtain a JWT first. Run `/setup-deploy-auth` to configure the new auth flow.
- **[BREAKING]** Convex public `query`/`mutation` functions now call `await requireUser(ctx)` and throw "unauthenticated" if no user identity is present. Server-side code uses `internal.X.YInternal` twins that skip the check (deploy-key path). External callers using the Convex deployment URL directly will get "unauthenticated" until they set up auth.
- **[BREAKING]** Sendblue webhook now verifies HMAC signatures via `SENDBLUE_SIGNING_SECRET` (when set) and rejects requests where `from_number !== SENDBLUE_FROM_NUMBER`. Set the signing secret from your Sendblue dashboard → Webhook Settings.
- **[BREAKING]** WebSocket `/ws` upgrade now requires an `?token=<jwt>` query parameter. The dashboard sets this automatically; direct WS clients need updating.
- **[BREAKING]** Convex schema spreads `authTables` from `@convex-dev/auth/server` (adds `users`, `authAccounts`, `authSessions`, etc.). Pushed automatically on next `npx convex dev`.
- **[BREAKING]** New env vars: `BOOP_ADMIN_PASSWORD` (single dashboard password — must be set in Convex env, not just `.env.local`), `SENDBLUE_SIGNING_SECRET` (Sendblue dashboard → Webhook Settings), `CLAUDE_CODE_OAUTH_TOKEN` (optional, for deployed forks using Claude subscription auth).
- Added: `npm run deploy` — interactive script that creates a Fly app, generates secrets, configures Convex/Sendblue/GitHub Actions, and ships the first deploy. See `docs/deploying.md`.
- Added: `Dockerfile` (multi-stage `node:22-slim`, runs server with `tsx`), `fly.toml` (single machine, always-on), `.github/workflows/deploy.yml` (test → `convex deploy` → bootstrap → `fly deploy` → smoke).
- Added: `convex/auth.ts`, `convex/auth.config.ts`, `convex/users.ts` (with `bootstrap` and `setPassword` admin actions), `server/auth.ts` (`verifyHmac`, `requireAdmin`).
- Added: `debug/src/auth.tsx` (login form), `debug/src/api-client.ts` (authed `fetch` wrapper). Existing `fetch` call sites in `ConsolidationPanel` + `ComposioSection` migrated.
- Added: 15 unit tests (5 `verifyHmac`, 6 `requireAdmin`, 4 Sendblue webhook auth) using Node's built-in `node:test` runner. Run with `npm test`.
- Added: `scripts/create-test-stubs.mjs` (pretest hook) so `npm test` works without a Convex deployment configured.
- Added: `docs/deploying.md`, `.claude/skills/setup-deploy-auth/SKILL.md`.
- Added npm deps: `jose` (JWT verification), `@convex-dev/auth`, `@auth/core`. `tsx` promoted from `devDependencies` to `dependencies` so the production Docker image can run it.

### Composio integration layer

- **[BREAKING]** Hand-built integrations (`/integrations/gmail`, `/integrations/google-calendar`, `/integrations/notion`, `/integrations/slack`, `/integrations/_template`) removed. To reconnect equivalents: set `COMPOSIO_API_KEY` in `.env.local`, open the Debug UI's Connections tab, click Connect on the toolkit you want. The dispatcher will see it under the same slug (`gmail`, `slack`, `notion`, `googlecalendar`).
- **[BREAKING]** Convex `connections` table dropped. Composio stores OAuth state on its side. Any existing rows in that table are discarded on the next `convex dev` push.
Expand Down
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ---- Stage 1: install deps ----
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# ---- Stage 2: build debug UI bundle ----
FROM node:22-slim AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# convex/_generated is gitignored, so it's not in the build context.
# Generate it inside the image so the debug UI build (which imports types
# from ../convex/_generated/api) can resolve them.
RUN npx convex codegen --typecheck=disable
RUN npm run build:debug

# ---- Stage 3: runtime ----
FROM node:22-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/server ./server
COPY --from=build /app/convex ./convex
COPY --from=build /app/debug/dist ./debug/dist
COPY --from=build /app/scripts/preflight.mjs ./scripts/preflight.mjs
COPY package.json tsconfig.json ./
EXPOSE 3456
USER node
CMD ["npx", "tsx", "server/index.ts"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Built on:
- **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.
- **Convex** for persistence — real-time, typed, free tier.
- **Uses your Claude Code subscription** — no separate Anthropic API key required.
- **Deploy** — one-command production setup with `npm run deploy`. See [`docs/deploying.md`](docs/deploying.md).

<p align="center">
<img src="assets/agents-view.jpg" alt="Agents view in the Boop debug dashboard" width="900" />
Expand Down
Loading