Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -453,13 +453,13 @@ Check distro name with `wsl -l -v` in PowerShell.

If `ccb`, `cask`, `cping` commands are not found after running `./install.sh install`:

**Cause:** The install directory (`~/.local/bin`) is not in your PATH.
**Cause:** The install directory (`$CODEX_BIN_DIR` or default `~/.local/bin`) is not in your PATH.

**Solution:**

```bash
# 1. Check if install directory exists
ls -la ~/.local/bin/
ls -la "${CODEX_BIN_DIR:-$HOME/.local/bin}/"

# 2. Check if PATH includes the directory
echo $PATH | tr ':' '\n' | grep local
Expand All @@ -468,7 +468,7 @@ echo $PATH | tr ':' '\n' | grep local
cat ~/.zshrc | grep local

# 4. If not configured, add manually
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
echo 'export PATH="${CODEX_BIN_DIR:-$HOME/.local/bin}:$PATH"' >> ~/.zshrc

# 5. Reload config
source ~/.zshrc
Expand All @@ -482,7 +482,7 @@ If WezTerm cannot find ccb commands but regular Terminal can:
- Add PATH to `~/.zprofile` as well:

```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zprofile
echo 'export PATH="${CODEX_BIN_DIR:-$HOME/.local/bin}:$PATH"' >> ~/.zprofile
```

Then restart WezTerm completely (Cmd+Q, reopen).
Expand Down
8 changes: 4 additions & 4 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,13 @@ cping

如果运行 `./install.sh install` 后找不到 `ccb`、`cask`、`cping` 等命令:

**原因:** 安装目录 (`~/.local/bin`) 不在 PATH 中。
**原因:** 安装目录(`$CODEX_BIN_DIR`,或默认 `~/.local/bin`不在 PATH 中。

**解决方法:**

```bash
# 1. 检查安装目录是否存在
ls -la ~/.local/bin/
ls -la "${CODEX_BIN_DIR:-$HOME/.local/bin}/"

# 2. 检查 PATH 是否包含该目录
echo $PATH | tr ':' '\n' | grep local
Expand All @@ -489,7 +489,7 @@ echo $PATH | tr ':' '\n' | grep local
cat ~/.zshrc | grep local

# 4. 如果没有配置,手动添加
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
echo 'export PATH="${CODEX_BIN_DIR:-$HOME/.local/bin}:$PATH"' >> ~/.zshrc

# 5. 重新加载配置
source ~/.zshrc
Expand All @@ -503,7 +503,7 @@ source ~/.zshrc
- 同时添加 PATH 到 `~/.zprofile`:

```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zprofile
echo 'export PATH="${CODEX_BIN_DIR:-$HOME/.local/bin}:$PATH"' >> ~/.zprofile
```

然后完全重启 WezTerm(Cmd+Q 退出后重新打开)。
Expand Down
36 changes: 31 additions & 5 deletions bin/ccb-completion-hook
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import argparse
import json
import os
import subprocess
import shutil
import sys
from pathlib import Path

Expand Down Expand Up @@ -186,11 +187,28 @@ def send_via_tmux(pane_id: str, message: str) -> bool:

def find_ask_command() -> str | None:
"""Find the ask command in common locations."""
found = shutil.which("ask")
if found:
return found

ask_paths = [
# Prefer the sibling command in the same installed bin folder.
Path(__file__).resolve().parent / "ask",
]

bin_dir = (os.environ.get("CODEX_BIN_DIR") or "").strip()
if bin_dir:
ask_paths.append(Path(bin_dir).expanduser() / "ask")

install_prefix = (os.environ.get("CODEX_INSTALL_PREFIX") or "").strip()
if install_prefix:
ask_paths.append(Path(install_prefix).expanduser() / "bin" / "ask")

