Files
spoon/README.md
T

688 lines
31 KiB
Markdown

<p align="center">
<img src="apps/next/public/favicon.png" alt="Spoon logo" width="96" height="96" />
</p>
<h1 align="center">Spoon</h1>
<p align="center">
<strong>Fork freely & keep them all intimately close to upstream.</strong>
</p>
<p align="center">
Spoon is a self-hostable fork maintenance cockpit built around managed forks,
durable maintenance threads, and a persistent per-user dev box that agents,
terminals, and project commands all run inside.
</p>
<p align="center">
<a href="#what-this-is">What this is</a>
·
<a href="#product-model">Product model</a>
·
<a href="#architecture">Architecture</a>
·
<a href="#environment-reference">Environment</a>
</p>
---
## What This Is
Spoon is a private, actively evolving project for making forks less lonely to
maintain.
Forking a project is easy. Keeping that fork close to upstream after you add
custom changes is the hard part. Spoon treats a fork as an ongoing relationship:
it watches upstream, understands fork-only commits, automatically syncs clean
drift when it can, and opens a durable **Thread** when a decision needs context
or code.
The application is currently GitHub-first. Future provider-neutral fields exist
in the data model, but GitHub is the active automation surface today.
## Highlights
- **Managed forks, called Spoons**
Track upstream metadata, fork metadata, clone URLs, extra remotes, sync
cadence, production-ref strategy, fork-only commits, and pull requests.
- **Thread-first maintenance**
Upstream updates, conflict review, ignore decisions, user-requested work,
worker output, and draft PR handoff all live inside Threads.
- **Clean drift auto-sync**
If upstream moves and the fork has no custom commits, Spoon can fast-forward
the fork without creating busywork.
- **Custom forks get context**
If the fork has custom commits, Spoon creates a maintenance thread rather than
pretending the update is trivial.
- **Effective drift**
Spoon keeps raw GitHub drift visible while also tracking ignored upstream
changes so irrelevant commits do not keep a fork permanently actionable.
- **One persistent per-user box**
Every user owns a single long-running Fedora container `spoon-box-{username}`
with a persistent home. Every thread's agent turn, terminal session, and
project command `docker exec`s into that same box — there is no per-job
throwaway container.
- **Three agent runtimes**
Codex, OpenCode, and Claude Code all run inside the box behind one adapter
interface, selected per thread and validated at queue time against the AI
provider profile.
- **Workspaces**
Agent work happens in a workspace with a file tree, browser editor, diff
viewer, command panel, logs, artifacts, an interactive terminal, and draft PR
actions.
- **My Machine**
A `/machine` surface to start/stop/restart the box, open a `~`-rooted
terminal, and browse the persistent home.
- **GitHub webhooks**
A signature-verified webhook keeps drift fresh on `push` and flips connection
status on `installation` changes; the hourly cron is a fallback.
- **Notifications**
An in-app bell plus preference-gated transactional email for maintenance
threads, agent turns, needed input, sync failures, and connection re-auth.
- **User-owned providers and secrets**
AI provider profiles, Codex/OpenCode/Anthropic auth, and per-Spoon project
secrets are encrypted. Secrets are written `0600`, redacted from logs, and
refused from commits when materialized into env files.
- **Draft PR handoff**
Code changes become branches and draft pull requests. Spoon does not
auto-merge custom forks behind the user's back.
## Product Model
<details open>
<summary><strong>Spoons</strong></summary>
A **Spoon** is a managed fork. It records the upstream project, the fork
repository, default branches, sync policy, extra remotes, current drift, cached
commits, cached pull requests, secrets, and agent settings.
Spoons are the durable project-level objects. They answer:
- What did I fork?
- Where does my fork live?
- How far has it drifted?
- Which commits are mine?
- Which upstream changes matter?
- What threads or PRs are open?
</details>
<details open>
<summary><strong>Threads</strong></summary>
A **Thread** is the durable place where Spoon talks about maintenance work.
Threads can be created by a user or by the system.
Common thread sources:
- `user_request`: user asks Spoon to change a fork.
- `upstream_update`: upstream moved and the fork needs review.
- `merge_conflict`: a sync conflict needs context or code.
- `manual_review`: user explicitly asks for a review.
- `system`: internal maintenance coordination.
Threads hold messages, status, outcomes, related sync runs, related jobs,
workspace links, draft PR links, and ignored upstream decisions.
Opening a thread opens its workspace when a run exists. The workspace is the
primary surface for that thread: agent messages, tool activity, file edits,
manual edits, diffs, commands, and draft PR actions all happen there. Legacy
job URLs under `/spoons/[spoonId]/agent/[jobId]` are kept for compatibility,
but normal navigation targets `/threads/[threadId]`.
</details>
<details open>
<summary><strong>Maintenance decisions</strong></summary>
Spoon's maintenance policy is intentionally conservative:
| Situation | Default action |
| ------------------------------------------ | ------------------------------------- |
| No fork-only commits and upstream is ahead | Auto-sync |
| Fork-only commits and upstream is ahead | Create a maintenance thread |
| Merge conflicts | Open or continue a workspace thread |
| Irrelevant upstream changes | Record an intentional ignore decision |
| Agent/code changes | Open a draft PR |
The goal is to keep forks close without hiding risk or skipping review when
custom work exists.
</details>
<details>
<summary><strong>The box and workspaces</strong></summary>
Spoon's optional agent worker is designed to run outside Convex actions. The
worker claims queued jobs, clones the current GitHub fork into the owner's
persistent home, creates a branch, and exposes workspace operations to the Next
app through server-only API proxies.
Everything runs inside **one long-running container per user**,
`spoon-box-{username}`. The box is a Fedora image (`docker/agent-job.Dockerfile`)
started with `sleep infinity` and a `--memory 4g` / `--cpus 2` cap. Each
thread's agent turn, terminal, and project commands `docker exec` into that same
box; the box's `/home/{username}` is a bind-mounted persistent home so dotfiles,
installed tools, shell history, and thread checkouts under `~/Code/{spoon}/{branch}`
survive across sessions. The box is reference-counted by the worker and reaped
after `SPOON_AGENT_BOX_IDLE_MS` idle (default 30m). There is no per-job
`docker run --rm` container and no separate `opencode serve` container — that
path was removed.
Workspace capabilities:
- browse repository files
- edit files in a browser editor
- use optional Vim keybindings
- resize the agent thread panel on desktop
- inspect diffs
- send thread messages to the agent
- run configured commands
- use an interactive terminal (xterm.js → box PTY; see
[docs/agent-terminal.md](docs/agent-terminal.md))
- store logs and artifacts
- push a branch
- open a draft PR
The browser never receives worker tokens and never talks directly to the worker
or the box.
Worker cleanup is available in `Settings -> Worker`. It can delete stale
workspace records and ask the active worker to remove orphaned containers and
inactive work directories (persistent per-user homes are preserved).
Local worker development:
```sh
scripts/build-agent-images
bun smoke:agent-container
bun dev:next:worker
bun dev:next:worker:staging
```
Local host-run worker commands load env through Infisical, then
`scripts/dev-agent-worker` selects Podman when available and falls back to
Docker. Override the container CLI with:
```env
SPOON_AGENT_CONTAINER_RUNTIME=podman
```
</details>
<details>
<summary><strong>Agent runtimes</strong></summary>
Three agent CLIs run inside the box behind one `AgentRuntime` adapter interface
(`apps/agent-worker/src/runtime/*-adapter.ts`), selected by `job.runtime`:
| Runtime | CLI | Selected for |
| ---------- | ----------------------------------- | -------------------------------------------------- |
| `codex` | `@openai/codex` | OpenAI providers and Codex ChatGPT-login snapshots |
| `opencode` | `opencode-ai` | OpenAI-compatible API-key providers (default) |
| `claude` | `@anthropic-ai/claude-code@2.1.207` | Anthropic providers |
The runtime is validated at queue time (`runtimeSupport.ts` +
`agentJobs.insertJob`): a job is rejected unless the resolved runtime is one the
AI provider profile supports. `runtimesForProfile` maps profiles to runtimes —
ChatGPT-login snapshots → `codex` only; `openai` API keys → `opencode`/`codex`;
Anthropic API keys → `claude`/`opencode`; Anthropic `anthropic_oauth_json`
credential snapshots → `claude` only; every other OpenAI-compatible API key →
`opencode`.
Auth is materialized into the box before a turn:
- **Codex ChatGPT-login** profiles get the encrypted `auth.json` written to
`CODEX_HOME/.codex/auth.json`.
- **Anthropic API-key** profiles authenticate via `ANTHROPIC_API_KEY` in the
environment (no file needed).
- **Anthropic `anthropic_oauth_json`** credential-snapshot profiles get the
encrypted OAuth blob written to `~/.claude/.credentials.json`.
- **API-key** profiles run through OpenCode.
All materialized auth files are written `0600`. Treat those saved auth files
like a password and only use them on trusted workers.
</details>
<details>
<summary><strong>Production runtime images</strong></summary>
Gitea CI builds and pushes three production images:
```txt
git.gbrown.org/gib/spoon-next:latest
git.gbrown.org/gib/spoon-agent-worker:latest
git.gbrown.org/gib/spoon-agent-job:latest
```
The worker image is the long-running service that polls Convex. The
`spoon-agent-job` image is the **box** image — the per-user Fedora dev box the
worker `exec`s into (it is not launched fresh per job). Point the worker at it
with:
```env
SPOON_AGENT_JOB_IMAGE="git.gbrown.org/gib/spoon-agent-job:latest"
```
The box image is Fedora 41 with Node, Bun, pnpm and yarn, npm, git, ripgrep,
Python, build tools, plus interactive tooling (neovim, tmux, fzf, fd, bat, eza,
zoxide, gh, gum, oh-my-posh) and the pinned agent CLIs OpenCode, Codex, and
Claude Code. It is not the forked project's production runtime; it is the agent
execution environment.
Production worker runtime requirements:
- `spoon-agent-worker` must run as a separate service.
- The worker needs `/var/run/docker.sock` mounted so it can create and `exec`
into per-user boxes on the host daemon.
- Production should keep `SPOON_AGENT_CONTAINER_RUNTIME=docker`.
- `SPOON_AGENT_HOST_WORKDIR` must equal the absolute host path backing
`SPOON_AGENT_WORKDIR` (identical inside and outside the worker container) so
the host daemon can bind-mount per-user homes under `${WORKDIR}/homes/{username}`.
- The production Docker host must be logged into `git.gbrown.org` so the worker
can pull the private `spoon-agent-job` box image.
- `SPOON_WORKER_TOKEN` must match the value stored in Convex production env.
- `spoon-next` needs `SPOON_AGENT_WORKER_URL=http://spoon-agent-worker:3921` and
`SPOON_AGENT_WORKER_INTERNAL_TOKEN` so Next API routes can proxy workspace and
box file, diff, message, command, terminal, and draft PR actions.
- `spoon-agent-worker` also needs `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`.
If the private key is stored in a single-line dotenv value, encode newlines as
literal `\n` characters so the worker can restore the PEM before using it.
Useful production checks:
```sh
docker login git.gbrown.org
docker pull git.gbrown.org/gib/spoon-agent-worker:latest
docker pull git.gbrown.org/gib/spoon-agent-job:latest
docker logs --tail=200 spoon-agent-worker
curl -H "Authorization: Bearer $SPOON_AGENT_WORKER_INTERNAL_TOKEN" \
http://spoon-agent-worker:3921/health
```
Deployment readiness checklist:
1. Production Convex env has `SPOON_WORKER_TOKEN`, `SPOON_ENCRYPTION_KEY`,
GitHub App env (including `GITHUB_APP_WEBHOOK_SECRET`), and Convex Auth
signing keys.
2. Compose env has `SPOON_AGENT_WORKER_URL`,
`SPOON_AGENT_WORKER_INTERNAL_TOKEN`, `SPOON_AGENT_JOB_IMAGE`, and the GitHub
App private key; the `spoon-next` image is built with
`NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL`.
3. The production Docker host can pull private images from `git.gbrown.org`.
4. `Settings -> Worker` reports the expected box image, runtime, network, and
active box count.
5. The first test thread uses a configured API-key provider or a trusted Codex
login profile.
6. If a worker restart leaves stale state, use the workspace recovery panel or
`Settings -> Worker` cleanup.
</details>
<details>
<summary><strong>GitHub webhooks</strong></summary>
Spoon exposes a signature-verified webhook as a Convex `httpAction` at
`POST /webhooks/github` (`packages/backend/convex/githubWebhooks.ts`,
`http.ts`). Configure the GitHub App webhook URL as
`<CONVEX_SITE_URL>/webhooks/github` and set `GITHUB_APP_WEBHOOK_SECRET`; the
endpoint fails closed (HTTP 503) when no secret is configured and rejects any
payload whose `x-hub-signature-256` does not verify.
- `push` → a targeted drift refresh for every Spoon tracking that repository.
- `installation` / `installation_repositories` → the matching connection's
status flips to `revoked` (deleted/suspended), `active` (created/unsuspended),
or otherwise `needs_reauth`, surfaced in `Settings -> Integrations`.
The hourly `refreshDueSpoons` cron remains a fallback, so drift still refreshes
even if a webhook is missed.
</details>
<details>
<summary><strong>Notifications</strong></summary>
Spoon delivers an in-app bell plus preference-gated transactional email
(`packages/backend/convex/notifications.ts`). Five kinds are emitted:
`maintenance_thread`, `agent_turn_finished`, `agent_needs_input`, `sync_failed`,
and `connection_needs_reauth`.
- Every event inserts an in-app notification (bell badge via `unreadCount`,
list via `listMine`, `markRead` / `markAllRead`).
- Email is sent through UseSend (`USESEND_*`) only when the user has an email
and the matching per-kind preference is not disabled. Unset preferences
default to enabled.
- Per-kind email toggles live in `Settings -> Notifications`. Web push is out of
scope.
</details>
## Architecture
<details open>
<summary><strong>Workspace layout</strong></summary>
```txt
.
├── apps
│ ├── next # Next.js 16 web app and primary Spoon UI
│ ├── agent-worker # Optional OpenCode workspace / draft PR worker
│ └── expo # Expo companion app scaffold
├── packages
│ ├── backend # Convex backend package
│ │ └── convex # Schema, functions, auth, HTTP routes
│ └── ui # Shared shadcn-based UI components
├── tools # Shared lint, format, Tailwind, TS, Vitest config
├── docker # Compose files and worker/job Dockerfiles
└── scripts # Env, Convex, codegen, database, and CI helpers
```
</details>
<details>
<summary><strong>Core tables</strong></summary>
| Table | Purpose |
| ------------------------- | ----------------------------------------------------------- |
| `spoons` | Managed fork records |
| `threads` | Durable maintenance and work conversations |
| `threadMessages` | Messages inside threads |
| `syncRuns` | Upstream checks, sync attempts, and maintenance decisions |
| `ignoredUpstreamChanges` | Intentional ignore records that affect effective drift |
| `gitConnections` | Git provider connection metadata |
| `spoonRepositoryStates` | Latest cached upstream/fork state |
| `spoonCommits` | Cached upstream and fork-only commits |
| `spoonPullRequests` | Cached fork/upstream pull requests |
| `spoonSecrets` | Encrypted per-Spoon environment variables |
| `spoonAgentSettings` | Per-Spoon runtime, branch, command, and env-file settings |
| `aiProviderProfiles` | Encrypted provider/auth profiles (Codex/OpenCode/Anthropic) |
| `agentJobs` | Worker-executed workspace jobs and PR lifecycle |
| `agentJobEvents` | Append-only worker event log |
| `agentJobArtifacts` | Diffs, summaries, command output, PR body drafts |
| `agentWorkspaceChanges` | Recorded user, agent, and command file changes |
| `userDotfiles` | Encrypted per-user dotfiles overlay + repo/setup config |
| `userEnvironment` | Per-user box/home environment config |
| `notifications` | In-app notification rows (bell) |
| `notificationPreferences` | Per-user email notification toggles |
</details>
<details>
<summary><strong>Important routes</strong></summary>
| Route | Purpose |
| --------------------------------- | ----------------------------------------------- |
| `/` | Public product landing page |
| `/dashboard` | Maintenance overview |
| `/spoons` | Managed fork list |
| `/spoons/new` | Manual/GitHub Spoon creation |
| `/spoons/[spoonId]` | Spoon detail dashboard |
| `/spoons/[spoonId]/agent/[jobId]` | Interactive workspace |
| `/threads` | Global thread queue |
| `/threads/[threadId]` | Thread detail |
| `/machine` | My Machine: box status, terminal, home |
| `/settings/profile` | User profile settings |
| `/settings/integrations` | GitHub and service integration settings |
| `/settings/ai-providers` | AI provider profiles (Codex/OpenCode/Anthropic) |
| `/settings/dotfiles` | Per-user dotfiles overlay + repo |
| `/settings/notifications` | Email notification preferences |
The Convex `httpAction` `POST /webhooks/github` (mounted at
`<CONVEX_SITE_URL>/webhooks/github`) handles GitHub App webhooks. Legacy
`/updates` and `/agents` routes redirect into `/threads`.
</details>
## Mobile App
<details open>
<summary><strong>Current Expo scope</strong></summary>
`apps/expo` is the mobile Spoon client. It is designed to mirror the core web
product without exposing worker internals or trying to turn a phone into the
primary code-editing surface.
The mobile app currently supports:
- password, GitHub, and Authentik sign-in
- Dashboard, Spoons, Threads, Workspace Review, and Settings tabs/screens
- manual Spoon creation and GitHub-assisted repository tracking
- Spoon detail views for overview, upstream commits, fork-only commits, PRs,
threads, settings, clone URLs, and additional remotes
- Spoon maintenance settings, agent settings, encrypted secrets, and bulk
`.env` paste import
- thread list/detail, message composer, resolve/cancel actions, and workspace
review links
- GitHub integration status and repository listing
- AI provider profile management, including Codex auth JSON and API-key
providers
- read-only workspace review for job status, messages, diffs, events,
artifacts, and draft PR links
The mobile app intentionally does not currently support:
- live workspace file browsing/editing
- mobile command execution
- direct mobile calls to the agent worker HTTP API
- mobile access to worker/container tokens
- long-running app preview stacks
- production app-store/EAS release flow
Mobile workspace editing is deferred until worker authorization and mobile
editor UX are designed explicitly. For now, the phone is a strong review and
control surface; the browser remains the code workspace.
</details>
<details>
<summary><strong>Expo validation</strong></summary>
Useful mobile checks:
```sh
bun --filter @spoon/expo lint
bun --filter @spoon/expo typecheck
bun --filter @spoon/expo test:unit
bun --filter @spoon/expo test:component
```
The Expo unit tests cover pure utilities such as `.env` parsing and formatting.
The component tests use a lightweight React Native mock layer to exercise shared
mobile controls, higher-value forms, and route smoke renders without booting a
native simulator.
</details>
## Environment Reference
This project is currently private, so this section is a reference for what the
application expects rather than public setup documentation.
<details>
<summary><strong>Local Infisical account selection</strong></summary>
Local `dev` and `staging` commands export secrets through Infisical. Spoon runs
`scripts/infisical-account ensure` from `scripts/export-env` before exporting so
machines logged into multiple Infisical accounts do not accidentally use the
wrong organization.
If your machine has only one local Infisical account, no extra setup is needed.
If it has multiple accounts, create this ignored local file:
```sh
mkdir -p .local
printf "INFISICAL_EMAIL=me@gbrown.org\n" > .local/infisical.env
```
Log into each needed account once with `infisical login`. You can inspect local
profiles without printing tokens:
```sh
jq '.loggedInUsers[] | {email, domain}' ~/.infisical/infisical-config.json
```
`.local/infisical.env` supports only `INFISICAL_EMAIL=...` and must not be
committed. CI is unchanged; it uses injected environment files/secrets and must
not call Infisical.
</details>
<details open>
<summary><strong>Public Next variables</strong></summary>
| Variable | Used for |
| --------------------------------------- | ------------------------------------------------------------------- |
| `NEXT_PUBLIC_SITE_URL` | Canonical Spoon web URL |
| `NEXT_PUBLIC_CONVEX_URL` | Convex client URL |
| `NEXT_PUBLIC_SPOON_AGENT_WORKER_WS_URL` | Browser-facing worker WS base for the terminal (**build-time** var) |
| `NEXT_PUBLIC_DEPLOYMENT_URL` | Convex dashboard/deployment URL when needed |
| `NEXT_PUBLIC_PLAUSIBLE_URL` | Plausible analytics endpoint |
| `NEXT_PUBLIC_SENTRY_DSN` | Browser Sentry DSN |
| `NEXT_PUBLIC_SENTRY_URL` | Sentry instance URL |
| `NEXT_PUBLIC_SENTRY_ORG` | Sentry organization |
| `NEXT_PUBLIC_SENTRY_PROJECT_NAME` | Sentry project name |
</details>
<details>
<summary><strong>Auth and email</strong></summary>
| Variable | Used for |
| ----------------------- | ----------------------------- |
| `SITE_URL` | Convex Auth site URL |
| `JWT_PRIVATE_KEY` | Convex Auth signing key |
| `JWKS` | Convex Auth JWKS |
| `AUTH_AUTHENTIK_ID` | Authentik OAuth client ID |
| `AUTH_AUTHENTIK_SECRET` | Authentik OAuth client secret |
| `AUTH_AUTHENTIK_ISSUER` | Authentik issuer URL |
| `AUTH_GITHUB_ID` | GitHub OAuth client ID |
| `AUTH_GITHUB_SECRET` | GitHub OAuth client secret |
| `USESEND_API_KEY` | UseSend API key |
| `USESEND_URL` | UseSend API URL |
| `USESEND_FROM_EMAIL` | Transactional email sender |
</details>
<details>
<summary><strong>GitHub App</strong></summary>
| Variable | Used for |
| ---------------------------- | ---------------------------------- |
| `GITHUB_APP_ID` | GitHub App ID |
| `GITHUB_APP_CLIENT_ID` | GitHub App OAuth client ID |
| `GITHUB_APP_CLIENT_SECRET` | GitHub App OAuth client secret |
| `GITHUB_APP_PRIVATE_KEY` | GitHub App PEM private key |
| `GITHUB_APP_WEBHOOK_SECRET` | GitHub webhook verification secret |
| `GITHUB_APP_SLUG` | GitHub App slug |
| `GITHUB_APP_INSTALLATION_ID` | Default/local installation ID |
| `GITHUB_APP_OWNER` | Default/local installation owner |
</details>
<details>
<summary><strong>Convex, storage, and runtime</strong></summary>
| Variable | Used for |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `CONVEX_SELF_HOSTED_URL` | Self-hosted Convex API URL |
| `CONVEX_SELF_HOSTED_ADMIN_KEY` | Admin key for deploying/syncing Convex |
| `CONVEX_CLOUD_ORIGIN` | Convex backend origin |
| `CONVEX_SITE_ORIGIN` | Convex site-function origin |
| `CONVEX_SITE_URL` | Site URL seen by Convex Auth |
| `POSTGRES_URL` | Convex storage database URL |
| `SPOON_ENCRYPTION_KEY` | Encryption key for stored secrets/provider auth |
| `SPOON_WORKER_TOKEN` | Worker token for Convex worker mutations |
| `SPOON_AGENT_WORKER_URL` | Internal worker HTTP URL used by Next |
| `SPOON_AGENT_WORKER_HTTP_PORT` | Worker HTTP port |
| `SPOON_AGENT_WORKER_INTERNAL_TOKEN` | Server-only token for Next-to-worker proxy |
| `SPOON_AGENT_TERMINAL_SECRET` | HMAC secret for terminal/box tokens (falls back to the internal/worker token) |
| `SPOON_AGENT_JOB_IMAGE` | Per-user box (Fedora) image the worker `exec`s into |
| `SPOON_AGENT_RUNTIME` | Runtime mode, currently Docker/Podman-oriented |
| `SPOON_AGENT_CONTAINER_RUNTIME` | Container CLI used by worker, `docker`/`podman` |
| `SPOON_AGENT_BOX_IDLE_MS` | Idle time before a per-user box is reaped (default `1800000`) |
| `SPOON_AGENT_MAX_CONCURRENT_JOBS` | Worker concurrency limit |
| `SPOON_AGENT_JOB_TIMEOUT_MS` | Agent turn timeout |
| `SPOON_AGENT_WORKDIR` | Worker work directory; per-user homes live under `homes/{username}` |
| `SPOON_AGENT_HOST_WORKDIR` | Host path matching `SPOON_AGENT_WORKDIR` when the worker runs in Docker and controls the host Docker socket |
| `SPOON_AGENT_NETWORK` | Optional box container network |
</details>
<details>
<summary><strong>Deployment and observability</strong></summary>
| Variable | Used for |
| ----------------------- | --------------------------------- |
| `NODE_ENV` | Runtime environment |
| `SENTRY_AUTH_TOKEN` | Sentry source map/upload auth |
| `REDACT_LOGS_TO_CLIENT` | Convex log redaction setting |
| `DISABLE_BEACON` | Self-hosted Convex beacon setting |
| `DO_NOT_REQUIRE_SSL` | Self-hosted Convex SSL behavior |
| `CI_ENV_FILE` | CI-provided env file path |
</details>
## Current Status
<details open>
<summary><strong>Implemented</strong></summary>
- Thread-first Next.js product shell
- GitHub App connection and fork creation foundation
- GitHub drift refresh, commit cache, PR cache, and sync-run history
- Effective drift and ignored upstream change records
- Global Threads page and Spoon-scoped Threads tab
- Persistent per-user box the worker `exec`s into for agent turns, terminal, and
commands
- Three agent runtimes (Codex, OpenCode, Claude Code) behind one adapter, with
queue-time runtime validation
- My Machine surface (box status/start/stop/restart, terminal, home browser)
- Interactive workspace terminal (xterm.js → box PTY)
- Per-user dotfiles overlay + optional dotfiles repo
- Signature-verified GitHub webhook with hourly cron fallback
- In-app + email notifications with per-user preferences
- Monaco editor with optional Vim mode
- Diff viewer, command panel, worker logs, and artifacts
- Encrypted Spoon secrets and bulk `.env` import
- Encrypted AI provider profiles, including Codex/Anthropic auth JSON and
API-key provider support
- Authentik, GitHub, and password auth through Convex Auth
- Self-hosted Convex/Postgres deployment model
</details>
<details>
<summary><strong>Intentionally not done yet</strong></summary>
- Autonomous merging for custom/diverged forks
- Non-GitHub provider automation
- Pushing agent branches to additional remotes
- Long-running preview stacks for arbitrary forked projects
- Direct browser access to worker containers
- Public self-hosting setup documentation
- Production mobile release flow
</details>
## Notes
Spoon is built for a very specific maintenance problem: "I want to fork this
project, but I do not want to permanently become its maintenance team."
The current product direction is to make that maintenance visible, threaded,
reviewable, and increasingly automated where it is safe. Clean forks can stay
close automatically. Custom forks get context, workspace help, and draft PRs.