From b6b66b8e7d752a2493d737b60a7ca0a605174226 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:17:46 +0000 Subject: [PATCH 1/6] fix: restore docker build policy and mcp validation --- .dockerignore | 1 + Dockerfile | 2 +- tests/routing.test.mjs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index ec77c70..0d10235 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,5 +8,6 @@ !orchestra.jsonc !package.json !pnpm-lock.yaml +!pnpm-workspace.yaml !scripts/ !scripts/build-config.mjs diff --git a/Dockerfile b/Dockerfile index 22f6fcc..bc4848d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ RUN case "$TARGETARCH" in amd64) arch=x64 ;; arm64) arch=arm64 ;; *) exit 1 ;; e FROM tools AS config WORKDIR /build -COPY package.json pnpm-lock.yaml opencode.jsonc ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml opencode.jsonc ./ COPY scripts/build-config.mjs ./scripts/build-config.mjs RUN pnpm install --frozen-lockfile --ignore-scripts --no-optional \ && node scripts/build-config.mjs /build/runtime.json diff --git a/tests/routing.test.mjs b/tests/routing.test.mjs index 3d3441d..ab85e61 100644 --- a/tests/routing.test.mjs +++ b/tests/routing.test.mjs @@ -39,7 +39,7 @@ test("image-owned skills are discoverable", async () => { }); test("existing providers, MCP integrations and quota are preserved", () => { - assert.deepEqual(Object.keys(config.mcp.servers).sort(), ["context7", "grafana", "karakeep", "lunchmoney", "notion"]); + assert.deepEqual(Object.keys(config.mcp.servers).sort(), ["context7", "grafana", "karakeep", "lunchmoney", "notion", "robinhood"]); assert.ok(config.plugins.includes("@slkiser/opencode-quota@5.0.2")); assert.ok(!config.permissions?.some(rule => rule.action === "*" && rule.effect === "allow")); assert.deepEqual(config.agents.build.permissions, [{ action: "*", resource: "*", effect: "allow" }]); From cab89ed74dd4d457899101862c61e5075571abcb Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:25:11 +0000 Subject: [PATCH 2/6] fix: deny robinhood tools for restricted agents --- opencode.jsonc | 2 ++ scripts/test-router-integration.mjs | 4 ++-- tests/routing.test.mjs | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/opencode.jsonc b/opencode.jsonc index 8853893..2a1b203 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -21,6 +21,7 @@ { "action": "lunchmoney_*", "resource": "*", "effect": "deny" }, { "action": "karakeep_*", "resource": "*", "effect": "deny" }, { "action": "notion_*", "resource": "*", "effect": "deny" }, + { "action": "robinhood_*", "resource": "*", "effect": "deny" }, { "action": "subagent", "resource": "*", "effect": "deny" }, { "action": "skill", "resource": "*", "effect": "allow" }, { "action": "execute", "resource": "*", "effect": "allow" }, @@ -35,6 +36,7 @@ { "action": "lunchmoney_*", "resource": "*", "effect": "deny" }, { "action": "karakeep_*", "resource": "*", "effect": "deny" }, { "action": "notion_*", "resource": "*", "effect": "deny" }, + { "action": "robinhood_*", "resource": "*", "effect": "deny" }, { "action": "subagent", "resource": "*", "effect": "deny" }, { "action": "skill", "resource": "*", "effect": "allow" }, { "action": "execute", "resource": "*", "effect": "allow" }, diff --git a/scripts/test-router-integration.mjs b/scripts/test-router-integration.mjs index 8814062..76c7a8a 100644 --- a/scripts/test-router-integration.mjs +++ b/scripts/test-router-integration.mjs @@ -93,13 +93,13 @@ try { function permission(agent, action) { return agent.permissions.filter(rule => new RegExp(`^${rule.action.split("*").map(RegExp.escape).join(".*")}$`).test(action)).at(-1)?.effect; } - for (const action of ["execute", "grafana_list_datasources", "lunchmoney_get_user", "karakeep_search-bookmarks", "notion_notion-search", "context7_query-docs"]) + for (const action of ["execute", "grafana_list_datasources", "lunchmoney_get_user", "karakeep_search-bookmarks", "notion_notion-search", "robinhood_test_tool", "context7_query-docs"]) assert.equal(permission(lead, action), "allow", `lead can use ${action}`); const general = agents.find(a => a.id === "general"); const explore = agents.find(a => a.id === "explore"); for (const agent of [general, explore]) { assert.ok(agent); - for (const action of ["grafana_list_datasources", "lunchmoney_get_user", "karakeep_search-bookmarks", "notion_notion-search", "subagent"]) + for (const action of ["grafana_list_datasources", "lunchmoney_get_user", "karakeep_search-bookmarks", "notion_notion-search", "robinhood_test_tool", "subagent"]) assert.equal(permission(agent, action), "deny", `${agent.id} cannot use ${action}`); for (const action of ["read", "glob", "grep", "webfetch", "websearch", "skill", "execute", "context7_query-docs"]) assert.equal(permission(agent, action), "allow", `${agent.id} can use ${action}`); diff --git a/tests/routing.test.mjs b/tests/routing.test.mjs index ab85e61..f08b8ff 100644 --- a/tests/routing.test.mjs +++ b/tests/routing.test.mjs @@ -43,6 +43,8 @@ test("existing providers, MCP integrations and quota are preserved", () => { assert.ok(config.plugins.includes("@slkiser/opencode-quota@5.0.2")); assert.ok(!config.permissions?.some(rule => rule.action === "*" && rule.effect === "allow")); assert.deepEqual(config.agents.build.permissions, [{ action: "*", resource: "*", effect: "allow" }]); + for (const name of ["general", "explore"]) + assert.ok(config.agents[name].permissions.some(rule => rule.action === "robinhood_*" && rule.resource === "*" && rule.effect === "deny"), `${name} denies Robinhood tools`); }); test("runtime conversion preserves JSONC overrides and rejects malformed input", async () => { From b090b51afdf43fa4bbb82e9e27e6fd07441c41f3 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:25:11 +0000 Subject: [PATCH 3/6] fix: enforce pnpm policy for global cli installs --- Dockerfile | 3 ++- pnpm-workspace.yaml | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bc4848d..cbc3adf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,13 +31,14 @@ ARG PNPM_VERSION=12.11.2 ARG TARGETARCH ENV PNPM_HOME=/opt/pnpm ENV PATH=${PNPM_HOME}/bin:${PATH} +COPY pnpm-workspace.yaml /opt/pnpm-config/pnpm/config.yaml RUN case "$TARGETARCH" in amd64) arch=x64 ;; arm64) arch=arm64 ;; *) exit 1 ;; esac \ && mkdir -p /opt/pnpm-cli "$PNPM_HOME" \ && curl --fail --silent --show-error --location \ "https://registry.npmjs.org/@pnpm/exe.linux-${arch}/-/exe.linux-${arch}-${PNPM_VERSION}.tgz" \ | tar -xz --strip-components=1 -C /opt/pnpm-cli \ && ln -s /opt/pnpm-cli/pnpm /usr/local/bin/pnpm \ - && pnpm --allow-build=@opencode/cli add --global \ + && XDG_CONFIG_HOME=/opt/pnpm-config pnpm --allow-build=@opencode/cli add --global \ "@opencode/cli@${OPENCODE_VERSION}" "typescript@${TYPESCRIPT_VERSION}" \ && opencode --version diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2f78782..5fdb217 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,19 @@ minimumReleaseAgeStrict: true minimumReleaseAgeExclude: - '@opencode/ai@2.0.27' + - '@opencode/cli-darwin-arm64@2.0.27' + - '@opencode/cli-darwin-x64-baseline@2.0.27' + - '@opencode/cli-darwin-x64@2.0.27' + - '@opencode/cli-linux-arm64-musl@2.0.27' + - '@opencode/cli-linux-arm64@2.0.27' + - '@opencode/cli-linux-x64-baseline-musl@2.0.27' + - '@opencode/cli-linux-x64-baseline@2.0.27' + - '@opencode/cli-linux-x64-musl@2.0.27' + - '@opencode/cli-linux-x64@2.0.27' + - '@opencode/cli-windows-arm64@2.0.27' + - '@opencode/cli-windows-x64-baseline@2.0.27' + - '@opencode/cli-windows-x64@2.0.27' + - '@opencode/cli@2.0.27' - '@opencode/client@2.0.27' - '@opencode/plugin@2.0.27' - '@opencode/protocol@2.0.27' From 83317f1b336b6c5f0e6d40bf11019a34480713ed Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:30:34 +0000 Subject: [PATCH 4/6] docs: streamline server setup and operation guide --- README.md | 182 +++++++++++++----------------------------------------- 1 file changed, 42 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 1eb4a08..590be50 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,90 @@ # OpenCode Server -Self-hosted [OpenCode V2](https://opencode.ai/v2/docs) web UI and API with Gemini, GitHub -Copilot, and WakaTime. Image: `ghcr.io/icco/opencode-server:main` (amd64/arm64). +Self-hosted [OpenCode V2](https://opencode.ai/v2/docs/) web UI and API. The image `ghcr.io/icco/opencode-server:main` supports amd64 and arm64. -## Quick start +## Run + +From this checkout, with Docker Compose and OpenSSL installed: ```sh +mkdir -p "$HOME/.ssh" export OPENCODE_PASSWORD="$(openssl rand -hex 32)" docker compose up -d ``` -For this local run, `OPENCODE_PASSWORD` is a random password of at least 32 -characters. Open and log in as -`opencode`. Keep repositories under `/data/workspace`. - -The same `OPENCODE_PASSWORD` authenticates commands run with `docker compose exec`. -The icco.me deployment loads it and integration tokens from Google Secret Manager -through its existing updater; no production `.env` file is used. - -The `/data` volume preserves workspaces, sessions, credentials, and caches. -Back it up. Host bind mounts must be writable by UID/GID 1000. +Open and sign in as `opencode` with that password. Keep the password in your deployment's secret store and export it for later Compose commands; startup requires at least 32 characters. -## GitHub CLI and Git access +The `/data` volume preserves repositories, sessions, credentials, and caches. Keep repositories in `/data/workspace` and back up the volume. Bind mounts must be writable by UID/GID 1000. Compose also mounts the host's `~/.ssh` read-only; keys must be readable by UID 1000, and `known_hosts` must be maintained on the host. -Authenticate `gh` once for HTTPS Git access (separate from Copilot login): +To update the image or apply changed environment variables: ```sh -docker compose exec opencode gh auth login --hostname github.com --git-protocol https --web -docker compose exec opencode gh auth status +docker compose pull +docker compose up -d --force-recreate opencode ``` -Credentials persist in `/data/.config/gh`. For automation, inject `GH_TOKEN` into -the container environment; it overrides stored logins. - -SSH remotes use the host's `${HOME}/.ssh`, mounted read-only at `/data/.ssh`. -Create it before startup, make keys readable by UID 1000 with SSH-compatible -permissions, and add required hosts to `known_hosts` on the host. - -## Connect providers +## Connect accounts -Connect both providers through the Web UI or the running server: +Connect providers in OpenCode or run: ```sh docker compose exec opencode opencode auth login github-copilot --server http://127.0.0.1:4096 docker compose exec opencode opencode auth login google --server http://127.0.0.1:4096 ``` -- **Copilot:** choose GitHub Copilot and complete device login. Requires a subscription. -- **Gemini:** choose Google → **Manually enter API Key**. Organization-backed Code - Assist can use OAuth: open the login URL and paste the full localhost redirect - URL into the prompt. Set `OPENCODE_GEMINI_PROJECT_ID` in the container environment. - Consumer OAuth is discontinued; see the [Gemini plugin docs](https://github.com/jenslys/opencode-gemini-auth). +- **GitHub Copilot:** complete device login with a subscribed account. +- **Gemini:** choose the API-key method or set `GOOGLE_GENERATIVE_AI_API_KEY`. Organization-backed Code Assist OAuth also needs `OPENCODE_GEMINI_PROJECT_ID`; see the [Gemini plugin's account requirements](https://github.com/jenslys/opencode-gemini-auth). +- **Git access:** set `GH_TOKEN` or run `docker compose exec opencode gh auth login --hostname github.com --git-protocol https --web`. This is separate from Copilot login; stored credentials persist in `/data/.config/gh`. +- **WakaTime:** put your API key in `/data/.wakatime.cfg`, owned by `1000:1000` with mode `600`, using the [plugin's configuration format](https://github.com/angristan/opencode-wakatime#prerequisites). -For automated hosting, inject `GOOGLE_GENERATIVE_AI_API_KEY` into the container environment. +## MCP integrations -For [WakaTime](https://github.com/angristan/opencode-wakatime), create -`/data/.wakatime.cfg` owned by `1000:1000`, mode `600`: +Set the variables below before creating the container. For remote servers, open `/mcps`, select the server, and sign in when prompted; OAuth credentials persist in `/data`. -```ini -[settings] -api_key = -``` +| Integration | Setup | +| --- | --- | +| [Context7](https://mcp.context7.com/mcp) | Remote library documentation. | +| [Grafana](https://github.com/grafana/mcp-grafana) | `GRAFANA_URL` and `GRAFANA_SERVICE_ACCOUNT_TOKEN`; bundled tools are read-only. | +| [Karakeep](https://docs.karakeep.app/integrations/mcp) | `KARAKEEP_API_KEY` and `KARAKEEP_API_ADDR` (defaults to `https://hoard.natwelch.com`); includes write tools. | +| [Lunch Money](https://github.com/akutishevsky/lunchmoney-mcp) | `LUNCHMONEY_API_TOKEN` from [developer settings](https://my.lunchmoney.app/developers); required to start its MCP server. | +| [Notion](https://developers.notion.com/guides/mcp/overview) | Remote server at `https://mcp.notion.com/mcp`. | +| Robinhood | Remote trading server at `https://agent.robinhood.com/mcp/trading`. | -## MCP integrations - -- **Lunch Money:** inject `LUNCHMONEY_API_TOKEN` using a token from - [developer settings](https://my.lunchmoney.app/developers). The pinned - [Lunch Money MCP](https://github.com/akutishevsky/lunchmoney-mcp) v3.0.0 provides - finance queries and updates through Lunch Money's v2 API. Recreate the container - after setting the token. -- **Grafana:** the image bundles [Grafana MCP](https://github.com/grafana/mcp-grafana) - v2.0.2 for read-only dashboards, alerts, annotations, Loki logs (LogQL), and - Prometheus metrics (PromQL). Set `GRAFANA_URL` and - `GRAFANA_SERVICE_ACCOUNT_TOKEN` in the container environment, then recreate it. Use a - Grafana service account with the Viewer role and access to the required - datasources. Include Grafana's subpath in the URL when applicable, and use an - endpoint that accepts service-account authentication without a browser-login - proxy. Loki and Prometheus use Grafana's existing datasource configuration. -- **Context7:** current library documentation via `https://mcp.context7.com/mcp`. - If OpenCode reports authentication is needed, open `/mcps`, select Context7, - and sign in. OAuth credentials persist in `/data`. -- **Karakeep:** the official [MCP](https://docs.karakeep.app/integrations/mcp) v0.33.1 - uses `KARAKEEP_API_ADDR` (Compose defaults to `https://hoard.natwelch.com`) and - `KARAKEEP_API_KEY` from Karakeep's **Settings → API Keys**. Inject the key and - recreate the container. It supports search/read and **create, update, and delete** - operations. Verify with a small bookmark search; connection status alone does - not validate the key. -- **Notion:** Notion's hosted [MCP](https://developers.notion.com/guides/mcp/overview) at `https://mcp.notion.com/mcp`. Open `/mcps`, select Notion, and sign in once. OAuth credentials persist in `/data`. - -All five integrations use V2 Code Mode. The default **Build** agent can use them. Without a Lunch Money token its server cannot start. Check connections with: +Check connections with: ```sh docker compose exec opencode opencode api get /api/mcp --server http://127.0.0.1:4096 ``` -Grafana can advertise tools before credentials are configured; verify access by -listing datasources and running a small query, not just checking MCP connection -status. To turn an integration off, set `disabled: true` on its entry under -`mcp.servers` in your replacement config. - -## Superpowers, worktrees, and quota +Grafana and Karakeep can connect before their credentials are validated; check a small read query too. Disable unused integrations with `disabled: true` in their `mcp.servers` entries; see [MCP configuration](https://opencode.ai/v2/docs/mcp-servers). -[`Superpowers`](https://github.com/obra/superpowers) v7.0.0 replaces Orchestra, -pinned to its full Git commit for reproducible installs. The default **Build** -agent uses native skills for planning, debugging, TDD, review, and verification. -Superpowers registers its skills and injects its bootstrap into top-level sessions; -native subagents can load skills without restarting the controller workflow. +## Agents and configuration -Skills in [`skills/`](skills) ship in the image at `/etc/opencode/skills` and update with it: `commit-push-pr` for the commit and PR workflow, and `go-repo` for Go repository scaffolding, lint, and release plumbing. +- **Build** is the default agent and can use all configured tools. **General** can implement and test; **Explore** is read-only. Both subagents may use Context7 but deny Grafana, Lunch Money, Karakeep, Notion, Robinhood, and further delegation. Tool permissions do not sandbox shell access. +- [Superpowers](https://github.com/obra/superpowers) supplies planning, debugging, testing, and review skills. The image also ships [`commit-push-pr` and `go-repo`](skills/). [`AGENTS.md`](AGENTS.md) requires repository edits in `/data/workspace/.worktrees//`. +- [OpenCode Quota](https://github.com/slkiser/opencode-quota) provides `/quota` and `/quota_status`. +- Defaults and pinned plugins live in [`opencode.jsonc`](opencode.jsonc). The image converts it to `/etc/opencode/opencode.json`; project `opencode.json(c)` files can override settings. See the [V2 configuration guide](https://opencode.ai/v2/docs/config). +- Each startup copies the image's [`AGENTS.md`](AGENTS.md) to `/data/.config/opencode/AGENTS.md`, replacing local edits. Change instructions in this repository and rebuild. -Only Build receives the blanket tool allow. General can implement and test, while Explore remains read-only; both native subagents are denied Grafana, Lunch Money, Karakeep, Notion, and further delegation. They may use Context7 through Code Mode, whose nested calls enforce each tool's permissions. These are tool boundaries, not OS isolation: General's shell still runs with the server user's authority. +To replace the image defaults, install dependencies, edit `opencode.jsonc`, run `node scripts/build-config.mjs runtime.json`, and mount the output read-only at `/etc/opencode/opencode.json`. Restart the container after replacing it. -Repository edits must run in Git worktrees under the shared workspace root: -`/data/workspace/.worktrees//`. Create the task branch before -editing and move the session there. OpenCode's native worktree API also uses -`/data/workspace/.worktrees` as its parent (and appends its requested/generated name). -The directory setting chooses placement; the global `AGENTS.md` supplies the -always-use-worktrees rule. It does not sandbox arbitrary shell commands. +## Hosting -[`OpenCode Quota`](https://github.com/slkiser/opencode-quota) provides `/quota` and -`/quota_status`; remaining balances do not influence routing. It replaces the -deprecated Cardinal fork; existing quota configuration remains compatible. Gemini Code Assist -quota requires [organization setup](https://github.com/slkiser/opencode-quota/blob/main/docs/readme/providers.md#gemini-cli). - -Old persistent `orchestra.jsonc` files are left untouched for rollback, but the -new image does not load Orchestra or generate its agents. Existing sessions that -selected `orch-lead` should switch to **Build** after deploying the image. - -### Additional plugin proposals - -Research checked on 2026-10-11; these are **not installed**: - -| Candidate | Recommendation | V2 and deployment considerations | -| --- | --- | --- | -| [opencode-dcp](https://github.com/arafays/opencode-dcp) | Best next trial for long Copilot sessions: model-directed compression and pruning. | Explicit V2 implementation. Pin a compatible version and test alongside Superpowers/native compaction; summaries can drop important context and alter prompt-cache behavior. | -| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Useful for unattended tasks if you want a server-side webhook/notification command. | Explicit dual V1/V2 support. Web clients do not receive its desktop sounds/popups; remote alerts need an explicitly configured server command. Native terminal notifications may already suffice. | -| [opencode-security-guard](https://www.npmjs.com/package/opencode-security-guard) | Optional shell-approval layer, not a default addition. | Supports V2 `>=2.0.14 <3`, but sends eligible command text to OpenRouter/Jev and needs a credential. Requires a privacy/cost decision; classifications are not a sandbox. | - -Keep Quota, WakaTime, and the current MCP integrations. Avoid adding another -orchestrator such as Oh My OpenCode alongside Superpowers. The popular -[`@tarquinen/opencode-dcp`](https://github.com/Opencode-DCP/opencode-dynamic-context-pruning) -is distinct from the V2-native `opencode-dcp` above; do not infer V2 compatibility -from its own package major version. - -## Public hosting - -Use HTTPS. With Caddy on the host: - -```caddyfile -code.example.com { - reverse_proxy 127.0.0.1:4096 { - flush_interval -1 - } -} -``` +Compose binds only to `127.0.0.1:4096`. Put an HTTPS reverse proxy in front for remote access. A containerized proxy can share a network and use `opencode:4096` instead of the host port mapping. -For a container proxy, join its network, remove the Compose `ports` mapping, -and proxy to `opencode:4096`. +The container runs as UID 1000 with dropped capabilities and resource limits. Login grants code execution and access to stored credentials; this is a trusted-user service, not a multi-tenant sandbox. -The container runs non-root with dropped capabilities and resource limits. -Keep these controls. **Login grants code execution and -access to stored credentials.** Shared networks permit service-to-service access; -outbound host/LAN access is unrestricted. This is not a multi-tenant sandbox. +## Development -## Configuration and development +The image includes Go, TypeScript, pnpm, go-task, golangci-lint, and shell tools. See [`Dockerfile`](Dockerfile) for pinned versions. Additional Go tools and pnpm global installs persist under `/data/go` and `/data/.local/share/pnpm`. -- Includes Go, TypeScript, pnpm, go-task, golangci-lint, and common shell tools; see [`Dockerfile`](Dockerfile) for versions. Go tools and pnpm global installs persist under `/data/go` and `/data/.local/share/pnpm`. -- Defaults live in [`opencode.jsonc`](opencode.jsonc), loaded at - `/etc/opencode/opencode.json` after JSONC conversion. To replace this file, - edit the source config, run `node scripts/build-config.mjs runtime.json`, and - mount `runtime.json` there read-only. Project `opencode.json(c)` files can override settings normally. -- [`AGENTS.md`](AGENTS.md) is the global instruction file. Each startup copies it to `/data/.config/opencode/AGENTS.md` and overwrites local edits, so change it here instead. OpenCode V2 loads the global file before project instructions and detects ambient edits before the next model request. These are model instructions, not hard permission enforcement. V2 ignores the old `instructions` array and does not run language servers; use each project's lint, typecheck, and compiler commands. -- Restart OpenCode after config changes. After environment/password changes, run - `docker compose up -d --force-recreate opencode`. -- Versions are pinned in `Dockerfile` and `opencode.jsonc`. CI tests both - architectures and publishes `main` with provenance attestations. +With the Node.js and pnpm versions pinned in `Dockerfile` and `package.json`: ```sh pnpm install --frozen-lockfile --ignore-scripts --no-optional pnpm test -node scripts/test-router-integration.mjs docker build -t opencode-server . bash scripts/smoke-test.sh opencode-server -gh attestation verify oci://ghcr.io/icco/opencode-server:main --owner icco ``` -The integration check needs the OpenCode version pinned in `Dockerfile`; it uses isolated data on port 4197 (`ROUTER_TEST_PORT` overrides) and requests no inference. It checks every plugin, Superpowers and image skill registration, Build/MCP permissions, worktree settings, Lunch Money/Karakeep stdio handshakes, and quota commands. Image smoke tests also check Grafana and need Node.js 26, Docker, Bash, curl, jq, OpenSSL, and `timeout`. CI runs them on amd64 and arm64. +Smoke tests require Docker, Bash, Node.js, curl, jq, OpenSSL, and `timeout`. They exercise the real server, authentication, plugins, permissions, MCP connections, and quota commands without model inference. CI runs both architectures before publishing `main` with provenance attestations. From 21b9c006ceee7d29e642647465d485d234246fc5 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:40:00 +0000 Subject: [PATCH 5/6] docs: link robinhood agent onboarding guide --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 590be50..137e907 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Set the variables below before creating the container. For remote servers, open | [Karakeep](https://docs.karakeep.app/integrations/mcp) | `KARAKEEP_API_KEY` and `KARAKEEP_API_ADDR` (defaults to `https://hoard.natwelch.com`); includes write tools. | | [Lunch Money](https://github.com/akutishevsky/lunchmoney-mcp) | `LUNCHMONEY_API_TOKEN` from [developer settings](https://my.lunchmoney.app/developers); required to start its MCP server. | | [Notion](https://developers.notion.com/guides/mcp/overview) | Remote server at `https://mcp.notion.com/mcp`. | -| Robinhood | Remote trading server at `https://agent.robinhood.com/mcp/trading`. | +| [Robinhood](https://robinhood.com/us/en/support/articles/onboarding-an-external-agent/) | Remote trading server at `https://agent.robinhood.com/mcp/trading`; complete account onboarding during sign-in. | Check connections with: From b9bdb79aa18c2938986f5b4fdfb219557109c490 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Mon, 12 Oct 2026 23:40:00 +0000 Subject: [PATCH 6/6] ci: rerun checks