Skip to content

Repository files navigation

PingPongCode

PingPongCode · 码来码去

You ping. Code pongs.

An open-source messaging bridge connecting WeChat, Feishu, and other chat platforms to Claude Code, Codex, and more.


Key Features

  • Lightweight, no build step. Pure Python, single asyncio process — no C extensions, no compiler toolchain, no Node sidecar, no Docker, no web framework, no browser automation.

  • Easy to use. A single /cc command surface covers querying, reviewing, and bidirectional file/image transfer. Everything is driven from the chat window — no extra client UI.

  • Safe development loop. Opt-in remote dev loop: each write task runs in an isolated git worktree (main workspace untouched), sensitive tool calls go through a IM approval gate, a risk classifier allow/ask/deny each call, and one command decides keep or rollback.

  • Bidirectional resume. IM and the desktop Terminal share the same local Vibe Code sessions — resume a Terminal session from your phone, or hand a IM session back to the computer.


How it works

One bridge process joins two halves: the iLink half (WeChat transport — long-polling, AES-encrypted CDN) and the Claude half (claude_agent_sdk.query() → bundled claude CLI → Anthropic-protocol gateway). The pingpong-bridge is the single orchestrator and the only place that calls Claude. Project/session/runtime state lives in SQLite; the Claude Code local transcript remains the single source of truth for session content, accessed only via the Agent SDK session API.

PingPongCode architecture


The three modes