# Legacy/default locations
ask_paths.extend([
Path.home() / ".local" / "share" / "codex-dual" / "bin" / "ask",
Path.home() / ".local" / "bin" / "ask",
]
])
# On Windows, also check LOCALAPPDATA
if os.name == "nt":
localappdata = os.environ.get("LOCALAPPDATA", "")
Expand Down Expand Up @@ -365,7 +383,10 @@ Result: {reply_content}
session_filename = session_files.get(caller, ".claude-session")

# Search for session file in multiple locations (order matters - most specific first)
work_dir = os.environ.get("CCB_WORK_DIR", "")
work_dir = os.environ.get("CCB_WORK_DIR", "").strip()
if not work_dir:
work_dir = os.getcwd()

search_paths = []

# 1. Request's work_dir (most specific)
Expand All @@ -374,13 +395,18 @@ Result: {reply_content}

# 2. Current working directory (fallback)
cwd = os.getcwd()
if cwd != work_dir:
if cwd and cwd != work_dir:
search_paths.append(Path(cwd) / ".ccb_config" / session_filename)

# 3. User's home-based locations
# 3. Explicit install prefix (optional)
install_prefix = (os.environ.get("CODEX_INSTALL_PREFIX") or "").strip()
if install_prefix:
search_paths.append(Path(install_prefix).expanduser() / ".ccb_config" / session_filename)

# 4. Legacy/default install location
search_paths.append(Path.home() / ".local" / "share" / "codex-dual" / ".ccb_config" / session_filename)

# 4. On Windows, also check LOCALAPPDATA
# 5. On Windows, also check LOCALAPPDATA
if os.name == "nt":
localappdata = os.environ.get("LOCALAPPDATA", "")
if localappdata:
Expand Down
64 changes: 59 additions & 5 deletions ccb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,55 @@ _MNT_DRIVE_RE = re.compile(r"^/mnt/([A-Za-z])/(.*)$")
_MSYS_DRIVE_RE = re.compile(r"^/([A-Za-z])/(.*)$")


def _get_bin_dir() -> Path:
"""Best-effort resolve the bin dir where CCB helper scripts live.

Priority:
1) CODEX_BIN_DIR / CCB_BIN_DIR env
2) directory of the invoked executable on PATH (shutil.which)
3) default ~/.local/bin
"""
env_bin = (os.environ.get("CODEX_BIN_DIR") or os.environ.get("CCB_BIN_DIR") or "").strip()
if env_bin:
return Path(env_bin).expanduser()

argv0 = (sys.argv[0] or "").strip()
candidates: list[Path] = []
if argv0:
# If invoked via an explicit path, keep its directory (do NOT resolve symlinks).
if ("/" in argv0) or ("\\" in argv0):
candidates.append(Path(argv0).expanduser())
found = shutil.which(argv0)
if found:
candidates.append(Path(found))

found_ccb = shutil.which("ccb")
if found_ccb:
candidates.append(Path(found_ccb))

for p in candidates:
try:
if p.exists():
return p.parent
except Exception:
continue

return Path.home() / ".local" / "bin"


def _find_helper_script(name: str) -> Path | None:
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type annotation Path | None requires Python 3.10+ or the from __future__ import annotations import. The ccb file is missing this import, which will cause a syntax error on Python 3.7-3.9. Add from __future__ import annotations at the top of the file, following the pattern used in other Python files in this codebase (e.g., lib/completion_hook.py, lib/ccb_protocol.py).

Copilot uses AI. Check for mistakes.
"""Locate an installed helper script by name."""
found = shutil.which(name)
if found:
return Path(found)

for base in (_get_bin_dir(), script_dir / "config"):
p = base / name
if p.exists():
return p
return None


def _looks_like_windows_path(value: str) -> bool:
s = value.strip()
if not s:
Expand Down Expand Up @@ -901,14 +950,15 @@ class AILauncher:
Enable/disable CCB tmux UI theming for the *current tmux session*.

