Get unstuck without skipping straight to the answer.
Progressive hints, clean editorials, and full C++ solutions for Codeforces problems. Generated through OpenCode, served at your own pace.
Install dependencies and start the app with Bun:
bun install
bun run devCopy the variables documented in .env.example into your local environment. The app requires a PostgreSQL database whose schema matches prisma/schema.prisma.
Run the full local quality gate before shipping:
bun run checkMost editorial sites give you the whole answer or nothing. Nudge sits in between: every problem has progressive hints that go from a gentle nudge toward the right area all the way to the key insight, plus a prose editorial and the full C++ solution when you're ready.
All content is generated locally through OpenCode, then stored in Postgres and served through a Next.js frontend. Problems are synced from the Codeforces API automatically.
Generation is local-only. Trigger.dev does not run OpenCode and there is no encoded cloud credential path.
Sign in with
opencode auth login, choose OpenAI, and select ChatGPT Plus/Pro. Confirmopencode auth listshows OpenAI OAuth.Make sure
DATABASE_URLpoints at the Nudge database.Connect a public Vercel Blob store to the project and set
BLOB_READ_WRITE_TOKENlocally.bunx vercel env pullcan pull the connected store credentials.Set
CACHE_REVALIDATION_URLto the deployed app URL. Set the same strongCACHE_REVALIDATION_SECRETlocally, in Trigger.dev, and in the deployed Next.js app. Local generation and Trigger.dev use these values to expire deployed problem caches after database writes.Configure the model, reasoning variant, and public display label in
nudge.config.json(see Switching models or providers).{ "model": "openai/gpt-5.6-sol", "variant": "max", "display": { "model": "GPT-5.6 Sol", "reasoning": "max" } }Run one queued generation:
bun run opencode:next
To run several queued generations sequentially:
bun run opencode:next -- 3
The explicit form also works:
bun run opencode:next -- --count 3
For a no-write preview of the next candidate:
bun run opencode:next -- --dry-runEach real run claims one eligible problem, creates an isolated OpenCode session, persists the generated hints/editorial/solution, records usage, mirrors the exact opencode export bytes into .opencode-runs, uploads that file to the public Blob store, and prints both transcript locations. OpenCode reads provider credentials from its normal local credential store; Nudge never copies them into project configuration.
Never hand-type a model ID from memory; copy it verbatim from the CLI. bun run models queries the same bundled OpenCode runtime that generation spawns and validates against, drilling down in three steps:
- Connect the provider (skip if already authed): run
opencode auth login, then confirm withopencode auth list. - Pick a provider:
bun run modelslists connected providers. - Pick a model:
bun run models <provider>lists its models, marking with ✓ the ones that meet Nudge's requirements (capabilities.toolcallandcapabilities.input.image). Copy the fullprovider/model-idstring, slashes and dashes included. - Pick a variant:
bun run models <provider>/<model>shows the validvariantvalues, the model's display name, and a ready-to-pastenudge.config.jsonblock. - Verify without generating anything:
bun run opencode:next -- --dry-runruns preflight, which validates the model ID, variant, and required capabilities against live provider metadata and prints the exact display name. A typo'd variant fails fast withdoes not expose the <variant> variantinstead of wasting a generation.
Notes:
- The config is strict-parsed on startup: unknown keys or a
modelmissing theprovider/prefix are rejected immediately. variantis optional and must be one of the variant keys from step 4.display.modelis the public label — free text, so fix naming there, not inmodel.display.reasoningis optional and falls back tovariantwhen omitted. The label renders asdisplay.model (reasoning), or justdisplay.modelwhen neither is set, so the default configuration rendersGPT-5.6 Sol (max).display.*is cosmetic and safe to edit anytime, but the label is stored on each problem at generation time — existing content keeps the label it was generated with, and the new label applies to future generations.