7.8 KiB
Spoon
Spoon is a self-hostable fork maintenance dashboard.
The product goal is simple: make it practical to fork a project, customize it, and still stay close to upstream. Spoon tracks managed forks, called Spoons, and lays the foundation for upstream update checks, AI-assisted change review, and agent-authored merge requests.
This repository is the Spoon application itself, not a generic starter.
Current scope
Implemented today:
- Public Spoon landing page in Next.js.
- Authenticated web dashboard routes:
/dashboard/spoons/spoons/new/updates/spoons/[spoonId]/settings
- Manual and GitHub-created Spoon records stored in Convex.
- GitHub App connection, repository listing, fork creation, drift refresh, commit/PR cache, and safe manual sync foundation.
- Per-user OpenAI settings for upstream compatibility review.
- Per-Spoon encrypted project secrets and agent runtime settings.
- Optional
apps/agent-workerservice that can claim queued jobs, clone the GitHub fork, keep an interactive workspace active, expose file browsing and edits through a server-side proxy, run selected commands, call OpenCode or the OpenAI direct fallback, push a branch, and open a draft PR. - Browser agent workspace at
/spoons/[spoonId]/agent/[jobId]with persisted thread messages, file tree, Monaco editor with optional Vim mode, diff view, command panel, and draft PR actions. - Password auth and Authentik OAuth through Convex Auth.
- Expo companion app shell with password and Authentik sign-in.
- Self-hosted local Convex using Postgres storage.
Not implemented yet:
- Automatic merge.
- Additional Git provider automation beyond preserving provider-neutral fields.
- Additional remotes as push targets.
- Long-running service-stack orchestration inside agent jobs.
- Direct browser access to agent containers.
- Production mobile build/release setup.
Architecture
apps/next: Next.js 16 web app and primary product UI.apps/agent-worker: optional server-side worker for queued coding-agent jobs.apps/expo: Expo companion app.packages/backend/convex: self-hosted Convex schema, functions, auth, and HTTP routes.packages/ui: shared shadcn-based UI components.tools: shared ESLint, Prettier, Tailwind, TypeScript, and Vitest config.docker: local and production Compose files.scripts: environment, database, and CI helpers.
The core domain objects are:
spoons: managed fork records.gitConnections: future Git provider connection metadata.syncRuns: future upstream checks, merge attempts, and AI reviews.agentRequests: prompt-driven agent work requests.agentJobs: worker-executed coding-agent jobs and their PR lifecycle.agentJobMessages: persisted per-job agent workspace thread messages.agentWorkspaceChanges: recorded user, agent, and command workspace changes.spoonSecrets: encrypted per-Spoon environment variables.spoonAgentSettings: per-Spoon agent model, branch, and command settings.
Local setup
Requirements:
- Bun 1.3.10
- Node 22
- Docker or Podman
- Infisical CLI
bun install --frozen-lockfile
infisical login
infisical init
bun db:up
bun dev:next
Local services:
- Next.js:
http://localhost:3000 - Convex API:
http://localhost:3210 - Convex site HTTP routes:
http://localhost:3211 - Convex dashboard:
http://localhost:6791 - Convex Postgres:
localhost:5432
Next and Expo run on the host. Local Convex runs in containers with Postgres
storage. Normal bun db:up never contacts staging; it starts local Postgres,
Convex, and the dashboard, generates a machine-local Convex admin key in
.local/dev.generated.env when needed, deploys functions/schema, and
configures local Convex Auth keys.
bun db:down # stop; preserve local data
bun db:down:wipe # remove local data volumes and generated admin key
Use staging services explicitly:
INFISICAL_ENV=staging bun dev:next
Run the optional local agent worker in a separate terminal:
bun dev:agent
The worker also starts an internal HTTP API, defaulting to
http://localhost:3921, for server-side Next route handlers. The browser never
receives the worker token or talks to this API directly.
The Docker Compose local worker service is disabled by default behind the
agent profile. Build the job image before using Docker-backed jobs:
docker build -f docker/agent-job.Dockerfile -t spoon-agent-job:latest .
docker compose -f docker/compose.local.yml --profile agent up spoon-agent-worker
The job image includes the OpenCode CLI. Rebuild it after changes to
docker/agent-job.Dockerfile.
Environment model
Local dev and staging values come from Infisical through scripts/with-env.
App commands do not fall back to root .env files.
Generated local state belongs in:
.local/<environment>.generated.env
CI uses Gitea-provided secrets or CI_ENV_FILE and must not call Infisical.
Useful helpers:
sh scripts/with-env dev -- <command>
sh scripts/export-env dev
bun sync:convex
Convex deployment env
Convex functions and HTTP actions read environment variables from the Convex deployment environment, not directly from the host process. For OAuth providers, that means Infisical values must also be present in local Convex env.
packages/backend runs scripts/sync-convex-env before convex dev, so
bun dev:next, bun dev:backend, and bun db:up sync the relevant Infisical
values into the selected Convex deployment first. Run it manually when needed:
sh scripts/sync-convex-env dev
sh scripts/sync-convex-env staging
INFISICAL_ENV=staging bun sync:convex
The sync includes:
AUTH_AUTHENTIK_ID
AUTH_AUTHENTIK_SECRET
AUTH_AUTHENTIK_ISSUER
AUTH_GITHUB_ID
AUTH_GITHUB_SECRET
GITHUB_APP_ID
GITHUB_APP_CLIENT_ID
GITHUB_APP_CLIENT_SECRET
GITHUB_APP_PRIVATE_KEY
GITHUB_APP_WEBHOOK_SECRET
GITHUB_APP_SLUG
GITHUB_APP_INSTALLATION_ID
GITHUB_APP_OWNER
SPOON_ENCRYPTION_KEY
SPOON_WORKER_TOKEN
SPOON_AGENT_WORKER_INTERNAL_TOKEN
SPOON_AGENT_WORKER_HTTP_PORT
SPOON_AGENT_WORKER_URL
USESEND_API_KEY
USESEND_URL
USESEND_FROM_EMAIL
JWT_PRIVATE_KEY
JWKS
SITE_URL
For local dev, JWT_PRIVATE_KEY, JWKS, SPOON_ENCRYPTION_KEY, and
SPOON_WORKER_TOKEN are generated automatically if they are not already present
in Convex. The generated Convex admin key remains machine-local in
.local/dev.generated.env; do not put it in Infisical.
The local OAuth callback URLs are:
http://localhost:3211/api/auth/callback/authentik
http://localhost:3211/api/auth/callback/github
If GitHub App actions fail with GITHUB_APP_PRIVATE_KEY is not configured, add
the full PEM contents to Infisical as GITHUB_APP_PRIVATE_KEY and rerun the
sync command.
Development
bun dev:next
bun dev:expo
Physical devices cannot resolve their own localhost; override the public
Convex URL with the development host's LAN address when testing Expo on-device.
Shared dependency versions belong in root catalogs. Edit the root catalog, run
bun install, then bun lint:ws. Do not run bun update inside a workspace.
Validation
Routine checks:
bun lint:ws
bun format
bun lint
bun typecheck
bun run test
Full local gate without e2e:
SKIP_E2E=1 bun run ci:check
Local-stack smoke e2e:
bun test:e2e
bun test:e2e starts the isolated local stack when needed and stops it
afterward only when it was not already running.
Use bun run test, not bare bun test; bare bun test invokes Bun's built-in
test runner instead of the repo's Turbo/Vitest test script.
Deployment
Production Compose keeps the self-hosted Convex backend/dashboard and expects
POSTGRES_URL to be a database-cluster URL without a database path.
Gitea runs the quality gate first, builds the Next image from a temporary
Gitea-secret env file, then pushes SHA and latest tags. CI never installs or
invokes Infisical.