Skill: zlhad/cc-bridge (version 0.2.1)

--- name: claude-code-bridge description: > Bridges OpenClaw (QQ, Telegram, WeChat, and other messaging channels) to a persistent Claude Code CLI session running in a background tmux process. Enables starting, stopping, restarting, and monitoring Claude Code sessions directly from any chat interface. Automatically detects session state on every message, routes user input to the active Claude Code session, and handles tool-approval prompts so the user can approve or deny Claude Code actions without leaving their chat app. Ideal for developers who want to control Claude Code from a mobile device or a group chat. Trigger phrases: "start claude code", "open claude code", "cc status", "stop claude code", "restart cc", "启动claude code", "开启claude code", "连接cc", "cc状态", "关闭cc", "退出cc", "重启cc", "/cc start", "/cc stop", "/cc restart", "/cc status". version: 0.2.1 --- # Claude Code Bridge Bridge every incoming message to a live, persistent `claude` CLI process running in a background tmux session. The user interacts via QQ/Telegram/any channel; Claude Code responds as if they were typing in a real terminal. ## Session State Detection (CRITICAL — CHECK EVERY TURN) **At the start of EVERY incoming message**, determine session state: ```bash ~/.openclaw/workspace/skills/cc-bridge/scripts/cc-bridge.sh "<SESSION_ID>" status ``` - `✅ Claude Code 会话运行中` → **CC mode active**, route message to CC - `⭕ 没有活跃` → **CC mode off**, respond normally - `⚠️ CC 正在等待审批` → tell user and await their approval choice Construct `<SESSION_ID>` as `<channel>_<chat_id>` using only `[a-zA-Z0-9_]`. ## Routing Logic ``` Every incoming message: 1. Run status check 2. Is it a CC control command? "启动cc" → start "关闭cc" → stop "重启cc" → restart "cc状态" → status "/cc peek" → peek "/cc history [N]" → history 3. Is CC in approval-waiting state? YES → parse user's intent (y/n/1/2/3) → approve 4. Is CC session active? YES → forward as send NO → respond normally as OpenClaw agent ``` ## Executing Actions ```bash SCRIPT="$HOME/.openclaw/workspace/skills/cc-bridge/scripts/cc-bridge.sh" "$SCRIPT" "<ID>" start # 启动 "$SCRIPT" "<ID>" send '<message>' # 发送（90s 超时） "$SCRIPT" "<ID>" send '<message>' --long # 长任务（5min 超时） "$SCRIPT" "<ID>" approve 1 # 审批：选 Yes "$SCRIPT" "<ID>" approve 2 # 审批：选 Allow always "$SCRIPT" "<ID>" approve 3 # 审批：选 No "$SCRIPT" "<ID>" approve esc # 审批：取消 "$SCRIPT" "<ID>" stop # 停止 "$SCRIPT" "<ID>" restart # 重启 "$SCRIPT" "<ID>" status # 状态 "$SCRIPT" "<ID>" peek # 原始终端画面 "$SCRIPT" "<ID>" history 200 # 最近 200 行历史 ``` **IMPORTANT — message quoting**: Use `tmux send-keys -l` (literal mode) so special characters (`$`, `!`, `\`) are sent verbatim. The script handles this internally; just pass the raw message as argument 3. ## CC Slash Commands — Direct Passthrough CC's own slash commands work by sending them via `send`: | User says | Forward as | |-----------|-----------| | `/plan` | `send '/plan'` | | `/model sonnet` | `send '/model sonnet'` | | `/compact` | `send '/compact'` | | `/help` | `send '/help'` | | `/cost` | `send '/cost'` | | `/clear` | `send '/clear'` | | `/diff` | `send '/diff'` | | `/fast` | `send '/fast'` | | `/vim` | `send '/vim'` | | `/context` | `send '/context'` | | `/export` | `send '/export'` | | `/copy` | `send '/copy'` | | `/rewind` | `send '/rewind'` | | `/fork` | `send '/fork'` | | `/permissions` | `send '/permissions'` | | `/tasks` | `send '/tasks'` | | `/status` | `send '/status'` | | `/stats` | `send '/stats'` | | `/review` | `send '/review'` | | `/theme` | `send '/theme'` | | `/sandbox` | `send '/sandbox'` | | Any `/xxx` command | `send '/xxx'` | These are NOT OpenClaw commands — forward them verbatim to CC. All CC slash commands work via passthrough, including ones not listed above. ## Handling CC Approval Prompts When CC encounters a tool it needs permission to run, it shows a TUI selection menu (arrow-key navigation, not text input). The `approve` action handles this: ``` CC shows: Do you want to proceed? ❯ 1. Yes 2. Yes, allow from this project 3. No ``` The user's reply should be interpreted: - "y" / "是" / "好" / "1" / "同意" → `approve 1` - "2" / "允许" / "一直允许" → `approve 2` - "n" / "否" / "不" / "3" / "拒绝" → `approve 3` - "取消" / "cancel" → `approve esc` ## Long Tasks & Streaming For tasks that take a long time (refactoring, writing large codebases): 1. Detect intent: if the user's message implies a large task (e.g. "重构整个项目", "帮我写一个完整的 XXX"), use `--long` flag (5-minute timeout) 2. If the output is empty after timeout, use `peek` to check CC's current state 3. If CC is still working, inform the user: `⏳ CC 仍在处理中，稍后我再检查` 4. Then use `peek` or `history` to get progress updates ## Formatting Replies 1. Prefix CC output with `🤖 **CC →**` 2. Keep code blocks, file paths, and tool output intact 3. If output is empty: reply `⏳ CC 正在处理中...` then try `peek` after 3s 4. If output >3000 chars: show the last 2000 chars, note earlier output available via `/cc history` 5. If CC is waiting for approval: clearly show the options to the user ## Error Handling | Situation | Action | |-----------|--------| | `send` returns empty | Wait 3s, run `peek`, relay result | | Session not found | Inform user, offer to `start` | | Session crashed | Detect via `status`, offer `restart` | | CC shows error | Relay error verbatim | | Timeout on long task | Inform user, suggest `peek` or `history` | ## Additional Resources - **`scripts/cc-bridge.sh`** — Full session management (start/send/approve/stop/restart/status/peek/history) - **`references/usage.md`** — User-facing help text and example conversations

