Skip to main content

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

Project description

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"
}

statestarting · 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opencode_talk_bridge-0.3.0.tar.gz (70.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opencode_talk_bridge-0.3.0-py3-none-any.whl (49.7 kB view details)

Uploaded Python 3

File details

Details for the file opencode_talk_bridge-0.3.0.tar.gz.

File metadata

  • Download URL: opencode_talk_bridge-0.3.0.tar.gz
  • Upload date:
  • Size: 70.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for opencode_talk_bridge-0.3.0.tar.gz
Algorithm Hash digest
SHA256 c19aacad5d768a4e467062f25190594fefcc4397acb30ae40502ada375b153a5
MD5 cecf16d75e232d1adb287c37ff515b03
BLAKE2b-256 114933b55242cd558c8181db70b53e7c4c350bbeb2d218baa766c52997aef437

See more details on using hashes here.

Provenance

The following attestation bundles were made for opencode_talk_bridge-0.3.0.tar.gz:

Publisher: publish.yml on leiverkus/opencode-talk-bridge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file opencode_talk_bridge-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for opencode_talk_bridge-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e6410b51fb916c2398dc3a50f077c8147e63e5e5a5aea0f248900f0a50e9b9e
MD5 595da478558e1835a374dac7b33316b2
BLAKE2b-256 9828d72389507a46cfb2a44344285b52f88e043c19e07c8d6d5f9f13b2807883

See more details on using hashes here.

Provenance

The following attestation bundles were made for opencode_talk_bridge-0.3.0-py3-none-any.whl:

Publisher: publish.yml on leiverkus/opencode-talk-bridge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page