From 750b481514f4a7e790a4ea5e535bd462375220e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=9C=A3=E5=BE=A1?= Date: Wed, 18 Mar 2026 20:32:11 +0800 Subject: [PATCH] fix(sync): multiple codex statusline fixes and improvements ## Changes - Fix context usage showing 100% by using last_token_usage.input_tokens instead of cumulative total_token_usage.total_tokens - Fix cross-instance context contamination by matching sessions via filename creation timestamp - Fix tmux normal mode statusline not showing due to inherited TERM_PROGRAM (add _is_tmux_cc to distinguish -CC mode) - Fix tmux -CC mode "Unrecognized command from tmux" error by wrapping SetUserVar with tmux passthrough - Switch iTerm2 plist operations from direct file I/O to defaults export/import (cfprefsd) to prevent running iTerm2 from overwriting changes - Fix zsh hooks update detection using shasum hash comparison instead of string equality - Auto-configure tmux allow-passthrough (prefer .tmux.conf.local for framework compatibility) - Add /clean-codex-statusline skill for complete statusline cleanup - Add cleanup_iterm2() function for proper Status Bar removal ## Impact - Codex statusline (tmux + iTerm2) - Install/uninstall workflow Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude-plugin/marketplace.json | 4 +- .../skills/clean-codex-statusline/SKILL.md | 109 ++++++++++++++++++ CHANGELOG.md | 21 ++++ README.en.md | 2 +- README.md | 2 +- plugins/sync/.claude-plugin/plugin.json | 2 +- plugins/sync/README.md | 1 + plugins/sync/scripts/codex-status | 85 +++++++++++--- plugins/sync/scripts/codex-statusline.zsh | 32 +++-- .../sync/scripts/ensure-codex-statusline.sh | 45 ++++++-- 10 files changed, 267 insertions(+), 36 deletions(-) create mode 100644 .claude/skills/clean-codex-statusline/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ba8ef49..ed9805f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "TapTap Claude Code 插件库 - 提供开发工作流自动化插件", - "version": "0.1.23", + "version": "0.1.24", "pluginRoot": "./plugins" }, "plugins": [ @@ -38,7 +38,7 @@ "name": "sync", "source": "./plugins/sync", "description": "项目配置同步插件,提供 MCP、LSP 和开发环境配置同步功能", - "version": "0.1.19", + "version": "0.1.20", "author": { "name": "TapTap AI Team" }, diff --git a/.claude/skills/clean-codex-statusline/SKILL.md b/.claude/skills/clean-codex-statusline/SKILL.md new file mode 100644 index 0000000..b71226c --- /dev/null +++ b/.claude/skills/clean-codex-statusline/SKILL.md @@ -0,0 +1,109 @@ +--- +name: clean-codex-statusline +description: 清理本地 Codex statusline 配置(codex-status 脚本、zsh hooks、tmux status-left、iTerm2 Status Bar、缓存) +--- + +## 执行流程 + +按顺序执行以下清理步骤,每步都检查是否存在再删除。 + +### 步骤 1:删除 codex-status 脚本 + +```bash +rm -f ~/.local/bin/codex-status && echo "✅ codex-status 已删除" || echo "⏭️ 不存在" +``` + +### 步骤 2:清理 zsh hooks + +检查 `~/.zshrc.local` 中是否有 codex-statusline 标记块,有则删除: + +```bash +if grep -q "codex-statusline BEGIN" ~/.zshrc.local 2>/dev/null; then + sed '/# >>> codex-statusline BEGIN >>>/,/# <<< codex-statusline END << ~/.zshrc.local.tmp && mv ~/.zshrc.local.tmp ~/.zshrc.local + echo "✅ zsh hooks 已清理" +else + echo "⏭️ 无 zsh hooks" +fi +``` + +### 步骤 3:重置 tmux status-left + +如果当前在 tmux 中,重置 status bar 为默认值: + +```bash +if [ -n "${TMUX:-}" ]; then + tmux set -g status-left "[#S]" 2>/dev/null + tmux set -gu window-status-format 2>/dev/null + tmux set -gu window-status-current-format 2>/dev/null + tmux set -gu status-right 2>/dev/null + echo "✅ tmux status bar 已重置" +else + echo "⏭️ 不在 tmux 中,跳过" +fi +``` + +### 步骤 4:清理 iTerm2 Status Bar + +仅 macOS 执行。**必须通过 osascript 操作运行中的 iTerm2**(直接改 plist 会被 iTerm2 退出时覆盖): + +```bash +python3 -c " +import subprocess, plistlib, sys + +# 通过 defaults export/import 操作(走 cfprefsd,不会被 iTerm2 退出时覆盖) +result = subprocess.run(['defaults', 'export', 'com.googlecode.iterm2', '-'], capture_output=True) +if result.returncode != 0: + print('⏭️ iTerm2 配置不存在') + sys.exit(0) + +data = plistlib.loads(result.stdout) +bookmarks = data.get('New Bookmarks', []) +changed = False +for b in bookmarks: + layout = b.get('Status Bar Layout', {}) + comps = layout.get('components', []) + new_comps = [c for c in comps if 'codex_status' not in str(c.get('configuration', {}).get('knobs', {}).get('expression', ''))] + if len(new_comps) != len(comps): + layout['components'] = new_comps + changed = True + if not new_comps and b.get('Show Status Bar'): + b['Show Status Bar'] = False + changed = True + +if changed: + xml = plistlib.dumps(data) + result = subprocess.run(['defaults', 'import', 'com.googlecode.iterm2', '-'], input=xml, capture_output=True) + if result.returncode == 0: + print('✅ iTerm2 codex_status 组件已清除(Cmd+Q 重启 iTerm2 生效)') + else: + print('❌ iTerm2 配置写入失败') + sys.exit(1) +else: + print('⏭️ iTerm2 无 codex_status 组件') +" +``` + +### 步骤 5:清理缓存 + +```bash +rm -rf ~/.cache/codex-status && echo "✅ 缓存已清理" || echo "⏭️ 不存在" +``` + +### 步骤 6:输出结果 + +汇总报告并提示。**必须包含以下完整提示,不可省略:** + +``` +✅ Codex statusline 已完全清理 + +清理项目: + - ~/.local/bin/codex-status + - ~/.zshrc.local codex-statusline 块 + - tmux status bar + - iTerm2 Status Bar codex_status 组件 + - ~/.cache/codex-status/ + +提示: + - 新开终端窗口使 zsh 变更生效 + - 如需重新安装:/sync:codex-statusline +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index f35898b..088f2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.1.24 — Codex statusline fixes + +### Sync Plugin (0.1.20) + +- Fixed context usage showing 100% by using `last_token_usage.input_tokens` instead of cumulative `total_token_usage.total_tokens` +- Fixed cross-instance context usage contamination by matching sessions via filename creation timestamp +- Fixed tmux normal mode statusline not showing due to `_is_iterm2` incorrectly returning true (inherited `TERM_PROGRAM`) +- Added `_is_tmux_cc()` to properly distinguish tmux -CC from normal tmux mode +- Fixed tmux -CC mode "Unrecognized command from tmux" error by wrapping SetUserVar with tmux passthrough +- Added `_iterm2_escape()` helper for automatic tmux passthrough wrapping +- Auto-configure `allow-passthrough on` in tmux.conf (prefers `.tmux.conf.local` for framework compatibility) +- Switched iTerm2 plist operations from direct file I/O to `defaults export/import` (cfprefsd) to prevent running iTerm2 from overwriting changes +- Added `cleanup_iterm2()` function for proper Status Bar removal +- Fixed zsh hooks update detection using shasum hash comparison instead of string equality +- Added `/clean-codex-statusline` skill for complete statusline cleanup + +### Marketplace + +- Bumped version from 0.1.23 to 0.1.24 +- Updated sync plugin to version 0.1.20 + ## 0.1.23 — Codex compatibility ### Sync Plugin (0.1.19) diff --git a/README.en.md b/README.en.md index 7747d3c..b056a14 100644 --- a/README.en.md +++ b/README.en.md @@ -86,7 +86,7 @@ One-click configuration for MCP, auto-update, and Cursor synchronization: | ------- | ------- | ---------------------------------------------------------------------------------------------------- | | spec | 0.1.4 | Spec-Driven Development workflow plugin | | git | 0.1.12 | Git workflow automation plugin (commit/push/MR + dual-mode code review + remote platform ops) | -| sync | 0.1.19 | Dev environment config sync plugin (MCP + LSP + Hooks + Cursor + Claude Skills) | +| sync | 0.1.20 | Dev environment config sync plugin (MCP + LSP + Hooks + Cursor + Claude Skills) | | quality | 0.0.4 | AI-powered code quality plugin (9 parallel Agents: Bug detection, code quality, security, performance) | diff --git a/README.md b/README.md index a886624..49864bd 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ mkdir -p .claude && echo '{ | ------- | ----- | ----------------------------------------------------------------- | | spec | 0.1.4 | Spec-Driven Development 工作流插件 | | git | 0.1.12 | Git 工作流自动化插件(提交/推送/MR + 自动代码审查 + 远程平台操作) | -| sync | 0.1.19 | 开发环境配置同步插件(MCP + LSP + Hooks + Cursor + Claude Skills) | +| sync | 0.1.20 | 开发环境配置同步插件(MCP + LSP + Hooks + Cursor + Claude Skills) | | quality | 0.0.4 | AI 驱动的代码质量检查插件(9 个并行 Agent,支持 Bug 检测、代码质量、安全检查、性能分析) | diff --git a/plugins/sync/.claude-plugin/plugin.json b/plugins/sync/.claude-plugin/plugin.json index dd41d81..49688f8 100644 --- a/plugins/sync/.claude-plugin/plugin.json +++ b/plugins/sync/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "sync", "description": "项目配置同步插件,提供 MCP、LSP 和开发环境配置同步功能", - "version": "0.1.19", + "version": "0.1.20", "author": { "name": "TapTap AI Team" } diff --git a/plugins/sync/README.md b/plugins/sync/README.md index d933cd1..d959e0d 100644 --- a/plugins/sync/README.md +++ b/plugins/sync/README.md @@ -344,6 +344,7 @@ chmod +x .githooks/pre-commit ## 版本历史 +- **v0.1.20** - 修复 context usage 显示 100% 问题(改用 last_token_usage);修复多实例 context 串扰;修复 tmux 普通模式无 statusline(_is_tmux_cc 区分 -CC 模式);修复 tmux -CC 报错(passthrough 包裹);iTerm2 配置改用 defaults export/import(防止被覆盖);zsh hooks 更新检测改用 hash 比较;新增 /clean-codex-statusline skill - **v0.1.19** - 新增 codex-statusline(tmux + iTerm2);飞书 MCP 的 Codex 配置改为可选(--with-codex);hooks 迁移至 $HOME 级(Codex 兼容);移除 sequential-thinking MCP;新增 plugin-status skill - **v0.1.17** - skills-sync 新增 review-rules 模板同步(不覆盖项目已有规则) - **v0.1.16** - review-checklist 不覆盖项目自定义版本;修正覆盖策略文档;修复 4 个 hook 脚本缺少可执行权限;Cursor 模板新增 Pipeline Watch diff --git a/plugins/sync/scripts/codex-status b/plugins/sync/scripts/codex-status index 273712b..f313405 100755 --- a/plugins/sync/scripts/codex-status +++ b/plugins/sync/scripts/codex-status @@ -351,20 +351,44 @@ def style(text: str, fg: str, bg: Optional[str] = None, bold: bool = False) -> s return f"#[{','.join(attrs)}] {text} " -def setup_iterm2() -> int: - """Auto-configure iTerm2 Status Bar with codex_status component for all profiles.""" +def _iterm2_prefs_read() -> Optional[dict]: + """Read iTerm2 preferences via 'defaults export' (works with running iTerm2).""" import plistlib + result = subprocess.run( + ["defaults", "export", "com.googlecode.iterm2", "-"], + capture_output=True, + ) + if result.returncode != 0: + return None + try: + return plistlib.loads(result.stdout) + except Exception: + return None - plist_path = pathlib.Path.home() / "Library" / "Preferences" / "com.googlecode.iterm2.plist" - if not plist_path.exists(): - return 1 +def _iterm2_prefs_write(data: dict) -> bool: + """Write iTerm2 preferences via 'defaults import' (works with running iTerm2).""" + import plistlib try: - with open(plist_path, "rb") as f: - plist = plistlib.load(f) + xml = plistlib.dumps(data) except Exception: + return False + result = subprocess.run( + ["defaults", "import", "com.googlecode.iterm2", "-"], + input=xml, + capture_output=True, + ) + return result.returncode == 0 + + +def setup_iterm2() -> int: + """Auto-configure iTerm2 Status Bar with codex_status component for all profiles.""" + data = _iterm2_prefs_read() + if data is None: return 1 + bookmarks = data.get("New Bookmarks", []) + component = { "class": "iTermStatusBarSwiftyStringComponent", "configuration": { @@ -410,12 +434,9 @@ def setup_iterm2() -> int: } changed = False - # Status Bar 位置设为底部(全局设置,0=top, 1=bottom) - if plist.get("StatusBarPosition") != 1: - plist["StatusBarPosition"] = 1 - changed = True + data["StatusBarPosition"] = 1 - for bookmark in plist.get("New Bookmarks", []): + for bookmark in bookmarks: if not bookmark.get("Show Status Bar", False): bookmark["Show Status Bar"] = True changed = True @@ -433,11 +454,41 @@ def setup_iterm2() -> int: changed = True if changed: - try: - with open(plist_path, "wb") as f: - plistlib.dump(plist, f) - print("[codex-status] iTerm2 Status Bar 已自动配置,重启 iTerm2 后生效") - except Exception: + if _iterm2_prefs_write(data): + print("[codex-status] iTerm2 Status Bar 已自动配置(Cmd+Q 重启 iTerm2 生效)") + else: + return 1 + return 0 + + +def cleanup_iterm2() -> int: + """Remove codex_status component from iTerm2 Status Bar for all profiles.""" + data = _iterm2_prefs_read() + if data is None: + return 0 + + bookmarks = data.get("New Bookmarks", []) + changed = False + for bookmark in bookmarks: + layout = bookmark.get("Status Bar Layout", {}) + comps = layout.get("components", []) + new_comps = [ + c for c in comps + if "codex_status" not in str( + c.get("configuration", {}).get("knobs", {}).get("expression", "") + ) + ] + if len(new_comps) != len(comps): + layout["components"] = new_comps + changed = True + if not new_comps and bookmark.get("Show Status Bar"): + bookmark["Show Status Bar"] = False + changed = True + + if changed: + if _iterm2_prefs_write(data): + print("[codex-status] iTerm2 codex_status 已清除(Cmd+Q 重启 iTerm2 生效)") + else: return 1 return 0 diff --git a/plugins/sync/scripts/codex-statusline.zsh b/plugins/sync/scripts/codex-statusline.zsh index 33a38d9..7e35861 100644 --- a/plugins/sync/scripts/codex-statusline.zsh +++ b/plugins/sync/scripts/codex-statusline.zsh @@ -16,21 +16,39 @@ codex_status_active_file() { print -r -- "$HOME/.cache/codex-status/${state_key}.active" } -# 检测是否在 iTerm2 环境(包括 tmux -CC 模式) +# 检测是否在 iTerm2 环境(包括 tmux 内继承的 iTerm2 环境) _is_iterm2() { [[ "${TERM_PROGRAM:-}" == "iTerm.app" || -n "${ITERM_SESSION_ID:-}" ]] && return 0 - # tmux -CC 模式:client_control_mode=1 表示 iTerm2 tmux 集成 - [[ -n "${TMUX:-}" ]] && [[ "$(tmux display-message -p '#{client_control_mode}' 2>/dev/null)" == "1" ]] && return 0 + [[ -n "${ITERM_PROFILE:-}" ]] && return 0 return 1 } +# 检测是否在 tmux -CC(iTerm2 控制模式) +_is_tmux_cc() { + [[ -n "${TMUX:-}" ]] || return 1 + [[ "$(tmux display-message -p '#{client_control_mode}' 2>/dev/null)" == "1" ]] && return 0 + return 1 +} + +# 发送 iTerm2 转义序列(tmux 内自动用 passthrough 包裹) +_iterm2_escape() { + local seq="$1" + local target="${2:-/dev/tty}" + if [[ -n "${TMUX:-}" ]]; then + # tmux passthrough: \ePtmux;\e\e\\ + printf '\033Ptmux;\033%s\033\\' "$seq" > "$target" 2>/dev/null + else + printf '%s' "$seq" > "$target" 2>/dev/null + fi +} + # iTerm2: 通过 SetUserVar 写入 status bar 数据(底部) codex_iterm2_set_user_var() { _is_iterm2 || return 0 local val="${1:-}" local encoded encoded="$(printf '%s' "$val" | base64 | tr -d '\r\n')" - printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "$encoded" > /dev/tty 2>/dev/null + _iterm2_escape "$(printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "$encoded")" } # 后台轮询更新 iTerm2 status bar(codex 运行期间) @@ -48,11 +66,11 @@ codex_iterm2_watch() { st="$("$HOME/.local/bin/codex-status" --plain --state-key="$sk" "$wd" "codex" "" "$pid" 2>/dev/null)" || true if [[ -n "$st" ]]; then encoded="$(printf '%s' "$st" | base64 | tr -d '\r\n')" - printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "$encoded" > "$tty_dev" 2>/dev/null + _iterm2_escape "$(printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "$encoded")" "$tty_dev" fi sleep 5 done - printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "" > "$tty_dev" 2>/dev/null + _iterm2_escape "$(printf '\033]1337;SetUserVar=%s=%s\a' "codex_status" "")" "$tty_dev" ) & ) 2>/dev/null } @@ -70,7 +88,7 @@ codex_iterm2_auto_setup() { codex_tmux_apply_status() { [[ -n "$TMUX" ]] || return 0 # tmux -CC 模式下不设 status-left(用 iTerm2 Status Bar 代替) - _is_iterm2 && return 0 + _is_tmux_cc && return 0 command -v tmux >/dev/null 2>&1 || return 0 tmux set -g status-left '#[fg=#1f2335,bg=#7aa2f7,bold] TMUX #[default] #(~/.local/bin/codex-status --state-key="#{pane_id}" "#{pane_current_path}" "#{pane_current_command}" "#{pane_title}" "#{pane_pid}")' >/dev/null 2>&1 tmux set -g window-status-format '' >/dev/null 2>&1 diff --git a/plugins/sync/scripts/ensure-codex-statusline.sh b/plugins/sync/scripts/ensure-codex-statusline.sh index a1fd448..d7f6d9f 100755 --- a/plugins/sync/scripts/ensure-codex-statusline.sh +++ b/plugins/sync/scripts/ensure-codex-statusline.sh @@ -63,12 +63,11 @@ else echo '[ -f ~/.zshrc.local ] && source ~/.zshrc.local' >> "$ZSHRC" echo "✅ 已在 ~/.zshrc 中添加 source ~/.zshrc.local" fi - src_content="$(cat "$SRC_ZSH")" - if [ -f "$DST_ZSH" ] && grep -q "$BEGIN_MARKER" "$DST_ZSH" 2>/dev/null; then - # 已有标记块 — 比较内容是否需要更新 - existing="$(sed -n "/$BEGIN_MARKER/,/$END_MARKER/p" "$DST_ZSH")" - if [ "$existing" = "$src_content" ]; then + # 已有标记块 — 用 hash 比较是否需要更新(避免空行/换行符差异导致误判) + existing_hash="$(sed -n "/$BEGIN_MARKER/,/$END_MARKER/p" "$DST_ZSH" | shasum | awk '{print $1}')" + src_hash="$(shasum "$SRC_ZSH" | awk '{print $1}')" + if [ "$existing_hash" = "$src_hash" ]; then echo "✅ zsh hooks 已是最新,无需更新" else # 替换标记块 @@ -91,12 +90,44 @@ else fi fi -# ========== 第三步:配置 iTerm2(仅 macOS) ========== +# ========== 第三步:配置 tmux passthrough(tmux -CC 模式需要) ========== + +PASSTHROUGH_LINE="set -g allow-passthrough on" + +if command -v tmux >/dev/null 2>&1; then + # 检查 ~/.tmux.conf 和 ~/.tmux.conf.local 是否已配置 + passthrough_found=false + for conf in "$HOME/.tmux.conf" "$HOME/.tmux.conf.local"; do + if [ -f "$conf" ] && grep -q "allow-passthrough" "$conf" 2>/dev/null; then + passthrough_found=true + break + fi + done + if [ "$passthrough_found" = "true" ]; then + echo "✅ tmux allow-passthrough 已配置" + else + # 优先写 .tmux.conf.local(gpakosz/.tmux 等框架的自定义文件) + TMUX_TARGET="$HOME/.tmux.conf.local" + [ ! -f "$TMUX_TARGET" ] && TMUX_TARGET="$HOME/.tmux.conf" + echo "" >> "$TMUX_TARGET" + echo "# Allow iTerm2 escape sequences to pass through tmux (required for tmux -CC mode)" >> "$TMUX_TARGET" + echo "$PASSTHROUGH_LINE" >> "$TMUX_TARGET" + echo "✅ 已在 $(basename "$TMUX_TARGET") 中启用 allow-passthrough" + fi + # 如果当前在 tmux 内,立即生效 + if [ -n "${TMUX:-}" ]; then + tmux set -g allow-passthrough on 2>/dev/null || true + fi +else + echo "⏭️ tmux 未安装,跳过 passthrough 配置" +fi + +# ========== 第四步:配置 iTerm2(仅 macOS) ========== if [ "$(uname)" = "Darwin" ]; then # 运行 codex-status --setup-iterm2(内部幂等) if "$DST_BIN" --setup-iterm2 2>/dev/null; then - echo "✅ iTerm2 Status Bar 配置完成" + echo "✅ iTerm2 Status Bar 配置完成(首次配置需完全退出并重启 iTerm2)" else echo "⚠️ iTerm2 配置跳过(可能非 iTerm2 环境)" fi