269 lines
7.8 KiB
Markdown
269 lines
7.8 KiB
Markdown
# 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-worker` service 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
|
|
|
|
```sh
|
|
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.
|
|
|
|
```sh
|
|
bun db:down # stop; preserve local data
|
|
bun db:down:wipe # remove local data volumes and generated admin key
|
|
```
|
|
|
|
Use staging services explicitly:
|
|
|
|
```sh
|
|
INFISICAL_ENV=staging bun dev:next
|
|
```
|
|
|
|
Run the optional local agent worker in a separate terminal:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```txt
|
|
.local/<environment>.generated.env
|
|
```
|
|
|
|
CI uses Gitea-provided secrets or `CI_ENV_FILE` and must not call Infisical.
|
|
|
|
Useful helpers:
|
|
|
|
```sh
|
|
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
|
|
sh scripts/sync-convex-env dev
|
|
sh scripts/sync-convex-env staging
|
|
INFISICAL_ENV=staging bun sync:convex
|
|
```
|
|
|
|
The sync includes:
|
|
|
|
```txt
|
|
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:
|
|
|
|
```txt
|
|
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
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
bun lint:ws
|
|
bun format
|
|
bun lint
|
|
bun typecheck
|
|
bun run test
|
|
```
|
|
|
|
Full local gate without e2e:
|
|
|
|
```sh
|
|
SKIP_E2E=1 bun run ci:check
|
|
```
|
|
|
|
Local-stack smoke e2e:
|
|
|
|
```sh
|
|
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.
|