Skip to main content

opencode-talk-bridge

CI PyPI Release Python License: MIT Ruff

Drive a local OpenCode coding agent from Nextcloud Talk — a self-hosted chat bridge.

An incoming Talk message is forwarded to a locally-running opencode serve HTTP API; the agent's reply (long output / code as a file attachment) is posted back into the conversation. The bridge uses polling (no webhooks), so it works on institutional Talk instances where you have no admin access and cannot register a webhook bot.

⚠️ This bridge runs AI coding-agent actions on your machine, triggered from chat in infrastructure you may not control. Read the Threat model first. A non-empty user allowlist is mandatory — the bridge refuses to start without one.


How it works

Nextcloud Talk  ──long-poll──▶  bridge  ──HTTP──▶  opencode serve (localhost)
   (you type)   ◀──post────────  bridge  ◀──SSE────  (agent runs locally)
  • Polling, not webhooks: the bridge long-polls the Talk chat endpoint (lookIntoFuture=1 + lastKnownMessageId) using only your app password.
  • OpenCode stays local: the bridge talks to opencode serve over 127.0.0.1. It does not start OpenCode — run it yourself, via launchd, or via the companion menubar app. The bridge health-checks it and reports when it is down.
  • Prompts block; permissions are concurrent: POST /session/{id}/message blocks until the turn finishes, so each prompt runs in a worker thread while a shared SSE consumer (/global/event) routes permission requests back into the conversation in real time.

Requirements

  • Python ≥ 3.10, macOS or Linux.
  • A Nextcloud account with Talk and an app password (Settings → Security → App passwords) — not your login password.
  • A running opencode serve. Tested against OpenCode 1.15.11; expected compatible with the OpenCode 1.15+ HTTP API until it changes. Default endpoint http://127.0.0.1:4096.

Install

One command — installs the opencode-talk-bridge CLI from PyPI into an isolated environment and onto your PATH (uv or pipx):

uv tool install opencode-talk-bridge
# or:  pipx install opencode-talk-bridge

For the unreleased latest commit, install from git instead: uv tool install git+https://github.com/leiverkus/opencode-talk-bridge.git.

Then create your config interactively and run:

opencode-talk-bridge --init     # interactive .env wizard (chmod 600)
opencode-talk-bridge --check    # validate config + OpenCode health
opencode-talk-bridge            # run (reads ./.env, or pass --env-file PATH)

--init asks only for the essentials; every other option keeps a safe default and can be added later (see .env.example for the full list).

First run against a real Talk server? Follow the end-to-end smoke test — it tests the riskiest paths first and covers the two-accounts gotcha.

Upgrade with uv tool upgrade opencode-talk-bridge (or pipx upgrade …).

From source (for development)
git clone https://github.com/leiverkus/opencode-talk-bridge.git
cd opencode-talk-bridge
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"         # editable + test/lint tools
cp .env.example .env            # then edit .env

The OCS client is the separate nextcloud-talk-core package from PyPI (tracking the 1.x line) — never reimplemented or vendored here.

Configure

Edit .env (see .env.example for the full list):