path: README.md
content: # CC-Bridge — OpenClaw ↔ Claude Code CLI Bridge

[中文](#中文文档) | [English](#english-documentation)

---

## 中文文档

### 简介

**CC-Bridge** 是一个 OpenClaw Skill，让你通过 QQ、Telegram 等任意聊天渠道，远程操作一个**真正的、交互式的 Claude Code 终端会话**。

不是简单的 API 调用或任务委派——而是像你坐在电脑前打开终端输入 `claude` 一样的完整体验。

### 工作原理

```
你的手机 (QQ/Telegram/...)
     │
     ▼
  OpenClaw Agent (接收消息)
     │
     ▼
  CC-Bridge Skill (tmux 桥接)
     │
     ▼
  Claude Code CLI (真实终端会话)
     │
     ▼
  CC-Bridge (捕获输出 → 返回给你)
```

核心技术：
- **tmux** 维持持久化终端会话（关掉手机对话也不会断）
- **pipe-pane** 实时监控输出变化
- **capture-pane** 获取干净的渲染文本（无 ANSI 转义码）
- **send-keys** 模拟键盘输入（包括方向键导航审批菜单）

### 前置条件

| 依赖 | 说明 |
|------|------|
| [OpenClaw](https://github.com/nicepkg/openclaw) | v2026.3.0+ |
| [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) | 已安装并登录（`claude` 命令可用） |
| tmux | macOS: `brew install tmux`，Linux: `apt install tmux` |
| bash | 4.0+（macOS 自带 zsh 可用，脚本指定 bash） |

### 安装

将 `cc-bridge` 目录放到 OpenClaw 的 skills 目录下：

```bash
# 方式 1：直接克隆
git clone https://github.com/your-username/cc-bridge.git \
  ~/.openclaw/workspace/skills/cc-bridge

# 方式 2：手动复制
cp -r cc-bridge ~/.openclaw/workspace/skills/
```

验证安装：
```bash
openclaw skills list
# 应该看到: ✓ ready │ 📦 cc-bridge
```

### 快速开始

| 你发送 | 效果 |
|--------|------|
| `启动cc` | 启动 Claude Code 会话 |
| 任意消息 | 转发给 Claude Code |
| `y` / `1` / `是` | 同意 CC 的工具审批 |
| `n` / `3` / `否` | 拒绝 CC 的工具审批 |
| `/plan` | 切换 CC 到 Plan 模式 |
| `/model sonnet` | 切换 CC 的模型 |
| `关闭cc` | 关闭会话 |

### 完整命令列表

| 命令 | 说明 |
|------|------|
| `启动cc` / `开启cc` / `/cc start` | 启动新的 CC 会话 |
| `关闭cc` / `退出cc` / `/cc stop` | 关闭会话 |
| `重启cc` / `/cc restart` | 重启会话 |
| `cc状态` / `/cc status` | 查看会话状态 |
| `/cc peek` | 查看终端原始画面（最近50行） |
| `/cc history [N]` | 查看最近 N 行对话历史 |
| `/plan`、`/model`、`/compact` 等 | CC 自身的斜杠命令，直接透传 |

### 审批操作

当 CC 需要执行命令、读写文件等需要权限的操作时，会弹出审批菜单。你只需回复：

| 你的回复 | 含义 |
|----------|------|
| `y` / `是` / `好` / `1` / `同意` | 同意（仅本次） |
| `2` / `允许` / `一直允许` | 允许（本项目永久） |
| `n` / `否` / `不` / `3` / `拒绝` | 拒绝 |
| `取消` / `cancel` | 取消操作 |

### 长任务处理

对于大型任务（如 "重构整个项目"），bridge 会自动使用 5 分钟超时。如果超时后 CC 仍在工作：

1. 发 `/cc peek` 查看当前进度
2. 发 `/cc history 200` 查看完整历史
3. CC 完成后再发消息会获取新的输出

### 功能覆盖对比

| Claude Code 原生功能 | CC-Bridge 支持 | 说明 |
|---------------------|:--------------:|------|
| 发送消息/提问 | ✅ | 完整支持 |
| 代码编辑/生成 | ✅ | 完整支持 |
| 文件读写 | ✅ | 通过审批后完整支持 |
| 命令行执行 | ✅ | 通过审批后完整支持 |
| 工具审批 (Yes/No/Allow) | ✅ | 方向键模拟 TUI 菜单 |
| 所有斜杠命令 (`/plan` `/model` `/diff` `/vim` ...) | ✅ | 全部透传 |
| 权限模式切换 (Shift+Tab) | ✅ | `key shift+tab` |
| 模型快速切换 (Alt+P) | ✅ | `key alt+p` |
| 扩展思考切换 (Alt+T) | ✅ | `key alt+t` |
| Ctrl+C 中断 | ✅ | `interrupt` 命令 |
| Ctrl+B 后台任务 | ✅ | `key ctrl+b` |
| Ctrl+L 清屏 | ✅ | `key ctrl+l` |
| Esc+Esc 回退/检查点 | ✅ | `key esc+esc` |
| 方向键/Tab/Enter/Space | ✅ | `key` 命令 |
| 多文件编辑 | ✅ | CC 原生能力 |
| Git 操作 | ✅ | CC 原生能力 |
| MCP 服务器 | ✅ | CC 自身配置 |
| Bash 模式 (`!` 前缀) | ✅ | 通过 `send` 直接发送 |
| 实时流式输出 | ⚠️ | 批量轮询（~2.4s 延迟），非逐字流式 |
| 多行输入 | ⚠️ | 可用 `\` + Enter，但无 Shift+Enter |
| `@` 文件引用补全 | ❌ | 无法触发终端内补全菜单 |
| 图片粘贴/查看 | ❌ | 文本通道限制 |
| 视觉 diff 渲染/颜色 | ❌ | 纯文本输出，无颜色高亮 |

### 技术细节

- tmux 会话名：`ccb_<session_id>`（自动清理特殊字符）
- 滚动缓冲区：50,000 行（长对话不丢内容）
- 输出检测：日志文件大小连续稳定 3 次（间隔 0.8s）视为完成
- 默认超时：90 秒，`--long` 模式：300 秒
- Claude Code 二进制：`$CLAUDE_BIN`（默认 `~/.local/bin/claude`）

### 常见问题

**Q: 需要 Anthropic API Key 吗？**
A: 不需要。CC-Bridge 使用你本地已登录的 Claude Code CLI，走 OAuth 认证。

**Q: 可以同时开多个会话吗？**
A: 可以。每个 OpenClaw 渠道（QQ 私聊/群/Telegram 对话）各自独立。

**Q: CC 断了怎么办？**
A: 发 `重启cc` 即可。

**Q: 支持 Windows 吗？**
A: 目前仅支持 macOS 和 Linux（依赖 tmux）。Windows 需要 WSL。

---

## English Documentation

### Overview

**CC-Bridge** is an OpenClaw Skill that lets you operate a **real, interactive Claude Code terminal session** remotely through any chat channel — QQ, Telegram, Discord, etc.

This is not a simple API call or task delegation — it's the full Claude Code CLI experience, as if you were sitting at your computer typing `claude` in a terminal.

### How It Works

```
Your Phone (QQ/Telegram/...)
     │
     ▼
  OpenClaw Agent (receives message)
     │
     ▼
  CC-Bridge Skill (tmux bridge)
     │
     ▼
  Claude Code CLI (real terminal session)
     │
     ▼
  CC-Bridge (captures output → sends back to you)
```

Core technology:
- **tmux** maintains persistent terminal sessions (survives disconnects)
- **pipe-pane** monitors output changes in real-time
- **capture-pane** captures clean rendered text (no ANSI escape codes)
- **send-keys** simulates keyboard input (including arrow-key navigation for approval menus)

### Prerequisites

| Dependency | Notes |
|-----------|-------|
| [OpenClaw](https://github.com/nicepkg/openclaw) | v2026.3.0+ |
| [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) | Installed and logged in (`claude` command available) |
| tmux | macOS: `brew install tmux`, Linux: `apt install tmux` |
| bash | 4.0+ |

### Installation

Place the `cc-bridge` directory in OpenClaw's skills directory:

```bash
# Option 1: Clone directly
git clone https://github.com/your-username/cc-bridge.git \
  ~/.openclaw/workspace/skills/cc-bridge

# Option 2: Manual copy
cp -r cc-bridge ~/.openclaw/workspace/skills/
```

Verify installation:
```bash
openclaw skills list
# Should show: ✓ ready │ 📦 cc-bridge
```

### Quick Start

| You Send | Effect |
|----------|--------|
| `启动cc` (Start CC) | Launch Claude Code session |
| Any message | Forwarded to Claude Code |
| `y` / `1` | Approve CC's tool request |
| `n` / `3` | Deny CC's tool request |
| `/plan` | Switch CC to Plan mode |
| `/model sonnet` | Switch CC's model |
| `关闭cc` (Stop CC) | Close session |

### Full Command Reference

| Command | Description |
|---------|-------------|
| `启动cc` / `/cc start` | Start a new CC session |
| `关闭cc` / `/cc stop` | Stop the session |
| `重启cc` / `/cc restart` | Restart the session |
| `cc状态` / `/cc status` | Check session status |
| `/cc peek` | View raw terminal screen (last 50 lines) |
| `/cc history [N]` | View last N lines of conversation history |
| `/plan`, `/model`, `/compact`, etc. | CC's own slash commands, passed through directly |

### Approval Handling

When CC needs permission to execute commands, read/write files, etc., an approval menu appears. Simply reply:

| Your Reply | Meaning |
|-----------|---------|
| `y` / `1` / `yes` | Approve (this time only) |
| `2` | Allow (permanently for this project) |
| `n` / `3` / `no` | Deny |
| `cancel` / `esc` | Cancel |

### Long Task Handling

For large tasks (e.g., "refactor the entire project"), the bridge automatically uses a 5-minute timeout. If CC is still working after timeout:

1. Send `/cc peek` to check current progress
2. Send `/cc history 200` to view full history
3. Send another message after CC finishes to get new output

### Feature Coverage

| Claude Code Native Feature | CC-Bridge Support | Notes |
|---------------------------|:-----------------:|-------|
| Send messages / ask questions | ✅ | Full support |
| Code editing / generation | ✅ | Full support |
| File read/write | ✅ | Full support after approval |
| Command execution | ✅ | Full support after approval |
| Tool approval (Yes/No/Allow) | ✅ | Arrow-key TUI menu simulation |
| All slash commands (`/plan` `/model` `/diff` `/vim` ...) | ✅ | Full passthrough |
| Permission mode cycling (Shift+Tab) | ✅ | `key shift+tab` |
| Quick model switch (Alt+P) | ✅ | `key alt+p` |
| Extended thinking toggle (Alt+T) | ✅ | `key alt+t` |
| Ctrl+C interrupt | ✅ | `interrupt` command |
| Ctrl+B background tasks | ✅ | `key ctrl+b` |
| Ctrl+L clear screen | ✅ | `key ctrl+l` |
| Esc+Esc rewind/checkpoint | ✅ | `key esc+esc` |
| Arrow keys / Tab / Enter / Space | ✅ | `key` command |
| Multi-file editing | ✅ | CC native capability |
| Git operations | ✅ | CC native capability |
| MCP servers | ✅ | CC's own config |
| Bash mode (`!` prefix) | ✅ | Send directly via `send` |
| Real-time streaming output | ⚠️ | Batch polling (~2.4s delay), not character-by-character |
| Multi-line input | ⚠️ | `\` + Enter works, no Shift+Enter |
| `@` file mention autocomplete | ❌ | Cannot trigger terminal autocomplete menu |
| Image paste / viewing | ❌ | Text channel limitation |
| Visual diff rendering / colors | ❌ | Plain text output, no color highlighting |

### Technical Details

- tmux session name: `ccb_<session_id>` (special characters auto-sanitized)
- Scrollback buffer: 50,000 lines (no content loss in long sessions)
- Output detection: log file size stable for 3 consecutive checks (0.8s interval)
- Default timeout: 90s, `--long` mode: 300s
- Claude Code binary: `$CLAUDE_BIN` (defaults to `~/.local/bin/claude`)

### FAQ

**Q: Do I need an Anthropic API Key?**
A: No. CC-Bridge uses your locally logged-in Claude Code CLI with OAuth authentication.

**Q: Can I run multiple sessions?**
A: Yes. Each OpenClaw channel (QQ DM/group, Telegram chat) is independent.

**Q: What if CC crashes?**
A: Send `重启cc` (or restart via `/cc restart`).

**Q: Windows support?**
A: Currently macOS and Linux only (requires tmux). Windows users need WSL.

---

## Project Structure

```
cc-bridge/
├── SKILL.md              # Skill metadata & agent instructions
├── LICENSE               # MIT License
├── README.md             # This file
├── scripts/
│   └── cc-bridge.sh      # Core bridge script (start/send/approve/stop/...)
├── references/
│   └── usage.md          # User-facing quick reference (Chinese)
└── docs/                 # Additional documentation
```

## License

MIT License — see [LICENSE](./LICENSE) for details.


path: references/usage.md
content: # CC-Bridge 使用手册

## 快速开始

| 用户说 | 效果 |
|--------|------|
| `启动cc` / `开启cc` | 启动 Claude Code 会话，进入 CC 模式 |
| `关闭cc` / `退出cc` | 关闭会话，回到普通 OpenClaw 模式 |
| `重启cc` | 重启 Claude Code 会话 |
| `cc状态` | 查看当前会话是否活跃 |
| `/cc peek` | 查看 CC 终端原始画面 |
| `/cc history 100` | 查看最近 100 行对话历史 |
| 其他任何消息（CC 模式中）| 直接转发给 Claude Code |

## CC 斜杠命令

CC 模式下，CC 自身的斜杠命令直接透传：

| 命令 | 作用 |
|------|------|
| `/plan` | 切换到 Plan 模式 |
| `/model sonnet` | 切换模型 |
| `/compact` | 压缩上下文 |
| `/cost` | 查看 Token 用量 |
| `/help` | 查看帮助 |
| `/clear` | 清空上下文 |

## 审批操作

当 CC 需要审批时（执行命令、读写文件等），会出现选择菜单：

```
Do you want to proceed?
❯ 1. Yes
  2. Yes, allow from this project
  3. No
```

直接回复：
- `y` / `是` / `好` / `1` → 同意
- `2` / `一直允许` → 允许该项目
- `n` / `否` / `3` → 拒绝
- `取消` / `cancel` → 取消

## 长任务处理

发送大任务时（如"重构整个项目"），bridge 会自动等待最多 5 分钟。
如果还没完成，会提示你可以发 `/cc peek` 查看进度。

## 典型对话示例

```
用户: 启动cc

🤖 CC →
✅ Claude Code 会话已启动！
💬 现在发消息就会直接转发给 Claude Code
🛑 发送「关闭cc」退出会话

---

用户: 帮我看看 ~/project 目录有什么文件

🤖 CC →
Bash(ls ~/project)
⎿ README.md  src/  tests/  package.json
项目包含 4 个主要文件/目录...

---

CC 请求审批:
🤖 CC → CC 需要执行命令，是否允许？
  1. Yes
  2. Yes, allow from this project
  3. No

用户: 1

🤖 CC →
[执行结果...]

---

用户: /plan

🤖 CC → [切换到 Plan 模式]

---

用户: /model sonnet

🤖 CC → [模型已切换为 sonnet]

---

用户: 关闭cc

✅ Claude Code 会话已关闭，回到正常模式
```

## 技术细节

- CC 进程运行在 tmux 会话中（名称: `ccb_<session_id>`）
- tmux 滚动缓冲区设为 50000 行，长会话不丢内容
- 输出检测：日志文件大小连续 3 次稳定（每次间隔 0.8s）即视为完成
- 新内容获取：基于 `tmux capture-pane -S -`（全缓冲区）的行号偏移 diff
- 默认超时 90 秒，长任务 `--long` 模式 300 秒
- 审批操作使用 tmux 方向键模拟 TUI 选择（非文本输入）

## 常见问题

**Q: CC 需要审批工具调用时怎么办？**
A: Bridge 会检测审批状态，把选项发给你。直接回复 1/2/3 或 y/n 即可。

**Q: CC 的回复太长怎么办？**
A: 超过 3000 字符会自动截取尾部。发 `/cc history 500` 查看完整历史。

**Q: 会话意外断开怎么办？**
A: 发送 `重启cc` 重新启动。

**Q: 可以同时开多个 CC 会话吗？**
A: 每个 OpenClaw 对话（QQ 私聊/群/Telegram）各自独立，互不影响。

**Q: CC 模式下能用 OpenClaw 的其他功能吗？**
A: 不能，CC 模式下所有消息都转发给 CC。发 `关闭cc` 退出后恢复正常模式。

**Q: 怎么切换 CC 的模型或 Plan 模式？**
A: 直接发 CC 的斜杠命令如 `/model sonnet`、`/plan` 即可，会原样透传给 CC。


path: scripts/cc-bridge.sh
content: #!/usr/bin/env bash
# cc-bridge.sh — OpenClaw ↔ Claude Code CLI 桥接脚本
#
# Usage:
#   cc-bridge.sh <session_id> start                    # 启动新 CC 会话
#   cc-bridge.sh <session_id> send "<message>"         # 发送消息并等待回复
#   cc-bridge.sh <session_id> send "<message>" --long   # 长任务模式 (5分钟超时)
#   cc-bridge.sh <session_id> approve [1|2|3|y|n]      # 回应 CC 的审批提示
#   cc-bridge.sh <session_id> stop                     # 关闭会话
#   cc-bridge.sh <session_id> restart                  # 重启会话
#   cc-bridge.sh <session_id> status                   # 查看状态
#   cc-bridge.sh <session_id> peek                     # 查看当前屏幕内容
#   cc-bridge.sh <session_id> history [N]              # 查看最近 N 行滚动缓冲区
#   cc-bridge.sh <session_id> interrupt                # 发送 Ctrl+C 中断当前操作
#   cc-bridge.sh <session_id> key "<key>"              # 发送特殊按键 (Escape/Up/Down/Tab)

set -euo pipefail

SESSION_ID="${1:?[cc-bridge] 缺少 session_id 参数}"
ACTION="${2:?[cc-bridge] 缺少 action 参数 (start|send|approve|stop|restart|status|peek|history)}"
MESSAGE="${3:-}"
FLAG="${4:-}"

# ── 常量 ─────────────────────────────────────────────────────────────────────
CLAUDE_BIN="${CLAUDE_BIN:-$HOME/.local/bin/claude}"
STATE_DIR="$HOME/.openclaw/cc-bridge"
SCROLLBACK_LINES=50000           # tmux 滚动缓冲区大小
STABLE_INTERVAL=0.8              # 轮询间隔(秒)
STABLE_NEEDED=3                  # 连续稳定次数
DEFAULT_TIMEOUT=90               # 默认最长等待(秒)
LONG_TIMEOUT=300                 # 长任务最长等待(秒)
mkdir -p "$STATE_DIR"

# 清理可能残留的 stale tmux socket（macOS 常见问题）
_tmux_socket="/private/tmp/tmux-$(id -u)/default"
if [[ -S "$_tmux_socket" ]] && ! tmux list-sessions &>/dev/null; then
    rm -f "$_tmux_socket"
fi

# 把任意 session_id 变成合法 tmux 名（字母数字下划线，<=20字符）
SAFE_ID="$(echo "$SESSION_ID" | tr -cd '[:alnum:]_' | cut -c1-20)"
TMUX_NAME="ccb_${SAFE_ID}"
LOG_FILE="$STATE_DIR/${TMUX_NAME}.log"
OFFSET_FILE="$STATE_DIR/${TMUX_NAME}.offset"

# ── 工具函数 ──────────────────────────────────────────────────────────────────

is_running() {
    tmux has-session -t "$TMUX_NAME" 2>/dev/null
}

current_log_size() {
    wc -c < "$LOG_FILE" 2>/dev/null | tr -d ' ' || echo 0
}

capture_full() {
    # 抓取完整滚动缓冲区（不只是可见区域），-S - 表示从头开始
    tmux capture-pane -t "$TMUX_NAME" -p -S - 2>/dev/null || true
}

capture_clean() {
    # 抓取并清理：去掉装饰性行，保留实际内容
    capture_full \
        | grep -v '^[─━═─▪]\{3,\}' \
        | grep -v '^\s*[?]\s*for\s*shortcuts' \
        | grep -v '^\s*esc\s*to\s*\(interrupt\|cancel\)' \
        | grep -v '^\s*Tab\s*to\s*amend' \
        | grep -v '^\s*ctrl+e\s*to\s*explain' \
        | grep -v '^[[:space:]]*$' \
        || true
}

read_new_output() {
    # 使用行号偏移（而非 sort+comm）保持输出顺序
    local offset=0
    [[ -f "$OFFSET_FILE" ]] && offset=$(cat "$OFFSET_FILE")

    local full
    full=$(capture_clean)
    local total_lines
    total_lines=$(echo "$full" | wc -l | tr -d ' ')

    if (( total_lines > offset )); then
        # 输出从 offset+1 行开始的新内容
        echo "$full" | tail -n "+$((offset + 1))" | head -500
        echo "$total_lines" > "$OFFSET_FILE"
    fi
}

save_current_offset() {
    local total
    total=$(capture_clean | wc -l | tr -d ' ')
    echo "$total" > "$OFFSET_FILE"
}

wait_for_stable_output() {
    # 轮询直到日志大小连续 N 次不再增长（CC 回复完毕）
    local max_wait="${1:-$DEFAULT_TIMEOUT}"

    local prev_size
    prev_size=$(current_log_size)
    local stable_count=0
    local iterations
    iterations=$(awk "BEGIN{printf \"%d\", $max_wait / $STABLE_INTERVAL}")

    local i
    for (( i=0; i<iterations; i++ )); do
        sleep "$STABLE_INTERVAL"
        local curr_size
        curr_size=$(current_log_size)

        if (( curr_size == prev_size )); then
            stable_count=$((stable_count + 1))
            if (( stable_count >= STABLE_NEEDED )); then
                break
            fi
        else
            stable_count=0
            prev_size="$curr_size"
        fi
    done
}

# ── 动作实现 ──────────────────────────────────────────────────────────────────

do_start() {
    if is_running; then
        echo "[cc-bridge] ⚠️  已有活跃会话 ($TMUX_NAME)，如需重开请先 stop 或 restart"
        return 0
    fi

    # 清理旧日志
    > "$LOG_FILE"
    echo "0" > "$OFFSET_FILE"

    echo "[cc-bridge] 🚀 正在启动 Claude Code..."

    # 构建启动命令：
    #   - unset CLAUDECODE/CLAUDE_CODE  防止 "nested session" 错误
    #   - export TERM                    保证 CC 可以正常渲染
    local launch_cmd
    launch_cmd="unset CLAUDECODE CLAUDE_CODE; export TERM=xterm-256color; exec '$CLAUDE_BIN'"

    # 在 tmux 中启动 claude
    #   - set history-limit 为大滚动缓冲区，确保长会话不丢内容
    tmux new-session -d -s "$TMUX_NAME" -x 220 -y 50 "bash --login -c '$launch_cmd'"
    tmux set-option -t "$TMUX_NAME" history-limit "$SCROLLBACK_LINES" 2>/dev/null || true
    sleep 0.5

    # 通过 pipe-pane 把所有输出追加到日志（用于 wait_for_stable_output 的 size 检测）
    tmux pipe-pane -t "$TMUX_NAME" -o "cat >> '$LOG_FILE'"

    # 等待 CC 初始化完成
    wait_for_stable_output 15

    # 记录初始偏移
    save_current_offset

    echo ""
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "✅ Claude Code 会话已启动！"
    echo "📌 会话 ID: $SESSION_ID"
    echo "💬 现在发消息就会直接转发给 Claude Code"
    echo "🔧 CC 斜杠命令也可直接发送：/plan /model /compact 等"
    echo "🛑 发送「关闭cc」退出会话"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
}

do_send() {
    if ! is_running; then
        echo "[cc-bridge] ❌ 没有活跃的 CC 会话，请先发送「启动cc」"
        return 1
    fi

    if [[ -z "$MESSAGE" ]]; then
        echo "[cc-bridge] ⚠️  消息为空"
        return 1
    fi

    # 记录发送前的行偏移
    save_current_offset

    # 将消息注入 tmux pane（逐字发送避免特殊字符被 tmux 拦截）
    tmux send-keys -t "$TMUX_NAME" -l "$MESSAGE"
    tmux send-keys -t "$TMUX_NAME" Enter

    # 选择超时：--long 标志 → 5分钟，否则 90秒
    local timeout="$DEFAULT_TIMEOUT"
    if [[ "$FLAG" == "--long" ]]; then
        timeout="$LONG_TIMEOUT"
    fi

    # 等待 CC 响应完毕
    wait_for_stable_output "$timeout"

    # 返回新增的输出
    read_new_output
}

do_approve() {
    # 处理 CC 的 TUI 审批菜单（上下选择 + Enter 确认）
    if ! is_running; then
        echo "[cc-bridge] ❌ 没有活跃的 CC 会话"
        return 1
    fi

    save_current_offset

    local choice="${MESSAGE:-y}"

    case "$choice" in
        1|y|yes|是)
            # 选项 1 (Yes)：直接按 Enter（默认已选中第一项）
            tmux send-keys -t "$TMUX_NAME" Enter
            ;;
        2)
            # 选项 2：下移一行 → Enter
            tmux send-keys -t "$TMUX_NAME" Down
            sleep 0.2
            tmux send-keys -t "$TMUX_NAME" Enter
            ;;
        3|n|no|否)
            # 选项 3 (No)：下移两行 → Enter
            tmux send-keys -t "$TMUX_NAME" Down
            sleep 0.2
            tmux send-keys -t "$TMUX_NAME" Down
            sleep 0.2
            tmux send-keys -t "$TMUX_NAME" Enter
            ;;
        esc|cancel|取消)
            # 按 Escape 取消
            tmux send-keys -t "$TMUX_NAME" Escape
            ;;
        *)
            echo "[cc-bridge] ⚠️  未知审批选项: $choice"
            echo "可用: 1/y/yes/是, 2, 3/n/no/否, esc/cancel/取消"
            return 1
            ;;
    esac

    # 等待 CC 处理审批后的响应
    wait_for_stable_output "$DEFAULT_TIMEOUT"
    read_new_output
}

do_stop() {
    if is_running; then
        # 先尝试优雅退出
        tmux send-keys -t "$TMUX_NAME" "/exit" Enter 2>/dev/null || true
        sleep 2
        # 如果还在就强杀
        tmux kill-session -t "$TMUX_NAME" 2>/dev/null || true
    fi
    rm -f "$LOG_FILE" "$OFFSET_FILE"
    echo "✅ Claude Code 会话已关闭"
}

do_restart() {
    echo "[cc-bridge] 🔄 正在重启..."
    do_stop
    sleep 1
    do_start
}

do_status() {
    if is_running; then
        local log_size
        log_size=$(current_log_size)
        local scroll_lines
        scroll_lines=$(capture_full | wc -l | tr -d ' ')
        echo "✅ Claude Code 会话运行中"
        echo "   tmux 会话: $TMUX_NAME"
        echo "   滚动缓冲: ${scroll_lines} 行"
        echo "   日志大小: ${log_size} bytes"

        # 检测 CC 是否在等待审批
        local screen
        screen=$(capture_full | tail -20)
        if echo "$screen" | grep -q "Do you want to proceed"; then
            echo "   ⚠️  CC 正在等待审批！发送 approve 来回应"
        fi
    else
        echo "⭕ 没有活跃的 Claude Code 会话"
        echo "   发送「启动cc」开始使用"
    fi
}

do_peek() {
    if ! is_running; then
        echo "[cc-bridge] 没有活跃会话"
        return 1
    fi
    capture_full | tail -50
}

do_history() {
    if ! is_running; then
        echo "[cc-bridge] 没有活跃会话"
        return 1
    fi
    local lines="${MESSAGE:-100}"
    capture_clean | tail -n "$lines"
}

do_interrupt() {
    if ! is_running; then
        echo "[cc-bridge] 没有活跃会话"
        return 1
    fi
    save_current_offset
    tmux send-keys -t "$TMUX_NAME" C-c
    sleep 1
    read_new_output
    echo "[cc-bridge] ⚡ 已发送中断信号 (Ctrl+C)"
}

do_key() {
    # 发送特殊按键：Escape, Up, Down, Tab, Enter, Space 等
    if ! is_running; then
        echo "[cc-bridge] 没有活跃会话"
        return 1
    fi

    local key="${MESSAGE:-Enter}"
    save_current_offset

    case "$key" in
        esc|escape|Escape)      tmux send-keys -t "$TMUX_NAME" Escape ;;
        up|Up)                  tmux send-keys -t "$TMUX_NAME" Up ;;
        down|Down)              tmux send-keys -t "$TMUX_NAME" Down ;;
        left|Left)              tmux send-keys -t "$TMUX_NAME" Left ;;
        right|Right)            tmux send-keys -t "$TMUX_NAME" Right ;;
        tab|Tab)                tmux send-keys -t "$TMUX_NAME" Tab ;;
        shift+tab|Shift+Tab)    tmux send-keys -t "$TMUX_NAME" BTab ;;
        enter|Enter)            tmux send-keys -t "$TMUX_NAME" Enter ;;
        space|Space)            tmux send-keys -t "$TMUX_NAME" Space ;;
        ctrl+c|Ctrl+C)          tmux send-keys -t "$TMUX_NAME" C-c ;;
        ctrl+d|Ctrl+D)          tmux send-keys -t "$TMUX_NAME" C-d ;;
        ctrl+b|Ctrl+B)          tmux send-keys -t "$TMUX_NAME" C-b C-b ;;
        ctrl+f|Ctrl+F)          tmux send-keys -t "$TMUX_NAME" C-f ;;
        ctrl+g|Ctrl+G)          tmux send-keys -t "$TMUX_NAME" C-g ;;
        ctrl+l|Ctrl+L)          tmux send-keys -t "$TMUX_NAME" C-l ;;
        ctrl+o|Ctrl+O)          tmux send-keys -t "$TMUX_NAME" C-o ;;
        ctrl+r|Ctrl+R)          tmux send-keys -t "$TMUX_NAME" C-r ;;
        ctrl+s|Ctrl+S)          tmux send-keys -t "$TMUX_NAME" C-s ;;
        ctrl+t|Ctrl+T)          tmux send-keys -t "$TMUX_NAME" C-t ;;
        ctrl+e|Ctrl+E)          tmux send-keys -t "$TMUX_NAME" C-e ;;
        alt+p|Alt+P)            tmux send-keys -t "$TMUX_NAME" M-p ;;
        alt+t|Alt+T)            tmux send-keys -t "$TMUX_NAME" M-t ;;
        alt+m|Alt+M)            tmux send-keys -t "$TMUX_NAME" M-m ;;
        esc+esc|Esc+Esc)        tmux send-keys -t "$TMUX_NAME" Escape; sleep 0.2; tmux send-keys -t "$TMUX_NAME" Escape ;;
        *)
            echo "[cc-bridge] ⚠️  未知按键: $key"
            echo "可用: esc, up, down, left, right, tab, shift+tab, enter, space"
            echo "      ctrl+c/d/b/f/g/l/o/r/s/t/e, alt+p/t/m, esc+esc"
            return 1
            ;;
    esac

    wait_for_stable_output 10
    read_new_output
}

# ── 入口 ──────────────────────────────────────────────────────────────────────

case "$ACTION" in
    start)      do_start     ;;
    send)       do_send      ;;
    approve)    do_approve   ;;
    stop)       do_stop      ;;
    restart)    do_restart   ;;
    status)     do_status    ;;
    peek)       do_peek      ;;
    history)    do_history   ;;
    interrupt)  do_interrupt ;;
    key)        do_key       ;;
    *)
        echo "[cc-bridge] ❌ 未知 action: $ACTION"
        echo "可用: start | send | approve | stop | restart | status | peek | history | interrupt | key"
        exit 1
        ;;
esac


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)