Skip to content

Developer guide

chatwire is an open-source project written in Python (bridge + web server) and TypeScript (React frontend + mobile app). This guide covers the architecture, how to contribute, and how to build plugins and themes.

Tech stack

Layer Technology
Bridge Python 3.10+, asyncio, sqlite3
Web server FastAPI + uvicorn
Frontend React 18, TypeScript, Vite, TanStack Query, Zustand
Mobile React Native + Expo (iOS + Android)
Plugin SDK Python, chatwire-sdk package
Process management macOS launchd
Tests pytest (Python), vitest (frontend), Jest (mobile)

Quick start for contributors

git clone https://github.com/allenbina/chatwire.git
cd chatwire
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/chatwire install-agents
.venv/bin/chatwire doctor

See Contributing for the full setup.

Guide pages

Page What you'll find
Architecture Process model, data flow, plugin sandbox, config format
Contributing Clone, dev install, tests, PR process
Plugin SDK BaseIntegration, hooks, slot API, settings schema
Theme format CSS variable reference, packaging, custom CSS
API reference REST endpoints, SSE, MCP tools
Testing pytest + vitest + Jest, CI, coverage
Release process Versioning, PyPI, Homebrew tap, changelogs

Repo layout

bridge.py             Message relay loop + integration dispatcher
chat_db.py            Reads chat.db, HEIC → JPEG via sips
chat_send.py          osascript wrappers + anti-spam guard
config.py             config.json loader and migrator
chatwire_cli.py       CLI: setup / install-agents / doctor / logs / status / migrate / mcp
contacts.py           Contacts.app → handle/name lookup
echo_log.py           Cross-process echo dedup
whitelist.py          Runtime-mutable contact allowlist
rules.py              Automation rules engine
_version.py           Semver source of truth
verify.py             Plugin signature verification

integrations/         Built-in plugins (web, stats, mcp, favorites)
chatwire-plugins/     Standalone plugin packages (ntfy, telegram, webhook, mqtt, ha, xmpp)
packages/sdk/         chatwire-sdk Python package (BaseIntegration, plugin CLI)
packages/shared/      @chatwire/shared TypeScript types + ChaiwireClient
packages/mobile/      React Native + Expo mobile app

web/                  FastAPI web server
  main.py             App entry point, middleware, auth, health endpoints
  api_v1.py           REST API router
  api_ui.py           UI-specific API router (stats, themes, etc.)
  auth.py             Cookie session + Bearer token auth
  api_keys.py         API key store and scope enforcement
  setup_wizard.py     First-run wizard routes
  themes.py           Theme discovery and picker
  log_stream.py       Structured JSONL logger
  registry.py         Plugin marketplace registry
  version_check.py    Update notification

web/frontend/         React SPA
  src/components/     UI components (MessageList, ComposeBox, Layout, …)
  src/pages/          Route-level pages (ChatPage, SettingsPage, PopoutPage)
  src/hooks/          Custom React hooks (useTheme, useSounds, useServerEvents)
  src/plugins/        Plugin slot system (registry, SlotRenderer, StatsWidget)
  e2e/                Playwright E2E + axe accessibility tests

migrations/           Config schema migration runner
templates/launchd/    Plist templates rendered by install-agents
scripts/              install.sh, chatwire-loop.sh

docs/                 This documentation
docs/wiki/            (legacy — superseded by docs/ tree)

Key design decisions

  • Two separate processes — the bridge and web server are independent launchd agents. They communicate via files on disk (no IPC socket). This means the web UI keeps working if the bridge crashes, and vice versa.
  • Flat Python layout — top-level modules instead of a chatwire/ package. Avoids a sweeping import rewrite; py-modules in pyproject.toml handles packaging.
  • Plugin sandbox — plugins run through a SandboxedContext that restricts data access by tier. Raw phone numbers and emails never reach plugin code directly.
  • No hot-reload — the bridge reads config.json on restart, not dynamically. This keeps the process model simple.
  • Zero dependencies for the bridge core — the bridge itself only uses the Python stdlib (sqlite3, asyncio, subprocess). FastAPI and its dependencies are only needed by the web process.