Variable Required Purpose
NC_URL, NC_USER, NC_APP_PASSWORD ✅ Nextcloud Talk credentials (app password).
TALK_CONVERSATIONS ✅ Conversation token(s) to watch, or all.
ALLOWED_USERS ✅ Comma-separated user IDs allowed to issue commands. Empty ⇒ refuses to start.
OPENCODE_URL – OpenCode base URL (default http://127.0.0.1:4096).
OPENCODE_USERNAME / OPENCODE_PASSWORD – Basic-Auth if your OpenCode server is secured.
OPENCODE_DIRECTORY – Workspace directory for OpenCode sessions.
OPENCODE_MODEL – Default model providerID/modelID.
SHARE_WEBDAV_DIR – WebDAV folder (relative to user root) for code/long-output and TTS attachments. Created on demand; blank disables attachments.
RESPONSE_STREAMING, STREAM_THROTTLE_MS – Live-stream replies via message editing (default on, 1500 ms throttle).
HIDE_TOOL_MESSAGES, HIDE_THINKING – Suppress 💻 tool / 💭 thinking notices.
BOT_LOCALE – UI language: de (default) or en.
STT_API_URL / STT_API_KEY / STT_MODEL / STT_LANGUAGE – Whisper-compatible speech-to-text for voice notes.
TTS_API_URL / TTS_API_KEY / TTS_MODEL / TTS_VOICE – OpenAI-compatible text-to-speech for /tts replies.
TASK_LIMIT, LIST_LIMIT, TRACK_BACKGROUND_SESSIONS – Scheduler limit, picker size, background notices.
DB_PATH, STATUS_FILE, LOG_LEVEL – Storage + logging.

ALLOWED_USERS must be the stable user ID (the login, e.g. jdoe), not the display name. The bridge matches the OCS actorId with actorType == "users", so guests and bots can never impersonate an allowed user even if display names collide.

Run

# Validate config + check OpenCode health, then exit:
opencode-talk-bridge --check

# Run the bridge (foreground):
opencode-talk-bridge            # or: python -m opencode_talk_bridge

Commands (in Talk)

Send any message (or a voice note / file) to prompt OpenCode. The reply streams live into one message as it is generated. Slash-commands:

Command Effect
/new Start a fresh OpenCode session for this conversation.
/session Show the current session id.
/sessions List & switch recent sessions.
/rename <title> Rename the current session.
/detach Detach from the current session.
/messages Browse messages, then revert or fork.
/model [providerID/modelID] Show, pick, or set the model.
/agent [name] Show, pick, or set the agent (e.g. plan/build).
/projects Switch the OpenCode project.
/worktree Switch the git worktree.
/commands Browse & run custom OpenCode commands.
/skills Browse & run OpenCode skills.
/mcps Enable/disable MCP servers.
/tts Toggle spoken (audio) replies (needs TTS_* + SHARE_WEBDAV_DIR).
/task <min> <prompt> Schedule a task (/task every <min> … to repeat).
/tasklist List & delete scheduled tasks.
/stop Abort the current run.
/status Show bridge & OpenCode health.
/help List commands.

/projects lists every project the OpenCode backend knows (same as the desktop app). /sessions, /commands, and /skills are project-scoped: they show what the conversation's current project shows — pick a project with /projects (or set a default OPENCODE_DIRECTORY). With no project bound, /sessions falls back to a global list across all projects.

No inline buttons on Talk → every picker is a numbered list: reply with the number. When OpenCode asks permission for a dangerous action, reply ja (allow once), immer (allow for this session), or nein (deny); agent questions are answered by their option number or free text.

Set BOT_LOCALE=en for English UI strings (default de).

Run under launchd (macOS)

A user-agent example is in deploy/. Edit the paths, then:

cp deploy/com.leiverkus.opencode-talk-bridge.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.leiverkus.opencode-talk-bridge.plist
# stop / uninstall:
launchctl bootout gui/$(id -u)/com.leiverkus.opencode-talk-bridge

Secrets stay in .env (read from WorkingDirectory), never in the plist.

Status file (menubar-app contract)

The bridge atomically writes STATUS_FILE (JSON) on every state change. A supervising app (e.g. the Swift menubar app) can poll it. The file is always valid JSON (temp-write + rename). Schema:

{
  "state": "polling",
  "since": 1748600000,
  "opencode_healthy": true,
  "conversations": ["abcdef12"],
  "last_error": null,
  "version": "0.1.0"
}

state ∈ starting · polling · working · opencode_down · error · stopped. since is a Unix timestamp; conversations lists watched tokens; last_error is a human-readable string or null.

Threat model

This bridge executes coding-agent actions on your machine (file writes, shell commands via OpenCode), triggered by chat messages in a Nextcloud Talk instance you may not administer (e.g. a university server). Treat that Talk instance as semi-trusted infrastructure. The trust boundary and mitigations:

  • Access control is the allowlist. Only actorType == "users" IDs in ALLOWED_USERS can issue commands; every other message is ignored. An empty allowlist aborts startup. Anyone who can post as an allowlisted user can run code as you — keep the list minimal and the control conversation private.
  • Dangerous actions require confirmation. OpenCode's permission prompts (shell, file writes) are surfaced into Talk and must be answered; nothing runs without a ja/immer. Configure OpenCode's own permission policy conservatively as defence in depth.
  • Secrets are never echoed. The bridge does not post your app password or tokens, and permission prompts show only the action kind and a length-capped pattern — not raw command arguments that might contain secrets. Be aware that OpenCode's answer text itself could contain sensitive repo content; it is posted into Talk, whose admins can read it.
  • A compromised Talk server could inject messages appearing to come from an allowlisted user. The allowlist mitigates casual misuse, not a fully compromised IdP/server. Do not point this at sensitive repos if that is your threat model.
  • Local-only OpenCode. Keep opencode serve bound to 127.0.0.1. If you expose it, secure it with OPENCODE_USERNAME/OPENCODE_PASSWORD.

To report a vulnerability, see SECURITY.md (please don't open a public issue).

Stability

This project follows SemVer. From 1.0.0, the following are the stable public contract — breaking changes to them only happen on a major bump:

  • CLI: the opencode-talk-bridge command and its flags (--init, --check, --env-file).
  • Configuration: the documented .env keys (see .env.example) and their meaning.
  • Talk commands: the slash-command names (/new, /sessions, …).
  • Status file: the JSON schema and its state values.
  • State store: SQLite migrations stay forward-compatible — upgrading never requires deleting your database.

Not covered (may change in any release): the Python module/API layout, exact wording of bot replies and log lines (the i18n strings), and which OpenCode/Nextcloud versions are supported.

Pre-1.0.0 (the current 0.x line) these may still change between minor versions; pin a version you have tested.

Development

pip install -e ".[dev]"
ruff check src tests
ruff format --check src tests
pytest

All tests use mocked HTTP for both Talk and OpenCode — no live calls. CI runs the matrix on Python 3.10–3.13. Dev setup, conventions, and the (one-command) release process are in CONTRIBUTING.md.

Changelog

See CHANGELOG.md.

License

MIT © Patrick Leiverkus

Metadata

Release files for opencode-talk-bridge 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opencode-talk-bridge 0.3.0
File Size Uploaded Provenance
opencode_talk_bridge-0.3.0.tar.gz 70.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for opencode-talk-bridge 0.3.0
File Interpreter ABI Platform
opencode_talk_bridge-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 119.8 kB

Release files / opencode_talk_bridge-0.3.0.tar.gz

Download URL opencode_talk_bridge-0.3.0.tar.gz
Size 70.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c19aacad5d768a4e467062f25190594fefcc4397acb30ae40502ada375b153a5
BLAKE2b-256 checksum
How to use checksums
114933b55242cd558c8181db70b53e7c4c350bbeb2d218baa766c52997aef437
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Release files / opencode_talk_bridge-0.3.0-py3-none-any.whl

Download URL opencode_talk_bridge-0.3.0-py3-none-any.whl
Size 49.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1e6410b51fb916c2398dc3a50f077c8147e63e5e5a5aea0f248900f0a50e9b9e
BLAKE2b-256 checksum
How to use checksums
9828d72389507a46cfb2a44344285b52f88e043c19e07c8d6d5f9f13b2807883
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

Security for opencode-talk-bridge 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for opencode_talk_bridge-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page