{"id":49182310,"url":"https://github.com/opendray/opendray","last_synced_at":"2026-04-23T02:01:45.515Z","repository":{"id":353043556,"uuid":"1213370365","full_name":"Opendray/opendray","owner":"Opendray","description":"Self-hosted terminal cockpit for piloting AI coding agents (Claude Code, Codex, Gemini, OpenCode, Qwen) from mobile, web, or Telegram. Go backend, Flutter app, plugin architecture.","archived":false,"fork":false,"pushed_at":"2026-04-22T08:38:36.000Z","size":2663,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-22T09:15:49.690Z","etag":null,"topics":["ai-coding","claude-code","cli","codex","developer-tools","flutter","gemini","golang","homelab","llm","mcp","multi-agent","open-source","plugin-architecture","self-hosted","telegram-bot","terminal"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Opendray.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["opendray"]}},"created_at":"2026-04-17T10:00:39.000Z","updated_at":"2026-04-22T08:37:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Opendray/opendray","commit_stats":null,"previous_names":["opendray/opendray"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Opendray/opendray","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Opendray%2Fopendray","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Opendray%2Fopendray/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Opendray%2Fopendray/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Opendray%2Fopendray/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Opendray","download_url":"https://codeload.github.com/Opendray/opendray/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Opendray%2Fopendray/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32162611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"online","status_checked_at":"2026-04-23T02:00:06.710Z","response_time":53,"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":["ai-coding","claude-code","cli","codex","developer-tools","flutter","gemini","golang","homelab","llm","mcp","multi-agent","open-source","plugin-architecture","self-hosted","telegram-bot","terminal"],"created_at":"2026-04-23T02:01:42.403Z","updated_at":"2026-04-23T02:01:45.503Z","avatar_url":"https://github.com/Opendray.png","language":"Go","funding_links":["https://github.com/sponsors/opendray"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"docs/logo.png\" alt=\"OpenDray\" width=\"180\"\u003e\n\n\u003ch1\u003eOpenDray\u003c/h1\u003e\n\n\u003cp\u003e\u003cstrong\u003ePilot AI coding agents from your phone. Self-hosted. Multi-agent. Plugin-driven.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003e\n\u003ca href=\"https://github.com/opendray/opendray/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/opendray/opendray/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/opendray/opendray/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/opendray/opendray?color=blue\" alt=\"Release\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/Opendray/opendray/pkgs/container/opendray\"\u003e\u003cimg src=\"https://img.shields.io/badge/ghcr-opendray%2Fopendray-blue?logo=docker\u0026logoColor=white\" alt=\"Docker image\"\u003e\u003c/a\u003e\n\u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-green\" alt=\"MIT License\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/opendray/opendray/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/opendray/opendray?style=social\" alt=\"Stars\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003ca href=\"#quick-start\"\u003e\u003cb\u003eQuick Start\u003c/b\u003e\u003c/a\u003e \u0026middot;\n\u003ca href=\"#features\"\u003e\u003cb\u003eFeatures\u003c/b\u003e\u003c/a\u003e \u0026middot;\n\u003ca href=\"#architecture\"\u003e\u003cb\u003eArchitecture\u003c/b\u003e\u003c/a\u003e \u0026middot;\n\u003ca href=\"#plugins\"\u003e\u003cb\u003ePlugins\u003c/b\u003e\u003c/a\u003e \u0026middot;\n\u003ca href=\"https://github.com/opendray/opendray/discussions\"\u003e\u003cb\u003eDiscussions\u003c/b\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n\u003cb\u003eEnglish\u003c/b\u003e \u0026middot;\n\u003ca href=\"README.zh-CN.md\"\u003e\u003cb\u003e简体中文\u003c/b\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003c!-- TODO: Replace with actual screenshot/screencast --\u003e\n\u003c!-- \u003cimg src=\"docs/screenshots/demo.svg\" alt=\"OpenDray demo\" width=\"720\"\u003e --\u003e\n\n\u003c/div\u003e\n\n---\n\nStart a Claude Code, Codex, or Gemini session on your server from the train. Close the app. Come back an hour later. The session kept running. Review the diff. Approve it from Telegram.\n\nNo other tool does this.\n\n## Features\n\n**Mobile-first remote control** \u0026mdash; Launch any AI coding agent from your phone, tablet, or browser. The PTY session runs on your server. Close the app, come back later \u0026mdash; it is still there.\n\n**Multi-agent, side-by-side** \u0026mdash; Run Claude Code, Codex, Gemini CLI, OpenCode, and Qwen in parallel sessions. Each gets its own terminal with independent lifecycle, idle detection, and output buffering.\n\n**Plugin architecture** \u0026mdash; Every agent and panel is a `manifest.json`. Add support for any new AI CLI by dropping one into `plugins/`. No code changes. No rebuilds. Restart and it appears in the launcher.\n\n**Telegram bridge** \u0026mdash; Full bidirectional session control over Telegram. List sessions, tail output, link a chat for two-way relay, answer structured prompts via inline keyboards, send control keys \u0026mdash; all without opening the app.\n\n**LLM provider routing** \u0026mdash; Register Ollama, Groq, Gemini free tier, LM Studio, or any OpenAI-compatible endpoint. Route per-session: same OpenCode binary, different model, different cost.\n\n**MCP injection** \u0026mdash; Register MCP servers once. OpenDray generates per-session config files and injects them via CLI args and env vars. No global config files touched.\n\n**Claude multi-account** \u0026mdash; Register multiple Claude OAuth tokens. Pick which account per session. Hot-swap accounts on a running session without losing context (session resumes under the new account).\n\n**Self-hosted, single binary** \u0026mdash; Go backend with the Flutter web build embedded via `go:embed`. One binary + PostgreSQL. No SaaS dependency. Your code stays on your hardware.\n\n## Quick Start\n\nPick your path. **Docker** is the shortest route — one image with every\nagent CLI bundled. **Native binary** is the right pick when you want\nOpenDray on bare metal without Docker in the stack.\n\n### Docker (recommended — all agent CLIs bundled)\n\n```bash\ngit clone https://github.com/Opendray/opendray.git\ncd opendray\ncp .env.docker.example .env \u0026\u0026 $EDITOR .env   # set DB_PASSWORD at minimum\n./scripts/opendray-docker up                  # starts opendray + postgres\n./scripts/opendray-docker login claude        # one-time OAuth per agent\n# open http://localhost:8640\n```\n\nThe `*-full` image ships Claude Code, Codex, Gemini CLI, and OpenCode\non PATH, so every builtin plugin works with zero host setup. Published\nas a multi-arch manifest (`linux/amd64` + `linux/arm64`) — same tag on\nx86 servers, Apple Silicon, Raspberry Pi 4+, and AWS Graviton.\n\nAlready have Docker running? Skip the clone and pull directly:\n\n```bash\ndocker pull ghcr.io/Opendray/opendray:latest-full\n```\n\nThe `opendray-docker` wrapper exposes `up / down / logs / doctor / login\n/ update / backup` verbs over the compose stack. Full reference:\n[docs/DOCKER.md](docs/DOCKER.md).\n\n### Native binary (macOS / Linux)\n\nOpenDray also ships as a single self-contained binary. Install, run the\nterminal wizard, start the server. **Setup is terminal-only** — there\nis no web-based first-run wizard, so the install flow works identically\nover SSH, in a VPS, or on your laptop.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Opendray/opendray/main/install.sh | sh\n```\n\nThe installer:\n1. detects your OS (`darwin` / `linux`) and architecture (`amd64` / `arm64`)\n2. downloads the matching binary from [Releases](https://github.com/Opendray/opendray/releases)\n3. verifies its SHA256 against the signed `SHA256SUMS` file\n4. installs it to `~/.local/bin/opendray`\n5. hands control to `opendray setup` — an interactive wizard in the same terminal\n\nOverride with env vars: `OPENDRAY_VERSION=v0.5.0`, `OPENDRAY_INSTALL_DIR=/usr/local/bin`, `OPENDRAY_NO_SETUP=1` (skip auto-wizard).\n\n\u003e **Windows:** not yet supported. The core feature (spawning agent CLIs\n\u003e in a pseudo-terminal) requires UNIX PTY via `creack/pty`; the Windows\n\u003e ConPTY equivalent is on the roadmap.\n\n### What the wizard asks\n\n```\n1 / 4   DATABASE        bundled PostgreSQL (managed by OpenDray)\n                        or external (bring your own PG 14+)\n2 / 4   LISTEN ADDRESS  loopback (127.0.0.1:8640, local only)\n                        or all interfaces (0.0.0.0:8640, LAN-exposed)\n                        or custom host:port\n3 / 4   ADMIN ACCOUNT   username + password (min 8 chars)\n4 / 4   JWT SECRET      auto-generate or paste your own\n```\n\nConfig persists to `~/.opendray/config.toml`. Re-running `opendray setup`\nresumes with existing values as defaults, so you can rotate any single field\nwithout re-entering the rest.\n\n### Scripted install (CI / cloud-init)\n\n```bash\nopendray setup --yes \\\n    --db=bundled \\\n    --listen=loopback \\\n    --admin-user=admin \\\n    --admin-password-file=/run/secrets/admin_pw\n```\n\nAll prompts have matching flags: `--db-host`, `--db-port`, `--db-user`,\n`--db-name`, `--db-password-file`, `--db-sslmode`, `--jwt-secret-file`.\nSee `opendray setup --help`.\n\n### Manual download\n\nGrab a binary from the [Releases page](https://github.com/Opendray/opendray/releases):\n- `opendray-darwin-arm64` — Apple Silicon Mac\n- `opendray-darwin-amd64` — Intel Mac\n- `opendray-linux-amd64` / `opendray-linux-arm64`\n\n```bash\nchmod +x opendray-darwin-arm64\n./opendray-darwin-arm64 setup\n./opendray-darwin-arm64\n```\n\n### Build from source\n\nPrerequisites: Go 1.25+ and Flutter (stable channel). No `make`\nrequired — the two direct commands are all you need.\n\n```bash\ngit clone https://github.com/Opendray/opendray.git\ncd opendray\n\n# 1. Build the Flutter web bundle (gets embedded into the Go binary)\ncd app \u0026\u0026 flutter pub get \u0026\u0026 flutter build web --release \u0026\u0026 cd ..\n\n# 2. Build the Go binary\ngo build -o bin/opendray ./cmd/opendray\n\n./bin/opendray setup\n./bin/opendray\n```\n\nIf you already have GNU `make` installed, `make build` is a one-liner\nshortcut for the two commands above.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eDev mode (hot-reload)\u003c/b\u003e\u003c/summary\u003e\n\n```bash\ncp .env.example .env     # point at your own PostgreSQL\nmake dev                 # Go backend + Flutter web client\n```\n\nWith `.env` set the wizard is skipped — env vars win over the config file,\nwhich preserves existing LXC/Docker deployments.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eBring your own PostgreSQL\u003c/b\u003e\u003c/summary\u003e\n\n```sql\nCREATE DATABASE opendray;\nCREATE USER opendray WITH PASSWORD 'changeme';\nGRANT ALL PRIVILEGES ON DATABASE opendray TO opendray;\n```\n\nPick `external` in the wizard, or set `DB_HOST` / `DB_USER` / `DB_PASSWORD` /\n`DB_NAME` env vars — either path triggers automatic migration (schema is\ncreated in your database on first connection).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eBundled PostgreSQL and root\u003c/b\u003e\u003c/summary\u003e\n\nThe `bundled` database mode refuses to run as `root` — upstream PostgreSQL's\n`initdb` hard-fails on `uid == 0`. Create an unprivileged user first:\n\n```bash\nuseradd -r -m -s /bin/bash -d /home/opendray opendray\nsu - opendray\nopendray setup\n```\n\nOr pick `external` and connect to an existing PG instance.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eProduction binary\u003c/b\u003e\u003c/summary\u003e\n\n```bash\nmake release-linux                    # cross-compile linux/amd64 with embedded web\n./bin/opendray-linux-amd64            # single binary, migrations run on startup\n```\n\n`JWT_SECRET` is required when binding to a non-loopback address. The wizard auto-generates one; for env-var deploys, set it yourself.\n\n\u003c/details\u003e\n\n## Run as a background service\n\nThe default `opendray` invocation runs in the foreground — great for\ntesting, not so great for \"always-on server tied to a specific user\nsession\". Install the service wrapper so it:\n\n- starts on boot\n- restarts on crash\n- logs to a sensible place (journald on Linux, `/var/log/opendray/` on macOS)\n- runs as your non-root setup user (bundled PG won't start as uid 0)\n\n```bash\nsudo opendray service install\n```\n\nAuto-detects the target user from `$SUDO_USER` (the account you ran\n`sudo` from). Override with `--user` if that's wrong:\n\n```bash\nsudo opendray service install --user opendray\n```\n\nOther lifecycle commands:\n\n```bash\nopendray service status      # current state\nopendray service logs        # tail (journalctl -fu on Linux, tail -f on mac)\nsudo opendray service start  # / stop / restart\nsudo opendray service uninstall\nopendray service help        # full reference\n```\n\n### What it writes\n\n| Platform | File | What it does |\n|---|---|---|\n| Linux | `/etc/systemd/system/opendray.service` | systemd unit, `Restart=on-failure`, journald output, `ProtectSystem=full` |\n| macOS | `/Library/LaunchDaemons/com.opendray.opendray.plist` | launchd daemon, `KeepAlive=SuccessfulExit:false`, logs to `/var/log/opendray/` |\n\nBoth run the binary as the `--user` account (never root) and inherit\n`HOME=$user` so the existing config under `~/.opendray/` is loaded as-is.\n\n### Preview without writing\n\n```bash\nopendray service install --user linivek --dry-run\n```\n\nprints the unit / plist that would be written. No system changes. Good\nfor reviewing before committing.\n\n## Uninstall\n\nMirrors the install flow. Two paths depending on whether your `opendray`\nbinary can still run.\n\n### Built-in command (preferred)\n\n```bash\nopendray uninstall               # interactive: show plan, confirm, remove\nopendray uninstall --yes         # no prompt\nopendray uninstall --dry-run     # preview only\nopendray uninstall --keep-data   # binary + config gone, ~/.opendray/ stays\n```\n\nOutput:\n1. stops any running OpenDray server + bundled PostgreSQL\n2. removes `~/.opendray/` (PG cluster, plugins, cache, marketplace)\n3. removes `~/.config/opendray/config.toml` if present\n4. removes the binary itself (self-delete)\n\n### One-line nuclear option (binary can't run)\n\nWhen the binary is corrupt or the config is so broken the wizard won't\nstart, use the shell script instead. It knows nothing about config; it\njust `rm -rf`s the well-known paths.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Opendray/opendray/main/uninstall.sh | sh\n```\n\nEnvironment overrides:\n- `OPENDRAY_YES=1` — skip confirmation\n- `OPENDRAY_DRY_RUN=1` — preview only\n- `OPENDRAY_INSTALL_DIR` — non-default binary location\n\n### External PostgreSQL\n\nOpenDray **never drops tables from an external database you provided** —\ntable names (`sessions`, `plugins`, `admin_auth`, …) are generic enough\nto collide with other applications sharing the DB, and automated drops\nare unrecoverable.\n\nInstead, `opendray uninstall` writes a `drop_opendray_schema.sql` file\nto your current directory with wrapped `DROP TABLE IF EXISTS … CASCADE`\nstatements. Review it, then apply manually:\n\n```bash\npsql -h \u003chost\u003e -U \u003cuser\u003e -d \u003cdb\u003e -f drop_opendray_schema.sql\n```\n\nThe nuclear scripts skip this helper; if you went nuclear, you're\nexpected to know which tables to drop.\n\n### Manual removal (last resort)\n\nIf both paths above fail, these are the locations to nuke by hand:\n\n| Platform | Path |\n|---|---|\n| macOS / Linux | `~/.local/bin/opendray` |\n| macOS / Linux | `~/.opendray/` |\n| macOS / Linux | `~/.config/opendray/` (XDG fallback) |\n\n## Architecture\n\n```mermaid\ngraph TB\n    subgraph Client[\"Flutter App (iOS / Android / Web)\"]\n        Sessions\n        Browser[\"Browser (Files, DB, Logs, Git, MCP, ...)\"]\n        Settings\n    end\n\n    subgraph Backend[\"Go Backend — single binary\"]\n        Gateway[\"gateway/ — HTTP + WebSocket routing\"]\n        Hub[\"kernel/hub — multi-session PTY manager\"]\n        Store[\"kernel/store — PostgreSQL + migrations\"]\n        Plugins[\"plugin/ — manifest scanner + runtime\"]\n        MCP[\"gateway/mcp — MCP config injector\"]\n        TG[\"gateway/telegram — Telegram bridge\"]\n        LLM[\"gateway/llm_proxy — provider router\"]\n    end\n\n    subgraph Agents[\"Agent Processes (spawned per session)\"]\n        Claude[\"Claude Code\"]\n        Codex[\"Codex CLI\"]\n        Gemini[\"Gemini CLI\"]\n        OC[\"OpenCode\"]\n        Qwen[\"Qwen Code\"]\n    end\n\n    subgraph Providers[\"LLM Providers\"]\n        Ollama\n        Groq\n        GeminiFree[\"Gemini Free\"]\n        OpenRouter\n        LMStudio[\"LM Studio\"]\n    end\n\n    Client -- \"REST + WebSocket\" --\u003e Gateway\n    Telegram -- \"Bot API\" --\u003e TG\n    Gateway --\u003e Hub\n    Hub --\u003e Agents\n    MCP -. \"inject per-session config\" .-\u003e Agents\n    OC -- \"/v1/chat/completions\" --\u003e Providers\n    Hub --\u003e Store\n    Gateway --\u003e Plugins\n```\n\n### Source Layout\n\n```\ncmd/opendray/       Entry point — setup, service, uninstall, plugin, version subcommands\nkernel/\n  terminal/         PTY engine: spawn, 4 MB ring buffer, idle detection\n  hub/              Multi-session lifecycle: create, attach, resume, stop (max 20)\n  store/            PostgreSQL: connection pool, 18 migrations, queries\n  auth/             JWT issuing and middleware (HS256, 7-day TTL)\n  pg/               Bundled PostgreSQL launcher (embedded PG 15.4 child process)\n  config/           config.toml parser + env overlay\ngateway/            HTTP + WebSocket handlers\n  telegram/         Telegram bot: commands, links, notifications, inline keyboards\n  mcp/              MCP server registry, per-session config renderer + cleanup\n  llm_proxy/        Anthropic-to-OpenAI request/response translation\n  files/            Sandboxed file browser (allowed-roots, symlink resolution)\n  pg/               Read-only PostgreSQL browser (DDL/DML blocked, row/time caps)\n  forge/            Git-forge clients (Gitea, GitHub, GitLab) for Obsidian reader\n  git/              Per-repo status, per-session baseline diffs, branch listing\n  logs/             Tail-follow with rotation detection, regex grep, extension filter\n  tasks/            Makefile / npm / shell discovery, concurrent runner with timeouts\n  docs/             Markdown reader (used by the Obsidian plugin)\nplugin/             Manifest scanner, runtime, hook bus, marketplace, consents\nplugins/\n  builtin/          Built-in plugins (agents + panels, embedded in binary)\napp/                Flutter client (iOS, Android, Web) — 19 feature modules\n```\n\n## Plugins\n\nEvery agent and panel is a plugin. OpenDray ships with 17.\n\n### Agents\n\n| Agent | Icon | Models | Key Capabilities |\n|---|---|---|---|\n| **Claude Code** | 🟣 | Sonnet, Opus, Haiku | Session resume (`--resume`), MCP injection, image input, multi-account OAuth, bypass-permissions mode |\n| **Codex CLI** | 🤖 | o4-mini, o3, GPT-4.1, GPT-4.1-mini | Approval modes (suggest / auto-edit / full-auto), MCP injection |\n| **Gemini CLI** | ✨ | Gemini 2.5 Pro, Gemini 2.5 Flash | Sandbox mode, yolo mode, multimodal input |\n| **OpenCode** | 🤖 | Dynamic (via LLM Endpoints) | Provider-agnostic routing to any OpenAI-compatible endpoint, session resume, MCP injection |\n| **Qwen Code** | 🐉 | Qwen3-Coder Plus/Flash/480B | DashScope, ModelScope, OpenRouter, dynamic model detection, MCP injection |\n| **Terminal** | ⬛ | \u0026mdash; | System login shell (zsh/bash/sh), no AI |\n\n### Panels\n\n| Panel | Category | What it does |\n|---|---|---|\n| **File Browser** | files | Sandboxed directory listing + file viewing with syntax highlighting, binary detection, size caps |\n| **PostgreSQL Browser** | database | Read-only schema introspection (databases, schemas, tables, columns) + filtered SELECT execution, query history, 8 SSL modes |\n| **Log Viewer** | logs | Tail-follow with backlog, rotation detection, regex grep, extension filtering |\n| **Task Runner** | tools | Discover Makefile targets, package.json scripts, shell scripts; concurrent execution with timeouts and live output |\n| **Git Viewer** | tools | Per-repo status, per-session baseline (shows only changes made during the session), unified diff, commit log, branch listing |\n| **Git Forge** | tools | Gitea / GitHub / GitLab integration — browse repos, clone, inspect PRs/issues |\n| **Telegram Bridge** | messaging | Bot token setup, link status, test messages, command reference |\n| **MCP Servers** | mcp | CRUD for stdio / SSE / HTTP MCP servers, per-agent filtering, enable/disable toggle |\n| **Obsidian Reader** | docs | Browse Obsidian vaults from Git repos (Gitea, GitHub, GitLab), branch selection, path filtering |\n| **Web Browser** | preview | Full in-app browser with multi-tab URLs, forward/back navigation, and port-on-host shortcuts |\n| **Simulator Preview** | simulator | Real-time WebSocket stream of iOS Simulator or Android Emulator, adaptive FPS (8 active / 1 idle), touch/swipe/key input forwarding |\n\n### Writing a Plugin\n\nAdd a new agent in under 5 minutes:\n\n```\nplugins/agents/my-agent/manifest.json\n```\n\n```json\n{\n  \"name\": \"my-agent\",\n  \"kind\": \"agent\",\n  \"icon\": \"🤖\",\n  \"cliSpec\": {\n    \"command\": \"my-agent-cli\",\n    \"defaultArgs\": [\"--no-color\"],\n    \"installDetect\": \"which my-agent-cli\"\n  },\n  \"capabilities\": {\n    \"supportsResume\": false,\n    \"supportsStream\": true,\n    \"supportsMcp\": true\n  }\n}\n```\n\nRestart OpenDray. The agent appears in the session launcher. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full manifest reference.\n\n## Telegram Bridge\n\nFull bidirectional control over Telegram \u0026mdash; no app required:\n\n| Command | Description |\n|---|---|\n| `/status` | List running sessions with IDs |\n| `/tail \u003cid\u003e [n]` | Last N lines of output (JSONL-aware for Claude, raw buffer for others) |\n| `/screen \u003cid\u003e` | Current screen snapshot (rich HTML for Claude, `\u003cpre\u003e` for others) |\n| `/link \u003cid\u003e` | Bind this chat to a session (two-way relay, replaces prior binding) |\n| `/unlink` | Remove the binding |\n| `/links` | List all active chat-session bindings |\n| `/send \u003cid\u003e \u003ctext\u003e` | One-shot send without linking |\n| `/stop \u003cid\u003e` | Terminate a session |\n| `/whoami` | Show your Telegram chat ID |\n| `/cc` `/cd` `/tab` `/enter` | Send control keys to linked session |\n| `/yes` `/no` | Quick-answer routing for prompts |\n\n**Linked chat behavior:** Plain text goes to the agent as terminal input. Agent output streams back in 2-second batches. Reply to any idle/exit notification to route directly to that session.\n\n**Multi-select prompts** (e.g., Claude Code permission dialogs, tool approval lists) render as inline Telegram keyboards with checkboxes and a submit button.\n\n## LLM Endpoint Routing\n\nRegister any OpenAI-compatible endpoint under **Settings → LLM Endpoints**\n(previously the `llm-providers` panel plugin — now a platform capability\nshared by every agent, not owned by a single plugin):\n\n- **Local**: Ollama, LM Studio, llama.cpp, vLLM\n- **Cloud**: Groq, Gemini free tier, OpenRouter, Together AI, Fireworks\n- **Custom**: Any server implementing `/v1/chat/completions`\n\nWhen creating a session with OpenCode, pick a provider and model. OpenDray generates a per-session config, sets `XDG_CONFIG_HOME`, and rewrites the `--model` arg. The same CLI binary, different brain, different cost.\n\nOther agents receive `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `OPENAI_MODEL` env vars for future OpenAI-native CLI support.\n\n## MCP Server Management\n\nRegister MCP servers once in OpenDray. When an agent session starts, OpenDray generates a temporary per-session config file and injects it via CLI args and env vars \u0026mdash; no editing `~/.claude.json` or `~/.codex/config.toml`. Temp files are cleaned up when the session exits.\n\nSupports `stdio`, `sse`, and `http` transports. Scope servers to specific agents or apply globally.\n\n## Session Lifecycle\n\n| Phase | What happens |\n|---|---|\n| **Create** | REST API accepts agent type, working directory, model, extra args, env overrides, Claude account, LLM provider |\n| **Start** | Hub resolves CLI command from plugin registry, builds args + env, injects MCP config, spawns PTY |\n| **Running** | WebSocket streams terminal I/O, 4 MB ring buffer captures output, idle detector fires after threshold |\n| **Resume** | Claude and OpenCode support `--resume` with stored session IDs; other agents spawn fresh |\n| **Account swap** | Claude sessions can hot-swap OAuth accounts without losing context (stop \u0026rarr; rebind \u0026rarr; resume) |\n| **Stop** | Graceful shutdown: SIGHUP \u0026rarr; SIGTERM \u0026rarr; SIGKILL (2s escalation), temp files cleaned |\n| **Recovery** | `AUTO_RESUME=true`: re-attach orphaned PTYs after an OpenDray crash if the process and DB row still exist |\n\nMax 20 concurrent sessions. Each session has independent idle detection, exit hooks, and Telegram notification routing.\n\n## Security\n\n| Control | Default |\n|---|---|\n| Bind address | `127.0.0.1:8640` (loopback only) |\n| Authentication | JWT required on non-loopback. Server refuses to start without `JWT_SECRET`. |\n| Rate limiting | Token-bucket per-IP on session mutations (10/min), reads (60/min) |\n| Body size | 1 MB cap on POST/PUT/PATCH |\n| File browser | Sandboxed to configured allow-list, symlinks resolved before prefix check |\n| Database browser | Read-only transactions, DDL/DML regex gate, keyword blacklist, row caps (500), query timeout (30s) |\n| LLM API keys | Stored as env-var names, never as values in the database |\n| MCP configs | Per-session temp files, cleaned on exit, never written to global config |\n\nThe PTY API is root-equivalent on the host. Always run behind a reverse proxy with TLS in production.\n\nSee [SECURITY.md](SECURITY.md) for the full threat model and deployment checklist.\n\n## Configuration\n\nAll configuration via environment variables. See [`.env.example`](.env.example) for the complete reference.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eKey variables\u003c/b\u003e\u003c/summary\u003e\n\n| Variable | Default | Description |\n|---|---|---|\n| `LISTEN_ADDR` | `127.0.0.1:8640` | Bind address |\n| `DB_HOST` | *(required)* | PostgreSQL host |\n| `DB_PASSWORD` | *(required)* | PostgreSQL password |\n| `DB_NAME` | `opendray` | Database name |\n| `JWT_SECRET` | *(empty = dev)* | Required for non-loopback bind |\n| `PLUGIN_DIR` | `./plugins` | Plugin manifest directory |\n| `OPENDRAY_TELEGRAM_BOT_TOKEN` | *(empty)* | Telegram bot token from @BotFather |\n| `AUTO_RESUME` | `false` | Re-attach orphaned PTYs on startup |\n| `IDLE_THRESHOLD_SECONDS` | `8` | Seconds of silence before idle event |\n\n\u003c/details\u003e\n\n## Tech Stack\n\n| Layer | Technology |\n|---|---|\n| Backend | Go 1.25+, chi, gorilla/websocket, creack/pty, pgx/v5 |\n| Frontend | Flutter 3.41+ (Dart 3), xterm.js via WebView, go_router, provider |\n| Database | PostgreSQL 14+ (18 auto-applied migrations, max 20 connections) |\n| Auth | JWT (HS256, 7-day TTL) + optional Cloudflare Access service-token support |\n| Packaging | Single binary with Flutter web build embedded via `go:embed` |\n| CI | GitHub Actions (Go vet + test + build, Flutter analyze + build) |\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, plugin authoring, and PR process.\n\nThe fastest way to contribute: write a `manifest.json` for your favorite AI coding CLI and submit a PR.\n\n## License\n\nMIT \u0026mdash; see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopendray%2Fopendray","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopendray%2Fopendray","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopendray%2Fopendray/lists"}