This is session-scoped and reversible (saves/restores user options) via helper scripts
installed to `~/.local/bin/`.
installed to BIN_DIR (see env `CODEX_BIN_DIR`).
"""
if self.terminal_type != "tmux":
return
if not os.environ.get("TMUX"):
return
script = Path.home() / ".local" / "bin" / ("ccb-tmux-on.sh" if active else "ccb-tmux-off.sh")
if not script.exists():
script_name = "ccb-tmux-on.sh" if active else "ccb-tmux-off.sh"
script = _find_helper_script(script_name)
if not script:
return
try:
debug = os.environ.get("CCB_DEBUG") in ("1", "true", "yes")
Expand Down Expand Up @@ -3847,9 +3897,13 @@ def _detect_cca() -> tuple[str | None, str | None]:
install_dir = _infer_install_dir_from_exe(exe)
return str(exe.resolve()), str(install_dir)
candidates = [
Path.home() / ".local/share/claude_code_autoflow",
Path.home() / ".local/bin/cca",
Path.home() / ".local" / "share" / "claude_code_autoflow",
]
# If user configured a custom bin dir for tools, also consider it for legacy fallbacks.
env_bin_dir = (os.environ.get("CODEX_BIN_DIR") or os.environ.get("CCB_BIN_DIR") or "").strip()
if env_bin_dir:
candidates.append(Path(env_bin_dir).expanduser() / "cca")
candidates.append(Path.home() / ".local" / "bin" / "cca")
# Windows 特定路径
if platform.system() == "Windows":
localappdata = os.environ.get("LOCALAPPDATA", "")
Expand Down
8 changes: 6 additions & 2 deletions config/tmux-ccb.conf
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ bind M set -g mouse off \; display "Mouse OFF"

bind r source-file ~/.tmux.conf \; display "Config reloaded!"

# Manually toggle CCB theme for current session (optional).
bind-key C run-shell "#{@ccb_bin_dir}/ccb-tmux-on.sh"
bind-key V run-shell "#{@ccb_bin_dir}/ccb-tmux-off.sh"

# -----------------------------------------------------------------------------
# Session Management
# -----------------------------------------------------------------------------
Expand All @@ -191,9 +195,9 @@ set -g visual-activity off
# NOTE:
# CCB intentionally does not set any persistent statusbar/theme options here.
# The CCB theme is applied per-session only while CCB is active via:
# - `~/.local/bin/ccb-tmux-on.sh`
# - `$CODEX_BIN_DIR/ccb-tmux-on.sh` (or default `~/.local/bin/ccb-tmux-on.sh`)
# and restored on exit via:
# - `~/.local/bin/ccb-tmux-off.sh`
# - `$CODEX_BIN_DIR/ccb-tmux-off.sh` (or default `~/.local/bin/ccb-tmux-off.sh`)
#
# This avoids clobbering your existing tmux theme when CCB is not running.

Expand Down
19 changes: 17 additions & 2 deletions lib/completion_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import os
import subprocess
import shutil
import sys
import threading
from pathlib import Path
Expand Down Expand Up @@ -40,11 +41,25 @@ def _run_hook_async(
def _run():
try:
# Find ccb-completion-hook script (Python script only, not .cmd wrapper)
script_paths = [
script_paths: list[Path] = []

found = shutil.which("ccb-completion-hook")
if found:
script_paths.append(Path(found))

bin_dir = (os.environ.get("CODEX_BIN_DIR") or "").strip()
if bin_dir:
script_paths.append(Path(bin_dir).expanduser() / "ccb-completion-hook")

install_prefix = (os.environ.get("CODEX_INSTALL_PREFIX") or "").strip()
if install_prefix:
script_paths.append(Path(install_prefix).expanduser() / "bin" / "ccb-completion-hook")

script_paths.extend([
Path(__file__).parent.parent / "bin" / "ccb-completion-hook",
Path.home() / ".local" / "bin" / "ccb-completion-hook",
Path("/usr/local/bin/ccb-completion-hook"),
]
])
# On Windows, check installed location (Python script, not .cmd)
if os.name == "nt":
localappdata = os.environ.get("LOCALAPPDATA", "")
Expand Down
Loading