{"id":50922136,"url":"https://github.com/sshcrack/opencode-discord-agent","last_synced_at":"2026-06-16T19:03:08.487Z","repository":{"id":363659662,"uuid":"1264321986","full_name":"sshcrack/opencode-discord-agent","owner":"sshcrack","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-09T21:23:11.000Z","size":104,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T21:23:14.017Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sshcrack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-09T19:22:44.000Z","updated_at":"2026-06-09T20:02:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sshcrack/opencode-discord-agent","commit_stats":null,"previous_names":["sshcrack/opencode-discord-agent"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sshcrack/opencode-discord-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshcrack%2Fopencode-discord-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshcrack%2Fopencode-discord-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshcrack%2Fopencode-discord-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshcrack%2Fopencode-discord-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sshcrack","download_url":"https://codeload.github.com/sshcrack/opencode-discord-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshcrack%2Fopencode-discord-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34419350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-16T19:03:07.555Z","updated_at":"2026-06-16T19:03:08.472Z","avatar_url":"https://github.com/sshcrack.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# opencode-discord\n\nA Discord bot that turns slash commands into AI-powered PRs. Users file bug reports, feature requests, or tasks via Discord, and a worker processes them with opencode (plan + build agents) to produce GitHub pull requests. Includes a web-based plan viewer/editor SPA.\n\n## Architecture\n\n```mermaid\nflowchart TB\n    subgraph Discord\n        T[Report Threads]\n        RC[Repo Channels]\n        B[Bot]\n    end\n\n    subgraph BotServer[\"Bot (Bun HTTP)\"]\n        TR[tRPC Server]\n        PV[Plan Viewer SPA]\n        PA[Plan REST API]\n    end\n\n    subgraph DB[\"SQLite (Prisma)\"]\n        R[Repositories]\n        J[Jobs]\n        S[Settings]\n        RT[Report Threads]\n    end\n\n    subgraph Worker[\"Worker (dev laptop)\"]\n        PL[Poll Loop]\n        JH[Job Handler]\n        GWQ[gwq worktrees]\n        OC[opencode agents]\n        GH[gh CLI]\n    end\n\n    subgraph GitHub\n        IS[Issues]\n        PR[Pull Requests]\n    end\n\n    B -- threads / messages --\u003e T\n    B -- repo channels --\u003e RC\n    B -- reads/writes --\u003e DB\n    Worker -- tRPC (HTTP Bearer) --\u003e TR\n    Worker -- creates --\u003e GWQ\n    Worker -- runs --\u003e OC\n    Worker -- creates/merges --\u003e GH\n    GH --\u003e IS\n    GH --\u003e PR\n    TR -- serves --\u003e PV\n    PV -- calls --\u003e PA\n    PA -- reads/writes planMd --\u003e DB\n    B -- fallback path --\u003e OC\n    B -- fallback path --\u003e GH\n```\n\nKey flows:\n\n- **Poll loop:** Worker → `pollNextJob` → claims pending jobs\n- **Job pipeline:** claim → issue → plan → approval → build → PR\n- **Status updates:** Worker → `postStatus` → Discord thread\n- **Plan viewer:** User opens `BOT_URL/plan-viewer/?jobId=N\u0026token=...` → edits plan → saves via PUT → approves via POST\n- **Fallback path:** Bot runs opencode + gh when no worker is online (no heartbeat within 60s)\n- **Stale-job sweep:** Bot releases jobs from workers with no heartbeat \u003e 2 min (every 60s)\n- **Auto-merge check:** Bot polls `gh pr view` for merged PRs → auto-closes thread\n\nThree packages in a Bun workspace:\n\n| Package | Entrypoint | Key Contents |\n|---------|------------|-------------|\n| `packages/shared` | `src/index.ts` | Zod schemas, types, stub tRPC router (for type safety only) |\n| `packages/bot` | `src/index.ts` | Discord.js client, Prisma/SQLite, tRPC server (real router), plan-viewer SPA (`public/plan-viewer/`) |\n| `packages/worker` | `src/index.ts` | Poll loop, `gwq`/`opencode`/`gh` orchestration, job pipeline (5 steps) |\n\n## Prerequisites\n\n- **Bun** ≥ 1.2\n- **Discord bot** with `applications.commands` scope and Gateway Intents: `Guilds`, `GuildMessages`, `MessageContent`\n- **gwq** installed and on PATH (git worktree manager)\n- **opencode** installed and on PATH\n- **gh** (GitHub CLI) authenticated\n- **oxlint** (optional, for pre-commit linting)\n- A repository directory with a checked-out git repo (the worker does **not** clone)\n- **Discord \"Repositories\" category** — auto-created on startup, one text channel per repo\n\n## Setup\n\n```bash\n# 1. Install dependencies\nbun install\n\n# 2. Create bot environment (copy root .env.example)\ncp .env.example packages/bot/.env\n# Edit packages/bot/.env with your values\n\n# 3. Generate Prisma client \u0026 run initial migration\ncd packages/bot\nbunx --bun prisma generate\nbunx --bun prisma migrate dev --name init\ncd ../..\n\n# 4. Deploy slash commands (requires CLIENT_ID in .env)\nbun run bot-deploy\n```\n\n**Important:** Always use `prisma migrate dev` for schema changes, never `prisma db push`. Run all Prisma commands from `packages/bot/` where `prisma.config.ts` picks up `DATABASE_URL` from `.env`.\n\n## Environment Variables\n\n### Bot (`packages/bot/.env`)\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `DISCORD_TOKEN` | ✅ | — | Discord bot token |\n| `CLIENT_ID` | ✅ (for deploy) | — | Discord application client ID |\n| `SHARED_SECRET` | ✅ | — | Shared secret for tRPC auth (must match worker) |\n| `DATABASE_URL` | ✅ | `file:./dev.db` | SQLite DB path |\n| `TRPC_PORT` | — | `3000` | tRPC HTTP server port |\n| `ALLOWED_GUILD_ID` | — | — | Restrict bot to this guild only |\n| `ALLOWED_USER_ID` | — | — | Restrict bot to this user only |\n| `GH_TOKEN` | — | — | GitHub PAT for bot account (used by fallback path) |\n| `GIT_BOT_NAME` | — | `opencode-bot` | Git commit author name (for fallback) |\n| `GIT_BOT_EMAIL` | — | `opencode-bot@users.noreply.github.com` | Git commit author email |\n\n### Worker (shell env or `packages/worker/.env`)\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `SHARED_SECRET` | ✅ | — | Must match bot's shared secret |\n| `WORKER_ID` | — | `default` | Worker identity (e.g. `my-laptop`) |\n| `BOT_URL` | — | `http://localhost:3000` | Bot's tRPC endpoint |\n| `DRY_RUN` | — | `false` | Skip `gwq`/`opencode`/`gh` execution, log only |\n| `SKIP_PERMISSIONS` | — | `true` | Pass `--dangerously-skip-permissions` to opencode |\n| `GH_TOKEN` | — | — | GitHub PAT for worker operations |\n| `GIT_BOT_NAME` | — | `opencode-bot` | Git commit author name |\n| `GIT_BOT_EMAIL` | — | `opencode-bot@users.noreply.github.com` | Git commit author email |\n| `GIT_COAUTHOR_NAME` | — | — | Your name (for `Co-authored-by` trailer) |\n| `GIT_COAUTHOR_EMAIL` | — | — | Your GitHub email (for `Co-authored-by` trailer) |\n\n\u003e **No model env variables** — the issue model, fallback model, auto-mode, quick-mode, and verbose-mode are stored in the database `Setting` table, not env variables.\n\n## Running\n\n```bash\n# ── Bot ───────────────────────────────────────────────────────\nbun run dev:bot            # watch mode\n# or: bun run --cwd packages/bot dev\n\n# ── Worker ─────────────────────────────────────────────────────\nbun run dev:worker         # watch mode\n# or: SHARED_SECRET=\"...\" WORKER_ID=\"my-laptop\" bun run --cwd packages/worker dev\n\n# ── Worker daemon (restart loop) ───────────────────────────────\n./run-worker.sh            # foreground with restart loop\n./run-worker.sh --daemon   # background via nohup\n./run-worker.sh stop       # stop daemon\n\n# ── Deploy slash commands ─────────────────────────────────────\nbun run bot-deploy         # requires CLIENT_ID in .env\n```\n\n## Discord Commands\n\n| Command | Description |\n|---------|-------------|\n| `/repo add \u003cslug\u003e \u003cpath\u003e [origin-url]` | Register a repository (first becomes default, creates Discord channel) |\n| `/repo remove \u003cslug\u003e` | Remove a repository record (deletes Discord channel, prevents if active jobs) |\n| `/repo list` | List all registered repositories (default marked with ⭐) |\n| `/repo set-default \u003cslug\u003e` | Change the default repository |\n| `/repo sync-channels` | Create missing Discord channels for all registered repos |\n| `/create-report \u003ckind\u003e [repo]` | Create a private report thread (uses repo-specific channel if bound, else creates in current channel) |\n| `/submit [auto] [quick]` | Submit thread as a job (collects messages as context, offers follow-up continuation) |\n| `/resolve \u003cissue\u003e [auto] [quick]` | Create a fix job from a GitHub issue URL or short description |\n| `/review \u003cpr_url\u003e [auto] [quick]` | Review a GitHub PR, fix issues, and merge (creates thread or uses current) |\n| `/review-merge` | Run review agent on the PR from the last completed job in this thread |\n| `/close` | Close and archive the current report thread (cancels active jobs) |\n| `/update` | Pull latest code via git, run migrations, restart bot |\n| `/clear-session` | Delete all bot messages in the current thread |\n| `/help` | Show categorized list of all commands |\n| `/jobs [repo] [status] [limit]` | List recent jobs with optional filters (emoji per status) |\n| `/settings view` | View all current bot settings |\n| `/settings model \u003cname\u003e` | Set the issue generation model |\n| `/settings fallback-model \u003cname\u003e` | Set the fallback model |\n| `/set-auto \u003cmode\u003e` | Set global auto-approve mode (on/off) |\n| `/set-quick \u003cmode\u003e` | Set global quick mode (on/off — skip planning, build directly) |\n| `/set-verbose \u003cmode\u003e` | Toggle verbose status reporting (on/off) |\n\n## Job Flow\n\n```mermaid\nflowchart TD\n    CR[\"/create-report\"] --\u003e T[Private report thread created]\n    T --\u003e SUB[/submit or /resolve\\]\n    SUB --\u003e J[Job created: status=pending]\n    J --\u003e WO{Worker online?}\n\n    WO --\u003e|No| FALLBACK[Fallback path]\n    WO --\u003e|Yes| CLAIM[Worker claims job: status=claimed]\n\n    CLAIM --\u003e WT[gwq add -b report-N : create worktree]\n    WT --\u003e ISSUE[opencode run --model \u003cissue_model\u003e : generate GitHub issue]\n    ISSUE --\u003e RENAME[Rename thread to #N title]\n\n    RENAME --\u003e QM{quickMode?}\n    QM --\u003e|Yes| BUILD[Build agent]\n    QM --\u003e|No| PLAN[opencode run --agent plan : write PLAN.md]\n\n    PLAN --\u003e POST[Post plan to thread + plan-viewer URL]\n    POST --\u003e APPROVAL{Approval loop}\n\n    APPROVAL --\u003e|Approve (button)| BUILD\n    APPROVAL --\u003e|Auto-approve (10s countdown)| BUILD\n    APPROVAL --\u003e|Suggest changes| SUGGEST[\"opencode --session --continue : revise plan\"]\n    SUGGEST --\u003e POST\n\n    subgraph AGENT_FLOW[Agent Question Flow]\n        PLAN --\u003e ASK{Agent has questions?}\n        ASK --\u003e|Yes| Q_SHOW[Show questions in thread via Discord buttons + text input]\n        Q_SHOW --\u003e Q_ANS[User answers]\n        Q_ANS --\u003e Q_INJECT[\"opencode --session --continue with Q\u0026A\"]\n        Q_INJECT --\u003e PLAN\n        ASK --\u003e|No| POST\n    end\n\n    BUILD --\u003e PR[\"gh pr create : PR URL\"]\n    PR --\u003e DONE[\"status=done, Review \u0026 Merge button\"]\n    DONE --\u003e RM[/review-merge or button\\]\n    RM --\u003e RM_JOB[Review-merge job created]\n\n    subgraph REVIEW_MERGE[Review \u0026 Merge Pipeline]\n        RM_JOB --\u003e CHECKOUT[Fetch PR branch + reset to base]\n        CHECKOUT --\u003e REVIEW[opencode --agent 'PR review merge' : review loop]\n        REVIEW --\u003e|Clean| MERGE[gh pr merge --auto --squash / --squash]\n        REVIEW --\u003e|Issues found| FIX[opencode run --agent build : fix issues]\n        FIX --\u003e REVIEW\n        MERGE --\u003e CLOSE[Thread closed + archived]\n    end\n\n    subgraph STALE[Background Tasks - Bot 60s interval]\n        SWEEP[Release jobs from workers with heartbeat \u003e 2 min stale]\n        AMC[Check merged PRs via gh pr view → auto-close thread]\n    end\n\n    subgraph FALLBACK_PATH[Fallback Path]\n        FALLBACK --\u003e CLONE[Clone repo to temp dir]\n        CLONE --\u003e GEN_ISSUE[opencode run --model \u003cfallback_model\u003e : generate issue]\n        GEN_ISSUE --\u003e GH_CREATE[gh issue create]\n        GH_CREATE --\u003e GH_COMMENT[gh issue comment with /opencode fix]\n        GH_COMMENT --\u003e FALLBACK_DONE[status=done]\n    end\n```\n\n### Fallback path (no worker online)\n\n1. Bot clones the repo to a temp directory\n2. Bot runs `opencode run --model \u003cfallback_model\u003e` to generate an issue title + body\n3. Bot runs `gh issue create` and posts the issue URL to the thread\n4. Bot runs `gh issue comment` with `/opencode fix this issue in a PR`\n5. Job marked done\n\n## Database Settings\n\nThese settings are stored in the `Setting` table and can be changed via Discord commands:\n\n| Key | Default | Description | Changed via |\n|-----|---------|-------------|-------------|\n| `auto_mode` | `off` | Global auto-approve mode | `/set-auto` |\n| `quick_mode` | `off` | Skip planning, build directly | `/set-quick` |\n| `verbose_mode` | `on` | Suppress info-level status messages | `/set-verbose` |\n| `issue_model` | `opencode/big-pickle` | Model for worker issue generation | `/settings model` |\n| `fallback_model` | `opencode/big-pickle` | Model for fallback path (no worker) | `/settings fallback-model` |\n| `worker:\u003cid\u003e:lastSeen` | — | Heartbeat timestamp (auto-set) | — |\n\n## Design Notes\n\n- **No database access in worker** — all state flows through tRPC\n- **Repo path resolved at claim time** — stored on the job record, never re-queried\n- **Removal is record-only (plus channel deletion)** — `/repo remove` deletes the Discord channel but never touches the filesystem\n- **No env variables for models or modes** — everything stored in `Setting` table\n- **Single job per worker** — poll loop is intentionally single-tenant\n- **Auto-mode** — plans auto-approve after a 10-second cancellable countdown\n- **Quick-mode** — skips the plan agent entirely, goes straight to build\n- **Verbose mode** — defaults to on; set to off to suppress info-level status messages\n- **Suggest-changes loop** — worker polls via `getJobStatus` for `pendingSuggestion`, resumes opencode with `--session --continue`, re-posts the updated plan\n- **Question flow** — opencode plan agent can ask multiple-choice questions; answers are injected via `--session --continue`\n- **Plan viewer** — web-based plan editor at `/plan-viewer/?jobId=N\u0026token=T`; supports view, edit, and approve with an embedded token for auth\n- **Stale-job recovery** — bot sweeps every 60s: releases jobs from workers with heartbeat \u003e 2 min stale\n- **Auto-merge check** — bot polls `gh pr view` for merged PRs every 60s; auto-closes thread on merge\n- **Repo channels** — each registered repo gets a Discord text channel; `/create-report` in a repo channel auto-binds to that repo; `sync-channels` creates missing channels under a \"Repositories\" category\n- **Follow-up jobs** — `/submit` in a thread with a completed job offers to continue the previous session\n- **Review \u0026 Merge** — standalone `/review` command or button from a completed PR job; runs review-fix loop (max 3 iterations) then squash-merges\n- **Update command** — `/update` runs `git pull`, `bun install`, `prisma migrate deploy`, then restarts; worker auto-updates on next heartbeat via git HEAD exchange\n- **Graceful shutdown** — releases all in-flight jobs back to `pending` on SIGTERM/SIGINT\n- **Presence indicator** — bot shows \"Worker online\" / \"Worker offline\" based on heartbeat (checked every 30s)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshcrack%2Fopencode-discord-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsshcrack%2Fopencode-discord-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshcrack%2Fopencode-discord-agent/lists"}