From 8d8e4fb4f5f15d8fa47cb2cf685d13f60066d812 Mon Sep 17 00:00:00 2001 From: Tej Date: Fri, 8 May 2026 19:29:45 +0900 Subject: [PATCH] =?UTF-8?q?docs(readme):=20=EB=B3=B8=EC=B2=B4=20=EB=8C=80?= =?UTF-8?q?=EB=B9=84=20fork=20=EC=B0=A8=EC=9D=B4=EC=A0=90=20=EB=AA=85?= =?UTF-8?q?=EC=8B=9C=20+=20Changelog=20=EC=84=B9=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 상단에 fork notice 추가하여 TejNote/ccbot이 six-ddc/ccbot 의 fork임을 명확히 표시. Features 섹션을 'Upstream' / 'Fork additions' 두 그룹으로 분리해 어떤 기능이 fork 추가분인지 한눈에 보이도록 정리. Codex/OMX provider 라우팅, plugin skill menu (`/favorite`+사용 빈도 sorting), MessageBatcher, DirectMessage 큐, `ccbot send` CLI subcommand, status msg ID 영속화, parse_status_line의 background-shell spinner 차단 등 fork 추가 기능을 Fork additions 절에 항목별로 설명. 옵션 env 변수 표에도 🔱 마크로 fork-only 설정(`CCBOT_BATCH_WINDOW`, `CCBOT_SHOW_USER_MESSAGES`, `CCBOT_SHOW_TOOL_CALLS`)을 표시. File Structure 트리에 send.py / skill_registry.py / message_batcher.py 등 신규 파일과 수정된 모듈을 🔱 표시와 함께 명시. Changelog (fork) 섹션 신설 — 머지된 PR(#1, #2, #4, #5)과 주요 commit을 시간 역순으로 정리하고, 아직 fork에 반영 안 된 upstream 커밋 3건(#67, #73, f5ddd7f)을 'Pending upstream merges' 표로 트래킹. Co-Authored-By: Claude Opus 4.7 --- README.md | 222 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 152 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index afc6f1b7..750a7a63 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ -# CCBot +# CCBot (TejNote fork) [中文文档](README_CN.md) [Русская документация](README_RU.md) -Control Claude Code sessions remotely via Telegram — monitor, interact, and manage AI coding sessions running in tmux. +> 🔱 **This is a fork** of [six-ddc/ccbot](https://github.com/six-ddc/ccbot) maintained at [TejNote/ccbot](https://github.com/TejNote/ccbot). +> Adds Codex/OMX provider routing, a plugin skill menu, message batching/ordering, and several reliability fixes for the Telegram ↔ tmux bridge. See [Fork additions](#fork-additions) and [Changelog (fork)](#changelog-fork) below for details. + +Control Claude Code (and Codex/OMX) sessions remotely via Telegram — monitor, interact, and manage AI coding sessions running in tmux. https://github.com/user-attachments/assets/15ffb38e-5eb9-4720-93b9-412e4961dc93 @@ -23,44 +26,53 @@ In fact, CCBot itself was built this way — iterating on itself through Claude ## Features +### Upstream (shared with [six-ddc/ccbot](https://github.com/six-ddc/ccbot)) + - **Topic-based sessions** — Each Telegram topic maps 1:1 to a tmux window and Claude session -- **Real-time notifications** — Get Telegram messages for assistant responses, thinking content, tool use/result, and local command output +- **Real-time notifications** — Assistant responses, thinking content, tool use/result, local command output - **Interactive UI** — Navigate AskUserQuestion, ExitPlanMode, and Permission Prompts via inline keyboard - **Voice messages** — Voice messages are transcribed via OpenAI and forwarded as text -- **Send messages** — Forward text to Claude Code via tmux keystrokes - **Slash command forwarding** — Send any `/command` directly to Claude Code (e.g. `/clear`, `/compact`, `/cost`) -- **Plugin skill menu** — Installed Claude Code plugin skills (superpowers, pr-review-toolkit, etc.) are auto-discovered and registered in the Telegram `/` command menu -- **Skill favorites** — Toggle favorites via `/favorite` to pin frequently-used skills to the top of the menu -- **Usage-based sorting** — Skills are sorted by per-project usage frequency, so your most-used skills surface first -- **Create new sessions** — Start Claude Code sessions from Telegram via directory browser -- **Resume sessions** — Pick up where you left off by resuming an existing Claude session in a directory -- **Kill sessions** — Close a topic to auto-kill the associated tmux window -- **Message history** — Browse conversation history with pagination (newest first) +- **Create / resume / kill sessions** — Start fresh or pick up an existing Claude session via directory browser; close a topic to auto-kill the window +- **Message history** — Browse conversation history with pagination - **Hook-based session tracking** — Auto-associates tmux windows with Claude sessions via `SessionStart` hook - **Persistent state** — Thread bindings and read offsets survive restarts +### 🔱 Fork additions + +- **Codex / OMX provider routing** — `codex` / `codex-*` windows are auto-detected and routed bidirectionally. Uses tmux paste-buffer (vs. plain send-keys) so the Codex composer receives a single bracketed-paste event. A separate status parser (`parse_codex_status_line`) reports `⏳ Working` and `🔧 ` lines from Codex output. State serialization stays backward-compatible (default `provider=claude` is omitted). +- **Plugin skill menu with usage sorting** — Installed Claude Code plugin skills (superpowers, pr-review-toolkit, octo, etc.) are auto-discovered at startup and registered as Telegram `/` commands. Skills with Korean descriptions show localized text. Use `/favorite` to pin frequently-used skills; per-project usage frequency sorts the rest. +- **MessageBatcher** — Tool-use and thinking events are grouped into a periodic summary (`⚙️ 작업 중 N건`) instead of flooding the chat. Configurable via `CCBOT_BATCH_WINDOW`. +- **DirectMessage queue** — Confirmation messages (commands, photo/voice acks) are routed through the per-user message queue so they never interleave with assistant output. +- **`ccbot send` CLI subcommand** — `ccbot send --session-id ` and `ccbot send --window ` let external hooks (e.g. `Stop`, `PostToolUse`) push messages to a topic without going through Telegram. +- **Persistent status message IDs** — `state.json` now tracks live status message IDs and the bot deletes orphans on next startup, so a crash-and-restart no longer leaves dangling `⏳ Working` messages on the chat. +- **Status polling reliability fixes** — `parse_status_line` ignores background-shell-only spinners (`Sautéed for 3s · 1 shell still running`) so the answer remains the last visible message after a turn ends. Status update is delegated entirely to the polling loop (no immediate enqueue from the content task path). +- **Claude busy-state guard** — `send_keys` checks the receiving pane is idle before transmitting, preventing silent command drops. +- **Hook hardening** — Tmux session name normalization via `TMUX_SESSION_NAME`; `.env` value quoting stripped; `/clear` resets `session_map` correctly. + ## Prerequisites - **tmux** — must be installed and available in PATH - **Claude Code** — the CLI tool (`claude`) must be installed +- **Codex / OMX** *(optional)* — required only if you want Codex windows routed; install [`omx`](https://github.com/) and the bundled `~/Documents/Claude/.omx/hooks/ccbot-bridge.mjs` plugin ## Installation -### Option 1: Install from GitHub (Recommended) +### Option 1: Install from this fork (Recommended) ```bash # Using uv (recommended) -uv tool install git+https://github.com/six-ddc/ccmux.git +uv tool install git+https://github.com/TejNote/ccbot.git # Or using pipx -pipx install git+https://github.com/six-ddc/ccmux.git +pipx install git+https://github.com/TejNote/ccbot.git ``` ### Option 2: Install from source ```bash -git clone https://github.com/six-ddc/ccmux.git -cd ccmux +git clone https://github.com/TejNote/ccbot.git ccbot-src +cd ccbot-src uv sync ``` @@ -91,18 +103,20 @@ ALLOWED_USERS=your_telegram_user_id **Optional:** -| Variable | Default | Description | -| ----------------------- | ---------- | ------------------------------------------------ | -| `CCBOT_DIR` | `~/.ccbot` | Config/state directory (`.env` loaded from here) | -| `TMUX_SESSION_NAME` | `ccbot` | Tmux session name | -| `CLAUDE_COMMAND` | `claude` | Command to run in new windows | -| `MONITOR_POLL_INTERVAL` | `2.0` | Polling interval in seconds | -| `CCBOT_SHOW_HIDDEN_DIRS` | `false` | Show hidden (dot) directories in directory browser | -| `OPENAI_API_KEY` | _(none)_ | OpenAI API key for voice message transcription | -| `OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI API base URL (for proxies or compatible APIs) | - -Message formatting is always HTML via `chatgpt-md-converter` (`chatgpt_md_converter` package). -There is no runtime formatter switch to MarkdownV2. +| Variable | Default | Description | +| -------------------------- | --------------------------- | --------------------------------------------------------------------------------- | +| `CCBOT_DIR` | `~/.ccbot` | Config/state directory (`.env` loaded from here) | +| `TMUX_SESSION_NAME` | `ccbot` | Tmux session name | +| `CLAUDE_COMMAND` | `claude` | Command to run in new windows | +| `MONITOR_POLL_INTERVAL` | `2.0` | Polling interval in seconds | +| `CCBOT_SHOW_HIDDEN_DIRS` | `false` | Show hidden (dot) directories in directory browser | +| `OPENAI_API_KEY` | _(none)_ | OpenAI API key for voice message transcription | +| `OPENAI_BASE_URL` | `https://api.openai.com/v1` | OpenAI API base URL (for proxies or compatible APIs) | +| `CCBOT_BATCH_WINDOW` | `10` | 🔱 Seconds before MessageBatcher emits a summary (`0` to disable batching) | +| `CCBOT_SHOW_USER_MESSAGES` | `true` | 🔱 Echo the user's own message back to the topic (set `false` to suppress) | +| `CCBOT_SHOW_TOOL_CALLS` | `true` | 🔱 Forward `tool_use` / `tool_result` events (set `false` to keep only summaries) | + +🔱 = fork-specific. > If running on a VPS where there's no interactive terminal to approve permissions, consider: > @@ -134,6 +148,10 @@ Or manually add to `~/.claude/settings.json`: This writes window-session mappings to `$CCBOT_DIR/session_map.json` (`~/.ccbot/` by default), so the bot automatically tracks which Claude session is running in each tmux window — even after `/clear` or session restarts. +### `Stop` hook bridge (fork) + +Pair with the `ccbot send` subcommand to push per-window summaries from arbitrary hooks. Example: `~/.local/bin/claude-stop-notify.sh` runs on `Stop`, computes a `git diff --shortstat`, and calls `ccbot send --session-id "$SESSION_ID" "📊 [] N개 파일 변경, M줄 추가"`. + ## Usage ```bash @@ -142,22 +160,27 @@ ccbot # If installed from source uv run ccbot + +# Hook helper / inter-process messaging (fork) +ccbot hook --install +ccbot send --session-id "" +ccbot send --window "" ``` ### Commands **Bot commands:** -| Command | Description | -| ------------- | ---------------------------------- | -| `/start` | Show welcome message | -| `/history` | Message history for this topic | -| `/screenshot` | Capture terminal screenshot | -| `/esc` | Send Escape to interrupt Claude | -| `/kill` | Kill session and delete topic | -| `/unbind` | Unbind topic from session | -| `/usage` | Show Claude Code usage remaining | -| `/favorite` | Toggle skill favorites | +| Command | Description | +| ------------- | --------------------------------- | +| `/start` | Show welcome message | +| `/history` | Message history for this topic | +| `/screenshot` | Capture terminal screenshot | +| `/esc` | Send Escape to interrupt Claude | +| `/kill` | Kill session and delete topic | +| `/unbind` | Unbind topic from session | +| `/usage` | Show Claude Code usage remaining | +| `/favorite` | 🔱 Toggle skill favorites | **Claude Code commands (forwarded via tmux):** @@ -172,20 +195,20 @@ uv run ccbot Any unrecognized `/command` is also forwarded to Claude Code as-is (e.g. `/review`, `/doctor`, `/init`). -**Plugin skills (auto-discovered):** +**Plugin skills (auto-discovered, fork):** -Installed Claude Code plugins are automatically scanned at startup. Their skills appear in the Telegram `/` command menu alongside built-in commands. Skills with Korean translations show localized descriptions. For example: +Installed Claude Code plugins are scanned at startup. Their skills appear in the Telegram `/` command menu alongside built-in commands. Skills with Korean translations show localized descriptions. For example: -| Command | Description | -| -------------------------- | ------------------------------ | +| Command | Description | +| -------------------------- | ------------------------------------ | | `/brainstorming` | ↗ 브레인스토밍 — 기능 설계 전 아이디어 구체화 | -| `/systematic_debugging` | ↗ 체계적 디버깅 | -| `/writing_plans` | ↗ 구현 계획 작성 | -| `/test_driven_development` | ↗ TDD — 테스트 주도 개발 | -| `/skill_debug` | ↗ Octo 디버깅 | -| ... | (all installed plugin skills) | +| `/systematic_debugging` | ↗ 체계적 디버깅 | +| `/writing_plans` | ↗ 구현 계획 작성 | +| `/test_driven_development` | ↗ TDD — 테스트 주도 개발 | +| `/skill_debug` | ↗ Octo 디버깅 | +| ... | (all installed plugin skills) | -Use `/favorite` to pin your most-used skills to the top of the menu. +Use `/favorite` to pin your most-used skills to the top of the menu. Per-project usage counts surface the rest by frequency. ### Topic Workflow @@ -203,6 +226,16 @@ Use `/favorite` to pin your most-used skills to the top of the menu. Once a topic is bound to a session, just send text or voice messages in that topic — text gets forwarded to Claude Code via tmux keystrokes, and voice messages are automatically transcribed and forwarded as text. +**Codex / OMX windows (fork):** + +Windows named `codex` or `codex-*` are routed to OMX in `direct` mode. Set this in your launcher (e.g. ccbot's bootstrap script): + +```bash +OMX_LAUNCH_POLICY=direct omx +``` + +Status updates from Codex (`Working`, `Ran`, `Read`, `Edit`, etc.) flow through the same Telegram pipeline as Claude. The `ccbot-bridge.mjs` OMX hook plugin (at `~/Documents/Claude/.omx/hooks/`) emits assistant responses back to the topic via `ccbot send`. + **Killing a session:** Close (or delete) the topic in Telegram. The associated tmux window is automatically killed and the binding is removed. @@ -231,7 +264,7 @@ The monitor polls session JSONL files every 2 seconds and sends notifications fo - **Assistant responses** — Claude's text replies - **Thinking content** — Shown as expandable blockquotes -- **Tool use/result** — Summarized with stats (e.g. "Read 42 lines", "Found 5 matches") +- **Tool use/result** — Summarized with stats (e.g. "Read 42 lines", "Found 5 matches"); on this fork, repeated tool-use events within `CCBOT_BATCH_WINDOW` collapse into `⚙️ 작업 중 N건` - **Local command output** — stdout from commands like `git status`, prefixed with `❯ command_name` Notifications are delivered to the topic bound to the session's window. @@ -261,13 +294,13 @@ The window must be in the `ccbot` tmux session (configurable via `TMUX_SESSION_N ## Data Storage -| Path | Description | -| ------------------------------- | ----------------------------------------------------------------------- | -| `$CCBOT_DIR/state.json` | Thread bindings, window states, display names, and per-user read offsets | -| `$CCBOT_DIR/session_map.json` | Hook-generated `{tmux_session:window_id: {session_id, cwd, window_name}}` mappings | -| `$CCBOT_DIR/monitor_state.json` | Monitor byte offsets per session (prevents duplicate notifications) | -| `$CCBOT_DIR/skill_state.json` | Skill favorites and per-project usage counts | -| `~/.claude/projects/` | Claude Code session data (read-only) | +| Path | Description | +| ------------------------------- | ------------------------------------------------------------------------------------------- | +| `$CCBOT_DIR/state.json` | Thread bindings, window states (incl. `provider`), display names, read offsets, **status_msg_ids** | +| `$CCBOT_DIR/session_map.json` | Hook-generated `{tmux_session:window_id: {session_id, cwd, window_name}}` mappings | +| `$CCBOT_DIR/monitor_state.json` | Monitor byte offsets per session (prevents duplicate notifications) | +| `$CCBOT_DIR/skill_state.json` | 🔱 Skill favorites and per-project usage counts | +| `~/.claude/projects/` | Claude Code session data (read-only) | ## File Structure @@ -276,34 +309,83 @@ src/ccbot/ ├── __init__.py # Package entry point ├── main.py # CLI dispatcher (hook subcommand + bot bootstrap) ├── hook.py # Hook subcommand for session tracking (+ --install) +├── send.py # 🔱 ccbot send subcommand (--session-id / --window) ├── config.py # Configuration from environment variables ├── bot.py # Telegram bot setup, command handlers, topic routing ├── session.py # Session management, state persistence, message history ├── session_monitor.py # JSONL file monitoring (polling + change detection) ├── monitor_state.py # Monitor state persistence (byte offsets) ├── transcript_parser.py # Claude Code JSONL transcript parsing -├── terminal_parser.py # Terminal pane parsing (interactive UI + status line) -├── html_converter.py # Markdown → Telegram HTML conversion + HTML-aware splitting +├── terminal_parser.py # Terminal pane parsing (interactive UI + status line + 🔱 Codex parser) +├── markdown_v2.py # Markdown → Telegram HTML conversion + HTML-aware splitting ├── screenshot.py # Terminal text → PNG image with ANSI color support ├── transcribe.py # Voice-to-text transcription via OpenAI API -├── skill_registry.py # Plugin skill discovery and Telegram command registration -├── message_batcher.py # Batch tool_use/thinking messages into summaries +├── skill_registry.py # 🔱 Plugin skill discovery and Telegram command registration +├── message_batcher.py # 🔱 Batch tool_use/thinking messages into summaries ├── utils.py # Shared utilities (atomic JSON writes, JSONL helpers) -├── tmux_manager.py # Tmux window management (list, create, send keys, kill) +├── tmux_manager.py # Tmux window management (incl. 🔱 paste-buffer path for Codex) ├── telegram_sender.py # Telegram message splitting (4096 char limit) ├── fonts/ # Bundled fonts for screenshot rendering └── handlers/ - ├── __init__.py # Handler module exports - ├── callback_data.py # Callback data constants (CB_* prefixes) - ├── directory_browser.py # Directory browser inline keyboard UI - ├── history.py # Message history pagination - ├── interactive_ui.py # Interactive UI handling (AskUser, ExitPlan, Permissions) - ├── message_queue.py # Per-user message queue + worker (merge, rate limit) - ├── message_sender.py # safe_reply / safe_edit / safe_send helpers - ├── response_builder.py # Response message building (format tool_use, thinking, etc.) - └── status_polling.py # Terminal status line polling + ├── __init__.py + ├── callback_data.py + ├── cleanup.py + ├── directory_browser.py + ├── history.py + ├── interactive_ui.py + ├── message_queue.py # Per-user queue + worker (🔱 DirectMessage, status convert) + ├── message_sender.py + ├── response_builder.py + └── status_polling.py # Terminal status polling (1s interval) ``` +🔱 = file or section added/extended in this fork. + +## Changelog (fork) + +Tracked here so internal contributors can see what changed relative to upstream `six-ddc/ccbot`. Most recent first. + +### 2026-05-08 + +- **PR [#5](https://github.com/TejNote/ccbot/pull/5) `fix:` parse_status_line ignores background-shell-only spinners** — Lines like `· Sautéed for 3s · 1 shell still running` (no `esc to interrupt` signal) are no longer enqueued as status updates, eliminating stale status messages that lingered on Telegram after a turn ended. +- **PR [#4](https://github.com/TejNote/ccbot/pull/4) `feat:` Codex topic routing** — `WindowState.provider`, `detect_window_provider`, paste-buffer send path, `parse_codex_status_line`, OMX `ccbot-bridge.mjs` hook, fixtures (`codex_thinking_trace.txt`), and tests (`TestWindowProvider`, `TestResolveRouting`, `TestParseCodexStatusLine`, `test_status_polling_codex.py`). +- **PR [#2](https://github.com/TejNote/ccbot/pull/2) `fix:` keep answer as last message** — Status display delegated to `status_polling`; content tasks no longer immediately re-enqueue a status update. Status message IDs are persisted in `state.json` and orphans cleaned on restart. + +### 2026-05-06 + +- `fix:` `/clear` no longer leaves a stale `session_map` entry — the next message correctly maps to the new session. +- `feat:` skill scanning includes `commands/` directories (e.g. `/octo:octo`, all CLI slash commands) and budgets descriptions to stay under Telegram's ~5000-char total command limit. +- `fix:` total bot-command count capped at 100 (Telegram API limit) with sane truncation. + +### 2026-04-28 + +- `feat:` route command/photo/voice acknowledgements through `enqueue_direct_message` so they don't interleave with Claude responses. +- `feat:` `DirectMessage` task type added to the queue; `enqueue_direct_message` for ordered delivery. + +### 2026-04-27 + +- `feat:` `SkillRegistry` integrated into the bot (`/favorite`, usage tracking, command menu). +- `feat:` `MessageBatcher` for timed grouping of tool-use / thinking events; `CCBOT_BATCH_WINDOW` config. +- `fix:` hook strips quotes from `.env` values and normalizes `TMUX_SESSION_NAME`. +- `fix:` `send_keys` checks Claude busy state before transmission to prevent silent drops. +- PR [#1](https://github.com/TejNote/ccbot/pull/1) `fix:` batch summary correctly traverses the message queue. + +### Pending upstream merges + +These commits are on `six-ddc/ccbot` `main` but not yet reconciled into this fork: + +| Upstream commit | Note | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------- | +| [`865ab89`](https://github.com/six-ddc/ccbot/commit/865ab89) (#67) | Fix interactive UI creating duplicate messages on button press | +| [`350c653`](https://github.com/six-ddc/ccbot/commit/350c653) (#73) | Stop renaming user-created Telegram topics on bind | +| [`f5ddd7f`](https://github.com/six-ddc/ccbot/commit/f5ddd7f) | Show correct line count for Write tool results | + +Plan: cherry-pick or merge in a follow-up PR. + +## Contributing back upstream + +Bug fixes that aren't fork-specific (e.g. anything not touching Codex routing, the skill menu, or the `ccbot send` subcommand) are welcome upstream — open the PR against [`six-ddc/ccbot`](https://github.com/six-ddc/ccbot) directly. For fork-specific work, target this repository's `main`. + ## Contributors Thanks to all the people who contribute! We encourage using Claude Code to collaborate on contributions.