PingPongCode has three operating modes that layer on top of each other. All are off by default, so the read-only path is the unchanged baseline — you only turn on what you need.

  • Legacy read-only (default). Claude can only Read/Glob/Grep against a single WORKSPACE_DIR — no edits, no shell. The safe baseline for project analysis, send/fetch reports/figures. Nothing to configure; this is what you get out of the box.

  • Managed mode (opt-in). Set CC_ALLOWED_PROJECT_ROOTS to let WeChat and your desktop Terminal share the same local Claude Code sessions — resume a Terminal session from your phone (/cc resume), or hand a WeChat session back to the computer (/cc handoffclaude --resume <id>). Write-capable but confined to allowed roots under a project+session lock.

  • Safe development loop (opt-in, requires managed mode). Set CC_SAFE_DEV_LOOP_ENABLED=true for a full remote dev loop: each write task runs in an isolated git worktree (main workspace untouched), sensitive tool calls go through a WeChat approval gate (/cc approve//cc deny), and a structured report is reconciled against Bridge-computed git/test facts. One command decides keep or rollback (/cc keep//cc rollback). Switch from WeChat with /cc mode readonly|edit|full.

For the full comparison table, exact permission_mode/setting_sources mappings, and how the two opt-ins layer, see docs/DETAIL.md#three-modes.


Requirements

Runtime environment

Requirement Notes
OS Ubuntu / Linux (designed & tested) · macOS (tested). Windows not supported currently (os.kill liveness + 0600 perms).
Python ≥ 3.11
claude CLI On PATHclaude-agent-sdk spawns it. The SDK bundles a compatible CLI; the system claude may differ.
git Required by managed mode (git init) and essential for the safe-dev-loop (every write task uses a git worktree).
Gateway An Anthropic-protocol endpoint. The SDK sets ANTHROPIC_BASE_URL and speaks the Anthropic Messages API. Image analysis needs a vision model (e.g. claude-sonnet-5, minimax-m3, etc.).
WeChat Via an iLink/ClawBot session.

Python dependencies

Installed via pip install -r requirements.txt:

Package Purpose
claude-agent-sdk spawns the bundled claude CLI subprocess
httpx iLink HTTP API (long-poll, send, CDN upload/download)
pydantic / pydantic-settings / python-dotenv config model + .env loading
aiosqlite async SQLite persistence
qrcode / pillow iLink QR login rendering
rich terminal output / QR display
tenacity polling backoff/retry

Install

From the repo root. Create a Python ≥ 3.11 environment, then install the dependencies — venv or conda both work (uv/poetry too, if you prefer):

# Option A — venv (built into Python, no extra install)
python3 -m venv .venv
source .venv/bin/activate

# Option B — conda
conda create -n pingpongcode python=3.11
conda activate pingpongcode

# then, in whichever env you activated:
pip install -r requirements.txt
pip install -e .

Ubuntu's system Python is externally-managed (PEP 668) — use one of the environments above, or --break-system-packages.


Configure

Recommended — the interactive wizard (grouped, validated, secrets masked):

python -m pingpong_bridge config init

Or create .env by hand:

cp .env.example .env
$EDITOR .env

For the required variables (gateway, model, WORKSPACE_DIR), managed-mode & safe-dev-loop variables, and how the wizard validates values, see docs/DETAIL.md#configure.

Check config (secrets masked):

python -m pingpong_bridge show-config

Note: running straight from source without a registered package, i.e., you prefer not using pip install -e .? Prefix the module with PYTHONPATH=src — e.g. PYTHONPATH=src python -m pingpong_bridge login (and likewise for run/check-*).


Run

Four steps, in order:

A. Scan to log in — terminal prints a QR (also saved to artifacts/ilink-login.png); scan with WeChat iLink/ClawBot and confirm. Credentials save to data/ilink_credentials.json (0600).

python -m pingpong_bridge login

B. iLink bidirectional self-check — run it, then send /cc ping from your phone; expect [CC] pong.

python -m pingpong_bridge check-ilink

C. Gateway + Agent SDK self-check — no phone needed.

python -m pingpong_bridge check-gateway

D. Start the bridge — runs in the foreground; Ctrl+C to stop.

python -m pingpong_bridge run

Note: on first run (if ILINK_ALLOWED_USER_ID isn't set) the terminal shows a pairing code — send /cc pair <code> from your phone to bind, then any /cc task.

Pairing details, a first-task example, and how to stop/restart: docs/DETAIL.md#run.


Commands

All commands are /cc-prefixed (case-insensitive). Only messages from the bound user are executed; bot's own messages and plain chat are ignored. Commands are grouped by the mode that enables them.

📋 Quick-reference cheatsheet (click to expand)

PingPongCode command cheatsheet

Common (all modes)

Command Purpose
/cc ping connectivity test → [CC] pong
/cc status mode-aware status (mode, working dir, project/session, runtime counters, iLink state, model — no secrets)
/cc help command list
/cc pair <code> first-time user binding
/cc <task> run a task against the current project
/cc ask <task> explicit ask form (use for tasks starting with a keyword like resume)
/cc send <relative-path> send a workspace file/image back to WeChat (details)
/cc clear drop buffered images/files

Command typos are not run as tasks. /cc aprove or /cc reports (no further text) replies "unknown command" with a "did you mean" hint — retype the correct command. A typo with trailing text (/cc aproove fix the bug) still runs as a task.

Managed mode (after CC_ALLOWED_PROJECT_ROOTS)

Command Purpose
/cc projects list discovered/registered projects
/cc project <name|index|new <name>|info> select / create / inspect a project
/cc sessions [limit|all] list sessions for the active project
/cc resume <index|id|prefix> set the active session (next /cc <task> resumes it)
/cc new-session [title] start a fresh session in the active project
/cc session show the active project + session
/cc rename-session <title> rename the active session
/cc handoff get cd '<cwd>'\nclaude --resume '<id>' to continue in a Terminal
/cc doctor bridge self-check

Bidirectional resume workflow (Terminal↔WeChat, restart persistence) and the path-safety model: docs/DETAIL.md#resume.

Safe dev loop (after CC_SAFE_DEV_LOOP_ENABLED=true)

Command Purpose
/cc mode readonly|edit|full switch the active mode (persisted)
/cc tasks list recent task runs (numbered, like /cc sessions)
/cc task [n|task-id] show one task's state/report — by list index or id
/cc stop interrupt the running task (worktree preserved)
/cc retry <task-id> retry a failed/cancelled task
/cc approvals list pending approvals
/cc approve|deny [code] [reason] decide a pending approval (omit code when only one is pending; deny may take a reason that is fed back to the model to guide a fallback)
/cc answer <code> <text> answer a question the model asked mid-task (seen as [CC][提问 XXXX]; code required)
/cc report|diff|files|tests|usage [task-id] inspect a task's reconciled facts
/cc keep preserve the worktree + branch
/cc continue [task-id] next task chains off a kept task's branch (progressive dev)
/cc rollback delete the worktree + branch (second approval)
/cc worktree show the task's worktree path
/cc checkpoints / /cc rewind <id> (optional, capability-gated) file checkpointing

Flow, the mode × tool permission matrix, hard invariants, and the SDK workarounds: docs/DETAIL.md#safe-dev-loop.

Two-phase planning (on by default; after CC_TWO_PHASE_ENABLED=true)

On by default, but inert unless the safe dev loop is enabled and the backend is Claude. When active, a bare /cc <task> (mode edit/full) splits into a multi-turn planning phase (plan mode, read-only) and a one-shot execution phase (same worktree, default mode). The model calls ExitPlanMode to submit its plan; you confirm or reject from WeChat. Set CC_TWO_PHASE_ENABLED=false to keep the single-phase safe-dev-loop path.

Command Purpose
/cc say <msg> append a follow-up message to the in-flight planning conversation (multi-turn)
/cc confirm [code] approve the proposed plan → proceed to execution
/cc reject [code] [reason] reject the plan → Claude revises it in the same planning conversation
/cc cancel end the in-flight planning/execution conversation

Design and E2E findings: docs/DETAIL.md#two-phase.


Execution rules (all modes): only one task at a time (a second gets "try later"); long results are split into segments ≤ WECHAT_MAX_TEXT_CHARS; a reply send failure never re-runs the Claude task.


Other CLI commands

python -m pingpong_bridge doctor        # bridge self-check
python -m pingpong_bridge doctor --live # also probe iLink send/receive
python -m pingpong_bridge logout        # delete iLink credentials
python -m pingpong_bridge reset-state   # delete the SQLite state DB
python -m pingpong_bridge show-config   # masked config overview
python -m pingpong_bridge config init   # interactive .env wizard
python -m pingpong_bridge config show   # grouped masked config
python -m pingpong_bridge config edit <KEY> [VAL]  # edit one field (validated)

Why a "task"? — design rationale

A task is the bridge's unit of remote work: one /cc <task> you send from your phone. The design is built on one idea — a task is an auditable, rollback-able execution unit + an isolated worktree of changes — and that idea shapes every task command.

  • Isolated by default (worktree). Each write task runs in its own git worktree on its own branch (cc/task-…). The model edits files there; your main workspace is never touched. Two tasks run on two separate trees, so they can't stomp on each other. This is why /cc <task> never modifies your working directory directly, and why a failed task leaves nothing behind in your tree.
  • Auditable. A task is a first-class DB record (state machine: received → … → awaiting_decision → kept/rolled_back), with an event trail, a reconciled report (the model's self-report checked against Bridge-computed git diff + test facts — bridge facts win), saved diff/test artifacts, and token/usage. This is what /cc tasks, /cc task, /cc report|diff|files|tests|usage, and /cc usage read from.
  • Rollback-able. Because the bridge never commits (it only edits the worktree's working tree), rolling back is just deleting the worktree + branch — nothing was ever written to your history, so /cc rollback loses nothing and leaves the main workspace untouched. The inverse, /cc keep, preserves the tree so you can hand off into it and commit yourself when satisfied.

This triad is why the commands are shaped the way they are:

You want to… Command Why it works this way
run a fresh change /cc <task> new worktree off main HEAD; isolated
stop a runaway task /cc stop interrupt; worktree preserved so you can inspect before deciding
retry a failed task /cc retry <id> starts fresh (new worktree, new session) — a failed task's edits are bad, so they're discarded, not inherited
keep good work /cc keep preserve the worktree+branch; hand off into it to commit
build B on top of A /cc keep A → /cc continue B's worktree branches off A's branch tip and applies A's uncommitted diff, so B sees A's changes without a merge
throw it away /cc rollback delete worktree+branch; second approval because it's destructive
inspect what happened /cc report|diff|tests read the task's audited facts (not the model's claim)

The bridge never commits or merges — /cc keep + /cc continue carry changes between worktrees via uncommitted-diff application, not git history.


Concepts: task, session, worktree, branch

In the safe development loop, one write task produces three distinct things that are easy to conflate:

  • task — the execution record of this one task (state, result). Lives in the bridge DB; fixed when the task ends.
  • worktree + branch (cc/task-YYYYMMDD-…) — an isolated git working copy where the model's file edits land. The main workspace is never touched. Preserved by /cc keep, deleted by /cc rollback.
  • session (019fd…) — the conversation history with the model. Persistent across tasks and restarts. A later task can fork a prior session to carry its context forward without polluting it (like a git branch off a commit).

task ↔ worktree ↔ branch is 1:1:1. task ↔ session is not 1:1 — sessions are reused via fork/resume.

The bridge never commits. It edits files in the worktree but never runs git add/commit/push. After /cc keep, you hand off into the worktree and commit yourself when satisfied. /cc rollback deletes the worktree outright (nothing was committed, so nothing is lost from your history).

Progressive development without leaving WeChat. Because each task starts a fresh worktree off the main HEAD, two independent tasks don't see each other's changes. To build feature A then feature B on top of it, use the keep → continue chain: /cc keep task A (preserves its branch), then /cc continue — the next /cc <task> creates its worktree off task A's branch tip (not main HEAD), so it sees A's changes. The session is forked too, so both code and conversation stay continuous. No Terminal merge needed. The chain can be extended: keep → continue → keep → continue …

Full explanation with a diagram and the why-behind-the-design: docs/DETAIL.md#task-session-worktree-branch-how-they-relate.


Known limitations & compatibility

PingPongCode targets claude_agent_sdk 0.2.128 + the bundled claude CLI. The bridge works around 9 verified SDK/CLI differences, and several features are cooperative (not OS-enforced) or deferred. The highlights:

  • Execution locks + claude agents --json detection are cooperative — a user can still manually run claude --resume <same-session-id> in a Terminal despite the bridge holding a lock.
  • The gateway you use sees prompts and project content (read your gateway provider's data policy).
  • AskUserQuestion does not exist in claude_agent_sdk 0.2.128, so the spec's clarifying-question→WeChat flow is not implemented (deferred); only the approval flow is real.
  • /cc rewind//cc checkpoints are capability-gated (worktree-only; rewind only covers Write/Edit/NotebookEdit, not Bash/subagent — use /cc rollback to fully abandon).
  • Auto-merge is out of scope (/cc keep preserves the branch only); for progressive development use the /cc keep/cc continue worktree chain instead of merging.

Full limitations: docs/KNOWN_LIMITATIONS.md. SDK/CLI differences and the 9 workarounds: docs/SDK_CLI_COMPATIBILITY.md.


Project layout

src/pingpong_bridge/
├── ilink/            # WeChat transport (poller, client, parser, protocol, media crypto)
├── projects/         # project discovery, PathPolicy, selection snapshots
├── sessions/         # SDK session API wrapper + backend Protocol
├── execution/        # execution locks, interrupt, checkpoint, live-session detector
├── permissions/      # risk classifier, PreToolUse hook, approval service, capability probe
├── tasks/            # task state machine, recovery, progress
├── worktrees/        # git worktree service (create/keep/rollback)
├── reports/          # diff/test services, reconciliation, reviewer stub
├── delivery/         # outbox (reliable send, retry, dead-letter)
├── media/            # bidirectional file/image transfer (CDN, AES)
├── bridge.py         # the orchestrator
├── config.py         # pydantic-settings (single chokepoint for all validators)
└── ...
docs/                 # SDK/CLI compatibility report + known limitations (public)

Roadmap

Category Supported Planned
IM WeChat ✅ Feishu ❌ · Telegram ❌
Agent Claude Code ✅ · Codex ✅ OpenCode ❌
OS Linux ✅ · macOS ✅ Windows ❌

Changelog

See CHANGELOG.md for the per-version history of added features, fixes, and security-relevant changes. Each release is tagged identically on Gitee (dev) and GitHub (pingpongcode).


License

MIT, see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages