tmuxclihook 0.5.13 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/adapters/codex.sh +2 -1
- package/scripts/adapters/opencode.sh +2 -1
- package/scripts/install-claude-hooks.sh +6 -0
- package/scripts/install-codex-hooks.sh +3 -0
- package/scripts/install-opencode-hooks.sh +3 -0
- package/scripts/lib-install-hooks.sh +62 -5
- package/scripts/lib-tmux-ai-status.sh +76 -6
- package/scripts/tmux-ai-status +3 -0
- package/tmux-ai-hooks-status.tmux +2 -1
package/package.json
CHANGED
|
@@ -219,7 +219,8 @@ adapter_uninstall_hooks() {
|
|
|
219
219
|
local stripped
|
|
220
220
|
stripped=$(_codex_read_existing | _codex_strip_managed)
|
|
221
221
|
if [ "$(printf '%s' "$stripped" | jq -c .)" = "{}" ]; then
|
|
222
|
-
|
|
222
|
+
# 符号链接(cc-switch 实例共享真源)只删真实目标,保留链接
|
|
223
|
+
_install_remove_target "$ADAPTER_HOOKS_FILE"
|
|
223
224
|
echo "Codex hooks uninstalled (removed empty $ADAPTER_HOOKS_FILE)"
|
|
224
225
|
else
|
|
225
226
|
_install_atomic_write "$stripped" "$ADAPTER_HOOKS_FILE"
|
|
@@ -225,7 +225,8 @@ adapter_install_hooks() {
|
|
|
225
225
|
adapter_uninstall_hooks() {
|
|
226
226
|
if [ -f "$ADAPTER_HOOKS_FILE" ]; then
|
|
227
227
|
if grep -q "$_OPENCODE_MARKER" "$ADAPTER_HOOKS_FILE" 2>/dev/null; then
|
|
228
|
-
|
|
228
|
+
# 符号链接只删真实目标,保留链接(与原子写一致)
|
|
229
|
+
_install_remove_target "$ADAPTER_HOOKS_FILE"
|
|
229
230
|
echo "opencode plugin uninstalled (removed $ADAPTER_HOOKS_FILE)"
|
|
230
231
|
else
|
|
231
232
|
echo "opencode plugin: $ADAPTER_HOOKS_FILE 非本插件生成,跳过删除"
|
|
@@ -45,6 +45,9 @@ esac
|
|
|
45
45
|
case "${1:-install}" in
|
|
46
46
|
uninstall)
|
|
47
47
|
adapter_uninstall_hooks
|
|
48
|
+
# 先清安装意图再写卸载意图:suppress 写失败时 errexit 会退出,此顺序
|
|
49
|
+
# 保证 installed 不残留(残留会让 _refresh 把刚卸载的 hooks 装回来)。
|
|
50
|
+
_hooks_installed_clear claude || true
|
|
48
51
|
_hooks_suppress_write claude
|
|
49
52
|
;;
|
|
50
53
|
check)
|
|
@@ -66,15 +69,18 @@ case "${1:-install}" in
|
|
|
66
69
|
# install 收敛。结构漂移(async/matcher 被外部改)不在判定内,auto 跳过
|
|
67
70
|
# 不修复,显式 install 仍强收敛。
|
|
68
71
|
if adapter_check_converged; then
|
|
72
|
+
_hooks_installed_ensure claude || true
|
|
69
73
|
echo "claude hooks already converged (skip install)"
|
|
70
74
|
exit 0
|
|
71
75
|
fi
|
|
72
76
|
adapter_install_hooks
|
|
73
77
|
_hooks_suppress_clear claude || true
|
|
78
|
+
_hooks_installed_ensure claude || true
|
|
74
79
|
;;
|
|
75
80
|
install)
|
|
76
81
|
adapter_install_hooks
|
|
77
82
|
_hooks_suppress_clear claude || true
|
|
83
|
+
_hooks_installed_ensure claude || true
|
|
78
84
|
;;
|
|
79
85
|
*)
|
|
80
86
|
echo "Usage: $0 [install|uninstall|check [--json]|auto]" >&2
|
|
@@ -40,6 +40,8 @@ esac
|
|
|
40
40
|
case "${1:-install}" in
|
|
41
41
|
uninstall)
|
|
42
42
|
adapter_uninstall_hooks
|
|
43
|
+
# 先清安装意图再写卸载意图(理由同 claude wrapper)
|
|
44
|
+
_hooks_installed_clear codex || true
|
|
43
45
|
_hooks_suppress_write codex
|
|
44
46
|
;;
|
|
45
47
|
check)
|
|
@@ -55,6 +57,7 @@ case "${1:-install}" in
|
|
|
55
57
|
install)
|
|
56
58
|
adapter_install_hooks
|
|
57
59
|
_hooks_suppress_clear codex || true
|
|
60
|
+
_hooks_installed_ensure codex || true
|
|
58
61
|
;;
|
|
59
62
|
*)
|
|
60
63
|
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
@@ -40,6 +40,8 @@ esac
|
|
|
40
40
|
case "${1:-install}" in
|
|
41
41
|
uninstall)
|
|
42
42
|
adapter_uninstall_hooks
|
|
43
|
+
# 先清安装意图再写卸载意图(理由同 claude wrapper)
|
|
44
|
+
_hooks_installed_clear opencode || true
|
|
43
45
|
_hooks_suppress_write opencode
|
|
44
46
|
;;
|
|
45
47
|
check)
|
|
@@ -55,6 +57,7 @@ case "${1:-install}" in
|
|
|
55
57
|
install)
|
|
56
58
|
adapter_install_hooks
|
|
57
59
|
_hooks_suppress_clear opencode || true
|
|
60
|
+
_hooks_installed_ensure opencode || true
|
|
58
61
|
;;
|
|
59
62
|
*)
|
|
60
63
|
echo "Usage: $0 [install|uninstall|check [--json]]" >&2
|
|
@@ -15,14 +15,45 @@ _install_require_jq() {
|
|
|
15
15
|
fi
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# 解析符号链接到最终真实路径(不要求目标存在——悬空链接同样要解析)。
|
|
19
|
+
# 相对链接基于链接所在目录解析;循环/超深链(>40 层)返回 1,调用方回退原路径。
|
|
20
|
+
# 用途:cc-switch 为每个 codex provider 建独立 CODEX_HOME,并把 hooks.json 做成
|
|
21
|
+
# 指向 ~/.codex/hooks.json 的符号链接(所有实例共享同一真源)。mv 作用于链接路径
|
|
22
|
+
# 时只替换链接本身,因此写入必须解析到真实目标,否则实例与共享真源分叉。
|
|
23
|
+
_install_real_target() { # $1=路径 → stdout 最终路径
|
|
24
|
+
local p="$1" link n=0
|
|
25
|
+
while [ -L "$p" ]; do
|
|
26
|
+
n=$((n + 1))
|
|
27
|
+
[ "$n" -le 40 ] || return 1
|
|
28
|
+
link=$(readlink "$p" 2>/dev/null) || return 1
|
|
29
|
+
[ -n "$link" ] || return 1
|
|
30
|
+
case "$link" in
|
|
31
|
+
/*) p="$link" ;;
|
|
32
|
+
*) p="$(dirname "$p")/$link" ;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
printf '%s\n' "$p"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# 原子写入:mktemp 与真实目标同目录 → 同文件系统 → mv 原子。
|
|
19
39
|
# 避免与 AI CLI 并发写产生竞态导致 hooks 字段丢失。
|
|
40
|
+
# 符号链接先解析:临时文件建在真实目标目录,mv 写穿链接(保留链接本身)。
|
|
20
41
|
_install_atomic_write() {
|
|
21
|
-
local content="$1" target="$2" tmp
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
local content="$1" target="$2" tmp real
|
|
43
|
+
real=$(_install_real_target "$target") || real="$target"
|
|
44
|
+
mkdir -p "$(dirname "$real")" 2>/dev/null || true
|
|
45
|
+
tmp=$(mktemp "$(dirname "$real")/.hooks.XXXXXX")
|
|
24
46
|
printf '%s\n' "$content" > "$tmp"
|
|
25
|
-
mv -f "$tmp" "$
|
|
47
|
+
mv -f "$tmp" "$real"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# 删除写入目标(uninstall 清空时用):符号链接解析后只删真实目标,保留链接
|
|
51
|
+
# (指向悬空,下次 install 写穿即恢复共享)。直接 rm 链接会删掉链接本身,把
|
|
52
|
+
# 共享真源留在原地——其他实例仍被注册,语义与「卸载」矛盾。
|
|
53
|
+
_install_remove_target() { # $1=路径
|
|
54
|
+
local target="$1" real
|
|
55
|
+
real=$(_install_real_target "$target") || real="$target"
|
|
56
|
+
rm -f "$real"
|
|
26
57
|
}
|
|
27
58
|
|
|
28
59
|
# --- 卸载意图标记(suppression marker)---
|
|
@@ -55,6 +86,32 @@ _hooks_suppress_clear() {
|
|
|
55
86
|
fi
|
|
56
87
|
}
|
|
57
88
|
|
|
89
|
+
# --- 安装意图标记(installed marker)---
|
|
90
|
+
# 显式 install / claude auto(含已收敛跳过)→ 写;显式 uninstall → 删。
|
|
91
|
+
# 语义 = 用户装过该工具;跨工具自修复(_maybe_repair_other_tools)据此决定
|
|
92
|
+
# hooks 丢失时是否允许自动重装。无标记 + check 失败时绝不安装——尊重
|
|
93
|
+
# 「codex/opencode 非人人安装」的现状。
|
|
94
|
+
# ⚠️ 与 suppressed 同属 _SUPPRESS_DIR 两处同步约束(另一处见 lib-tmux-ai-status.sh)。
|
|
95
|
+
_hooks_installed_file() { printf '%s/installed-%s' "$_SUPPRESS_DIR" "$1"; }
|
|
96
|
+
_hooks_installed_write() {
|
|
97
|
+
if ! mkdir -p "$_SUPPRESS_DIR" 2>/dev/null \
|
|
98
|
+
|| ! date +%s > "$(_hooks_installed_file "$1")" 2>/dev/null; then
|
|
99
|
+
echo "WARN: 安装意图标记写入失败——该工具 hooks 丢失后不会自动修复;" \
|
|
100
|
+
"请检查 $_SUPPRESS_DIR 可写性" >&2
|
|
101
|
+
return 1
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
_hooks_installed_clear() {
|
|
105
|
+
if [ -e "$(_hooks_installed_file "$1")" ] \
|
|
106
|
+
&& ! rm -f "$(_hooks_installed_file "$1")" 2>/dev/null; then
|
|
107
|
+
echo "WARN: 安装意图标记删除失败(卸载仍由 suppressed 标记兜底)" >&2
|
|
108
|
+
return 1
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
_hooks_installed_ensure() { # 已登记则不动,避免 mtime 抖动
|
|
112
|
+
[ -e "$(_hooks_installed_file "$1")" ] || _hooks_installed_write "$1"
|
|
113
|
+
}
|
|
114
|
+
|
|
58
115
|
# --- 安装/卸载读-改-写互斥锁 ---
|
|
59
116
|
# 写路径的并发方:TUI 显式装 / tmux 入口 auto / SessionStart 自修复 / _maybe_repair_hooks /
|
|
60
117
|
# drift 收敛——五方都经 wrapper dispatch,锁在此层一处覆盖三 adapter。
|
|
@@ -10,6 +10,17 @@ _STATUS_DIR="${TMUX_AI_STATUS_DIR:-/tmp/ai-status}"
|
|
|
10
10
|
_SUPPRESS_DIR="${TMUX_AI_SUPPRESS_DIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/tmuxclihook}"
|
|
11
11
|
STATUS_COLOR="#F1FA8C"
|
|
12
12
|
|
|
13
|
+
# 安装意图标记路径(与 lib-install-hooks.sh 的 _hooks_installed_file 同构)。
|
|
14
|
+
# 事件路径不 source 安装库,故此处独立实现;目录定义同 suppressed 两处同步。
|
|
15
|
+
# 语义:用户显式装过该工具 → 跨工具自修复允许在其 hooks 丢失时自动重装。
|
|
16
|
+
_installed_marker_path() { printf '%s/installed-%s' "$_SUPPRESS_DIR" "$1"; }
|
|
17
|
+
_installed_marker_write() {
|
|
18
|
+
local f
|
|
19
|
+
f=$(_installed_marker_path "$1") || return 1
|
|
20
|
+
mkdir -p "$_SUPPRESS_DIR" 2>/dev/null || return 1
|
|
21
|
+
date +%s > "$f" 2>/dev/null
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
# --- 日志模块 ---
|
|
14
25
|
source "${_LIB_DIR}/lib-tmux-ai-log.sh"
|
|
15
26
|
|
|
@@ -907,7 +918,15 @@ _cleanup_stale_panes() {
|
|
|
907
918
|
local ps_snap
|
|
908
919
|
ps_snap=$(_ps_process_snapshot)
|
|
909
920
|
|
|
910
|
-
|
|
921
|
+
# 动态追加 monitor seen 字段(与 build_all_status 同一组名字,复用其收集器)。
|
|
922
|
+
# launcher/daemon 场景工具进程可能脱离 pane 进程树,进程树 BFS 看不到;
|
|
923
|
+
# seen=1 是 monitor 用 launcher 进程树探测得出的「工具确实存活」信号。
|
|
924
|
+
local seen_fmt="" _seen_name
|
|
925
|
+
for _seen_name in $(_collect_ai_process_names); do
|
|
926
|
+
seen_fmt="${seen_fmt}|#{@ai_monitor_seen_${_seen_name}}"
|
|
927
|
+
done
|
|
928
|
+
|
|
929
|
+
while IFS='|' read -r pane_id pane_status _seen_flags; do
|
|
911
930
|
case "$pane_status" in
|
|
912
931
|
"!"|"?"|">")
|
|
913
932
|
local sanitized="${pane_id//[^a-zA-Z0-9]/_}"
|
|
@@ -923,13 +942,20 @@ _cleanup_stale_panes() {
|
|
|
923
942
|
fi
|
|
924
943
|
;;
|
|
925
944
|
"✓"|"-")
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
945
|
+
# 终态只认「进程存活」证据:seen=1 时保留(launcher/daemon 场景
|
|
946
|
+
# 进程树不可见);活跃态 !/?/> 的判据是归属证据,不使用 seen。
|
|
947
|
+
case "$_seen_flags" in
|
|
948
|
+
*1*) ;;
|
|
949
|
+
*)
|
|
950
|
+
if ! _pane_has_ai_process "$pane_id" "$ps_snap"; then
|
|
951
|
+
_ai_log "STALE: clearing terminal state '$pane_status' on $pane_id (no ai process)"
|
|
952
|
+
tmux set-option -pt "$pane_id" @ai_pane_status "" 2>/dev/null || true
|
|
953
|
+
fi
|
|
954
|
+
;;
|
|
955
|
+
esac
|
|
930
956
|
;;
|
|
931
957
|
esac
|
|
932
|
-
done < <(tmux list-panes -a -F "#{pane_id}|#{@ai_pane_status}" 2>/dev/null)
|
|
958
|
+
done < <(tmux list-panes -a -F "#{pane_id}|#{@ai_pane_status}${seen_fmt}" 2>/dev/null)
|
|
933
959
|
|
|
934
960
|
# 清理孤儿目录:目录存在但对应 pane 已不在 tmux 中
|
|
935
961
|
local all_pane_ids
|
|
@@ -1039,3 +1065,47 @@ _maybe_repair_hooks() {
|
|
|
1039
1065
|
fi
|
|
1040
1066
|
fi
|
|
1041
1067
|
}
|
|
1068
|
+
|
|
1069
|
+
# 跨工具自修复:tmux 入口所有 _refresh/_session-switched 固定传 TOOL_ID=claude,
|
|
1070
|
+
# codex/opencode 只有自身 SessionStart 能触发修复;hooks 文件被整体覆盖/删除后
|
|
1071
|
+
# 再无事件 → 永不自愈。本函数由 _run_refresh 调用,遍历其余 adapter,对
|
|
1072
|
+
# 「用户显式装过」(installed-<tool> 标记)的工具做 check → 缺失则 install。
|
|
1073
|
+
#
|
|
1074
|
+
# 不 source adapter:ADAPTER_* 与 adapter_* 会覆盖当前已加载的 adapter(TOOL_ID
|
|
1075
|
+
# 污染),一律子进程 exec wrapper(wrapper 自带 _LIB_DIR 与 adapter 加载)。
|
|
1076
|
+
# 未登记的工具只做「既存安装登记」:check 通过才补写标记,绝不自动安装——
|
|
1077
|
+
# 尊重 codex/opencode 非人人安装的现状。
|
|
1078
|
+
# 成本:每工具每 60s 至多 1 次 check;installed 存在且缺失时再加 1 次 install。
|
|
1079
|
+
_maybe_repair_other_tools() {
|
|
1080
|
+
local current="${TOOL_ID:-claude}"
|
|
1081
|
+
local f tool installer marker now last
|
|
1082
|
+
for f in "${_ADAPTER_DIR}"/*.sh; do
|
|
1083
|
+
[ -f "$f" ] || continue
|
|
1084
|
+
tool=$(basename "$f" .sh)
|
|
1085
|
+
[ "$tool" = "$current" ] && continue
|
|
1086
|
+
[ -f "${_SUPPRESS_DIR}/suppressed-${tool}" ] && continue
|
|
1087
|
+
installer="${_LIB_DIR}/install-${tool}-hooks.sh"
|
|
1088
|
+
[ -x "$installer" ] || continue
|
|
1089
|
+
marker="${_STATUS_DIR}/.hooks-repair-${tool}-ts"
|
|
1090
|
+
now=$(date +%s)
|
|
1091
|
+
[ -d "$_STATUS_DIR" ] || mkdir -p "$_STATUS_DIR" 2>/dev/null
|
|
1092
|
+
if [ -f "$marker" ]; then
|
|
1093
|
+
last=$(cat "$marker" 2>/dev/null)
|
|
1094
|
+
[ $((now - ${last:-0})) -lt 60 ] && continue
|
|
1095
|
+
fi
|
|
1096
|
+
echo "$now" > "$marker" 2>/dev/null || true
|
|
1097
|
+
if [ -f "$(_installed_marker_path "$tool")" ]; then
|
|
1098
|
+
"$installer" check >/dev/null 2>&1 && continue
|
|
1099
|
+
_ai_log "REPAIR: ${tool} hooks missing, reinstalling (cross-tool)"
|
|
1100
|
+
if "$installer" >/dev/null 2>&1; then
|
|
1101
|
+
tmux display-message "✓ ${tool} hooks 已自动修复(检测到缺失)" 2>/dev/null || true
|
|
1102
|
+
else
|
|
1103
|
+
tmux display-message "⚠ ${tool} hooks 自动修复失败,请运行 tmuxclihook 检查" 2>/dev/null || true
|
|
1104
|
+
fi
|
|
1105
|
+
elif "$installer" check >/dev/null 2>&1; then
|
|
1106
|
+
# 未登记但健康:登记既存安装(老用户升级迁移),绝不安装
|
|
1107
|
+
_installed_marker_write "$tool" 2>/dev/null || true
|
|
1108
|
+
_ai_log "REPAIR: ${tool} hooks healthy, registered installed marker"
|
|
1109
|
+
fi
|
|
1110
|
+
done
|
|
1111
|
+
}
|
package/scripts/tmux-ai-status
CHANGED
|
@@ -108,6 +108,9 @@ _notification_implicit_status() {
|
|
|
108
108
|
# 重建聚合状态(_refresh / _entered 共用)
|
|
109
109
|
_run_refresh() {
|
|
110
110
|
_maybe_repair_hooks
|
|
111
|
+
# 跨工具修复:_refresh 固定以 claude 身份运行,codex/opencode 的整体丢失
|
|
112
|
+
# 只能靠这里兜底(仅对 installed-<tool> 已登记的工具)。
|
|
113
|
+
_maybe_repair_other_tools
|
|
111
114
|
# 节流版清理(60s):tmux 生命周期 hook(pane-focus-in 等)高频触发,
|
|
112
115
|
# 全价 _cleanup_stale_panes 实测 ~455ms;卡死状态清理有 :37 的事件路径
|
|
113
116
|
# 节流兜底,此处无需每次全价。Stop/SessionEnd fallback 仍走全价精准清理。
|
|
@@ -85,7 +85,8 @@ tmux bind-key r run-shell "'${CURRENT_DIR}/scripts/tmux-ai-reload'" 2>/dev/null
|
|
|
85
85
|
# #{hook_client_name} 这个 format 变量——误用它恒展开为空,handler 的
|
|
86
86
|
# [ -n "$3" ] 守卫静默跳过,entered_time 从不写入,排序退化为 created 序。
|
|
87
87
|
tmux set-hook -g client-session-changed "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _session-switched #{hook_client}'" 2>/dev/null || true
|
|
88
|
-
# 注:_refresh 与 TOOL_ID
|
|
88
|
+
# 注:_refresh 与 TOOL_ID 无关(重建聚合 + 自修复当前工具 hooks + 跨工具修复
|
|
89
|
+
# 已显式安装的 codex/opencode),统一传 claude。
|
|
89
90
|
tmux set-hook -g session-closed "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _refresh'"
|
|
90
91
|
tmux set-hook -g client-detached "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _refresh'"
|
|
91
92
|
# (client-attached 不再注册:attach 场景由 client-session-changed 覆盖)
|