tmuxclihook 0.3.3 → 0.3.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmuxclihook",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "交互式 TUI 安装器:环境侦测、AI CLI 侦测、tmux-ai-status hooks 装/卸/修、TPM 软链校验",
5
5
  "type": "module",
6
6
  "bin": {
@@ -72,10 +72,7 @@ _CLAUDE_JQ_MERGE='
72
72
  .hooks[]
73
73
  | select(.command | contains("tmux-powerline-claude-status") | not)
74
74
  | select(.command | contains("tmux-claude-status") | not)
75
- | select(
76
- (.command | contains("tmux-ai-status") | not)
77
- or (.command | startswith($dev_script))
78
- )
75
+ | select(.command | contains("tmux-ai-status") | not)
79
76
  ]
80
77
  | select(.hooks | length > 0)
81
78
  )
@@ -98,7 +95,7 @@ _CLAUDE_JQ_STRIP='
98
95
  .hooks |= if . then
99
96
  [. | to_entries[] |
100
97
  .value |= [
101
- .[] | .hooks = [.hooks[] | select(.command | startswith($hook_script) | not)]
98
+ .[] | .hooks = [.hooks[] | select(.command | contains("tmux-ai-status") | not)]
102
99
  | select(.hooks | length > 0)
103
100
  ]
104
101
  | select(.value | length > 0)
@@ -107,13 +104,12 @@ _CLAUDE_JQ_STRIP='
107
104
  '
108
105
 
109
106
  _claude_apply_merge() { # $1=输入 JSON 文件 → stdout 变换后全文档
110
- jq --arg dev_script "${_LIB_DIR}/tmux-ai-status" \
111
- --arg tool_cmd "${_LIB_DIR}/tmux-ai-status claude" \
107
+ jq --arg tool_cmd "${_LIB_DIR}/tmux-ai-status claude" \
112
108
  "$_CLAUDE_JQ_MERGE" "$1"
113
109
  }
114
110
 
115
111
  _claude_apply_strip() { # $1=输入 JSON 文件 → stdout 变换后全文档
116
- jq --arg hook_script "${_LIB_DIR}/tmux-ai-status" "$_CLAUDE_JQ_STRIP" "$1"
112
+ jq "$_CLAUDE_JQ_STRIP" "$1"
117
113
  }
118
114
 
119
115
  _claude_count_registered() { # $1=输入 JSON 文件 → stdout 事件数(jq 失败时为空)
@@ -187,18 +183,41 @@ ON CONFLICT(key) DO UPDATE SET value = json_patch(settings.value, json($(_sqlquo
187
183
  }
188
184
 
189
185
  # 完整性检查:cc-switch 模式查通用配置;否则查 hooks 配置文件。
190
- # 10 个事件都注册了本插件 hook 才算完整;缺失(外部覆盖)→ 返回 1 触发自修复。
186
+ # 三要素:10 事件计数 + 每个事件至少一条条目的脚本文件存活(test -x)。
187
+ # 计数齐但脚本死(npm 包卸载/volta 升级换路径,含部分事件死)→ missing → 自修复重装收敛。
188
+ # 存活提取内联在本函数(jq + test),不依赖 lib-install-hooks.sh——本函数同时被
189
+ # 事件路径(lib-tmux-ai-status.sh)与 install wrapper(lib-install-hooks.sh)两个上下文调用。
191
190
  adapter_check_integrity() {
192
- local registered tmp
191
+ local src registered ev p ok_events=""
193
192
  if _ccswitch_active; then
194
- _ccswitch_dump_common tmp || return 1
195
- registered=$(_claude_count_registered "$tmp")
196
- rm -f "$tmp"
193
+ _ccswitch_dump_common src || return 1
197
194
  else
198
195
  [ -f "$ADAPTER_HOOKS_FILE" ] || return 1
199
- registered=$(_claude_count_registered "$ADAPTER_HOOKS_FILE")
196
+ src="$ADAPTER_HOOKS_FILE"
197
+ fi
198
+ registered=$(_claude_count_registered "$src")
199
+ if [ "${registered:-0}" -ge 10 ]; then
200
+ while IFS=$'\t' read -r ev p; do
201
+ if [ -n "$ev" ] && [ -x "$p" ]; then
202
+ case ",$ok_events," in *",$ev,"*) ;; *) ok_events="$ok_events,$ev" ;; esac
203
+ fi
204
+ done < <(jq -r '.hooks as $h |
205
+ ["SessionStart","SessionEnd","UserPromptSubmit","PreToolUse","PostToolUse",
206
+ "PostToolUseFailure","PermissionRequest","Notification","Stop","StopFailure"] as $events |
207
+ $events[] as $e |
208
+ [$h[$e][]?.hooks[]?.command // empty
209
+ | select(contains("tmux-ai-status")) | split(" ")[0]] as $scripts |
210
+ select(($scripts | length) > 0) |
211
+ $scripts[] | "\($e)\t\(.)"' "$src" 2>/dev/null)
200
212
  fi
201
- [ "${registered:-0}" -ge 10 ]
213
+ if [ "$src" != "$ADAPTER_HOOKS_FILE" ]; then
214
+ rm -f "$src"
215
+ fi
216
+ [ "${registered:-0}" -ge 10 ] || return 1
217
+ for ev in SessionStart SessionEnd UserPromptSubmit PreToolUse PostToolUse PostToolUseFailure PermissionRequest Notification Stop StopFailure; do
218
+ case ",$ok_events," in *",$ev,"*) ;; *) return 1 ;; esac
219
+ done
220
+ return 0
202
221
  }
203
222
 
204
223
  # 安装:合并式更新,保留他人(masko 等)注册的 hook。cc-switch 模式只写库
@@ -233,6 +252,7 @@ adapter_uninstall_hooks() {
233
252
  _install_require_jq
234
253
  if _ccswitch_active; then
235
254
  local tmp stripped cur_hooks final_hooks patch managed top_patch
255
+ _ccswitch_warn_gui
236
256
  _ccswitch_dump_common tmp || return 1
237
257
  cur_hooks=$(jq -c '.hooks // {}' "$tmp")
238
258
  # 已无本插件条目 → no-op(否则会把保留的他人 hook 重写一遍)
@@ -36,14 +36,38 @@ ADAPTER_PERMISSION_PRE_TOOL_NAMES="request_permissions"
36
36
  # 自修复/TUI 调用的安装脚本
37
37
  ADAPTER_INSTALLER="${_LIB_DIR}/install-codex-hooks.sh"
38
38
 
39
- # 完整性检查:hooks.json 是否含 6 个本插件 command(0.144+)。
39
+ # 完整性计数:6 个事件中注册了含 tmux-ai-status codex 的 command 的事件数(按事件,
40
+ # 非扁平总数——重复+缺失的组合在扁平计数下会假阳性)。
41
+ _CODEX_JQ_COUNT='
42
+ .hooks as $h |
43
+ ["SessionStart","UserPromptSubmit","PreToolUse","PermissionRequest","PostToolUse","Stop"]
44
+ | map(select([$h[.][]?.hooks[]?.command // empty] | any(contains("tmux-ai-status codex"))))
45
+ | length
46
+ '
47
+
48
+ # 完整性检查:6 事件计数 + 每个事件至少一条条目的脚本文件存活(test -x)。
49
+ # 跨安装源路径无关(contains);脚本死路径(npm 包卸载/volta 换路径,含部分事件死)
50
+ # → missing → install 收敛(no-op 门禁随之放行,接受一次 re-trust 代价换功能恢复)。
40
51
  adapter_check_integrity() {
41
52
  [ -f "$ADAPTER_HOOKS_FILE" ] || return 1
42
- local n
43
- n=$(jq '[.hooks[]?[]?.hooks[]?.command // empty
44
- | select(contains("tmux-ai-status codex"))] | length' \
45
- "$ADAPTER_HOOKS_FILE" 2>/dev/null)
46
- [ "${n:-0}" -ge 6 ]
53
+ local n ev p ok_events=""
54
+ n=$(jq "$_CODEX_JQ_COUNT" "$ADAPTER_HOOKS_FILE" 2>/dev/null)
55
+ [ "${n:-0}" -ge 6 ] || return 1
56
+ while IFS=$'\t' read -r ev p; do
57
+ if [ -n "$ev" ] && [ -x "$p" ]; then
58
+ case ",$ok_events," in *",$ev,"*) ;; *) ok_events="$ok_events,$ev" ;; esac
59
+ fi
60
+ done < <(jq -r '.hooks as $h |
61
+ ["SessionStart","UserPromptSubmit","PreToolUse","PermissionRequest","PostToolUse","Stop"] as $events |
62
+ $events[] as $e |
63
+ [$h[$e][]?.hooks[]?.command // empty
64
+ | select(contains("tmux-ai-status codex")) | split(" ")[0]] as $scripts |
65
+ select(($scripts | length) > 0) |
66
+ $scripts[] | "\($e)\t\(.)"' "$ADAPTER_HOOKS_FILE" 2>/dev/null)
67
+ for ev in SessionStart UserPromptSubmit PreToolUse PermissionRequest PostToolUse Stop; do
68
+ case ",$ok_events," in *",$ev,"*) ;; *) return 1 ;; esac
69
+ done
70
+ return 0
47
71
  }
48
72
 
49
73
  # Codex 6 事件(全部同步——0.144 静默跳过 async=true 的 hook)
@@ -73,11 +97,17 @@ _codex_read_existing() {
73
97
  }
74
98
 
75
99
  # 安装:改写 hooks.json(剥离旧 managed group 后为每事件追加本插件 group),保留他人 hook。
100
+ # no-op if valid:事件齐全且脚本存活时不改写——重写即 trusted_hash 失效(command 路径
101
+ # 变化 → codex 启动要重新 Trust all),跨安装源重复安装因此保持既有授信。
76
102
  # 清理旧 hooks.toml(0.144 不再读取)。trust 无法在 bash 复现,靠用户首启 codex TUI 授信。
77
103
  adapter_install_hooks() {
78
104
  _install_require_jq
79
105
  mkdir -p "$(dirname "$ADAPTER_HOOKS_FILE")" 2>/dev/null || true
80
106
  [ -f "$_CODEX_LEGACY_TOML" ] && rm -f "$_CODEX_LEGACY_TOML" && echo "移除旧 hooks.toml(0.144 不再读取)"
107
+ if adapter_check_integrity; then
108
+ echo "Codex hooks already installed and valid at $ADAPTER_HOOKS_FILE (no-op, trust preserved)"
109
+ return 0
110
+ fi
81
111
  local hook_script="${_LIB_DIR}/tmux-ai-status"
82
112
  local new_json
83
113
  new_json=$(_codex_read_existing | _codex_strip_managed | jq \
@@ -39,11 +39,17 @@ ADAPTER_INSTALLER="${_LIB_DIR}/install-opencode-hooks.sh"
39
39
 
40
40
  _OPENCODE_MARKER="tmux-ai-status (managed by tmux-ai-hooks-status)"
41
41
 
42
- # 完整性检查:plugin 文件存在且含本插件标记 + 指向当前仓库的 hook 脚本路径。
42
+ # 完整性检查:plugin 文件存在 + 本插件标记 + 结构指纹 + HOOK_SCRIPT 指向的脚本存活。
43
+ # 路径无关(不比对当前 _LIB_DIR——跨安装源的合法 plugin 同样有效);
44
+ # 脚本死路径(npm 包卸载/换路径)→ missing → 重装收敛。
43
45
  adapter_check_integrity() {
44
46
  [ -f "$ADAPTER_HOOKS_FILE" ] || return 1
45
47
  grep -q "$_OPENCODE_MARKER" "$ADAPTER_HOOKS_FILE" 2>/dev/null || return 1
46
- grep -q "${_LIB_DIR}/tmux-ai-status" "$ADAPTER_HOOKS_FILE" 2>/dev/null || return 1
48
+ local script_path
49
+ script_path=$(sed -n 's/^const HOOK_SCRIPT = "\(.*\)"$/\1/p' "$ADAPTER_HOOKS_FILE" 2>/dev/null | head -n 1)
50
+ if [ -z "$script_path" ] || [ ! -x "$script_path" ]; then
51
+ return 1
52
+ fi
47
53
  grep -q 'case "permission.asked"' "$ADAPTER_HOOKS_FILE" 2>/dev/null || return 1
48
54
  grep -q 'case "question.asked"' "$ADAPTER_HOOKS_FILE" 2>/dev/null || return 1
49
55
  grep -q 'case "permission.replied"' "$ADAPTER_HOOKS_FILE" 2>/dev/null || return 1
@@ -142,7 +148,12 @@ EOF
142
148
  }
143
149
 
144
150
  # 安装:写出/覆盖 plugin 文件。整文件替换(非合并)——同目录下他人 plugin 文件不受影响。
151
+ # no-op if valid:文件完整且 HOOK_SCRIPT 存活时不重写(避免跨安装源无谓覆盖)。
145
152
  adapter_install_hooks() {
153
+ if adapter_check_integrity; then
154
+ echo "opencode plugin already installed and valid at $ADAPTER_HOOKS_FILE (no-op)"
155
+ return 0
156
+ fi
146
157
  mkdir -p "$(dirname "$ADAPTER_HOOKS_FILE")" 2>/dev/null || true
147
158
  local hook_script="${_LIB_DIR}/tmux-ai-status"
148
159
  local content
@@ -1,11 +1,13 @@
1
1
  #!/bin/bash
2
2
  # install-claude-hooks.sh — Claude Code hooks 安装/卸载/检查 wrapper
3
- # 用法: install-claude-hooks.sh [uninstall|check]
3
+ # 用法: install-claude-hooks.sh [install|uninstall|check|auto]
4
4
  #
5
5
  # 薄 wrapper:source 公共骨架 + claude adapter,dispatch 到 adapter_install_hooks /
6
6
  # adapter_uninstall_hooks。实际逻辑在 scripts/adapters/claude.sh。
7
7
  # check:输出 ok/missing(exit 0/1),供 TUI 侦测在 cc-switch 模式下走 bash 侧
8
8
  # 完整性判定(sqlite 真源),与非 cc-switch 的文件判定共用同一真相。
9
+ # auto:tmux 入口加载时的自动安装——存在卸载意图标记(suppressed-claude)时不装。
10
+ # 显式 uninstall 成功写标记、显式 install 成功清标记(见 lib-install-hooks.sh)。
9
11
 
10
12
  set -o errexit
11
13
  set -o pipefail
@@ -14,15 +16,28 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
14
16
  source "${_LIB_DIR}/lib-install-hooks.sh"
15
17
  source "${_LIB_DIR}/adapters/claude.sh"
16
18
 
17
- if [ "${1:-install}" = "uninstall" ]; then
18
- adapter_uninstall_hooks
19
- elif [ "${1:-}" = "check" ]; then
20
- if adapter_check_integrity; then
21
- echo ok
22
- else
23
- echo missing
24
- exit 1
25
- fi
26
- else
27
- adapter_install_hooks
28
- fi
19
+ case "${1:-install}" in
20
+ uninstall)
21
+ adapter_uninstall_hooks
22
+ _hooks_suppress_write claude
23
+ ;;
24
+ check)
25
+ if adapter_check_integrity; then
26
+ echo ok
27
+ else
28
+ echo missing
29
+ exit 1
30
+ fi
31
+ ;;
32
+ auto)
33
+ if [ -f "$(_hooks_suppress_file claude)" ]; then
34
+ exit 0
35
+ fi
36
+ adapter_install_hooks
37
+ _hooks_suppress_clear claude
38
+ ;;
39
+ *)
40
+ adapter_install_hooks
41
+ _hooks_suppress_clear claude
42
+ ;;
43
+ esac
@@ -1,9 +1,11 @@
1
1
  #!/bin/bash
2
- # install-codex-hooks.sh — Codex CLI hooks 安装/卸载 wrapper(需 codex ≥ 0.144)
3
- # 用法: install-codex-hooks.sh [uninstall]
2
+ # install-codex-hooks.sh — Codex CLI hooks 安装/卸载/检查 wrapper(需 codex ≥ 0.144)
3
+ # 用法: install-codex-hooks.sh [install|uninstall|check]
4
4
  #
5
5
  # 薄 wrapper:source 公共骨架 + codex adapter,dispatch 到 adapter_install_hooks /
6
6
  # adapter_uninstall_hooks。实际逻辑在 scripts/adapters/codex.sh。
7
+ # check:输出 ok/missing(exit 0/1),供 TUI 侦测走 bash 侧完整性判定。
8
+ # 显式 uninstall 成功写标记、显式 install 成功清标记(见 lib-install-hooks.sh)。
7
9
 
8
10
  set -o errexit
9
11
  set -o pipefail
@@ -12,8 +14,21 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
12
14
  source "${_LIB_DIR}/lib-install-hooks.sh"
13
15
  source "${_LIB_DIR}/adapters/codex.sh"
14
16
 
15
- if [ "${1:-install}" = "uninstall" ]; then
16
- adapter_uninstall_hooks
17
- else
18
- adapter_install_hooks
19
- fi
17
+ case "${1:-install}" in
18
+ uninstall)
19
+ adapter_uninstall_hooks
20
+ _hooks_suppress_write codex
21
+ ;;
22
+ check)
23
+ if adapter_check_integrity; then
24
+ echo ok
25
+ else
26
+ echo missing
27
+ exit 1
28
+ fi
29
+ ;;
30
+ *)
31
+ adapter_install_hooks
32
+ _hooks_suppress_clear codex
33
+ ;;
34
+ esac
@@ -1,9 +1,11 @@
1
1
  #!/bin/bash
2
- # install-opencode-hooks.sh — opencode plugin 安装/卸载 wrapper
3
- # 用法: install-opencode-hooks.sh [uninstall]
2
+ # install-opencode-hooks.sh — opencode plugin 安装/卸载/检查 wrapper
3
+ # 用法: install-opencode-hooks.sh [install|uninstall|check]
4
4
  #
5
5
  # 薄 wrapper:source 公共骨架 + opencode adapter,dispatch 到 adapter_install_hooks /
6
6
  # adapter_uninstall_hooks。实际逻辑在 scripts/adapters/opencode.sh。
7
+ # check:输出 ok/missing(exit 0/1),供 TUI 侦测走 bash 侧完整性判定。
8
+ # 显式 uninstall 成功写标记、显式 install 成功清标记(见 lib-install-hooks.sh)。
7
9
 
8
10
  set -o errexit
9
11
  set -o pipefail
@@ -12,8 +14,21 @@ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
12
14
  source "${_LIB_DIR}/lib-install-hooks.sh"
13
15
  source "${_LIB_DIR}/adapters/opencode.sh"
14
16
 
15
- if [ "${1:-install}" = "uninstall" ]; then
16
- adapter_uninstall_hooks
17
- else
18
- adapter_install_hooks
19
- fi
17
+ case "${1:-install}" in
18
+ uninstall)
19
+ adapter_uninstall_hooks
20
+ _hooks_suppress_write opencode
21
+ ;;
22
+ check)
23
+ if adapter_check_integrity; then
24
+ echo ok
25
+ else
26
+ echo missing
27
+ exit 1
28
+ fi
29
+ ;;
30
+ *)
31
+ adapter_install_hooks
32
+ _hooks_suppress_clear opencode
33
+ ;;
34
+ esac
@@ -24,3 +24,16 @@ _install_atomic_write() {
24
24
  printf '%s\n' "$content" > "$tmp"
25
25
  mv -f "$tmp" "$target"
26
26
  }
27
+
28
+ # --- 卸载意图标记(suppression marker)---
29
+ # 显式 uninstall 成功 → 写 suppressed-<tool>;显式 install 成功 → 清。
30
+ # 自动修复路径(_maybe_repair_hooks / 入口 auto / SessionStart 重装)见标记一律不装。
31
+ # ⚠️ _SUPPRESS_DIR 与 lib-tmux-ai-status.sh 中的定义保持一致(两处同步改)。
32
+ _SUPPRESS_DIR="${TMUX_AI_SUPPRESS_DIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/tmuxclihook}"
33
+
34
+ _hooks_suppress_file() { printf '%s/suppressed-%s' "$_SUPPRESS_DIR" "$1"; }
35
+ _hooks_suppress_write() {
36
+ mkdir -p "$_SUPPRESS_DIR" 2>/dev/null || true
37
+ date +%s > "$(_hooks_suppress_file "$1")" 2>/dev/null || true
38
+ }
39
+ _hooks_suppress_clear() { rm -f "$(_hooks_suppress_file "$1")" 2>/dev/null || true; }
@@ -6,6 +6,8 @@
6
6
  _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
 
8
8
  _STATUS_DIR="${TMUX_AI_STATUS_DIR:-/tmp/ai-status}"
9
+ # 卸载意图标记目录(suppressed-<tool>)。⚠️ 与 lib-install-hooks.sh 中的定义保持一致(两处同步改)。
10
+ _SUPPRESS_DIR="${TMUX_AI_SUPPRESS_DIR:-${XDG_CONFIG_HOME:-${HOME}/.config}/tmuxclihook}"
9
11
  STATUS_COLOR="#F1FA8C"
10
12
 
11
13
  # --- 日志模块 ---
@@ -533,6 +535,10 @@ _check_hooks_integrity() {
533
535
  # 不依赖 SESSION_ID/EVENT,可被 _refresh 触发。60s 节流。
534
536
  # 安装器由 adapter 声明(ADAPTER_INSTALLER)。
535
537
  _maybe_repair_hooks() {
538
+ # 用户显式卸载意图(suppressed-<tool>)→ 不自动修复,尊重到显式安装为止。
539
+ if [ -f "${_SUPPRESS_DIR}/suppressed-${TOOL_ID:-claude}" ]; then
540
+ return 0
541
+ fi
536
542
  local marker="${_STATUS_DIR}/.hooks-repair-${TOOL_ID:-claude}-ts"
537
543
  local now
538
544
  now=$(date +%s)
@@ -178,8 +178,12 @@ case "$EVENT" in
178
178
  STATUS="-"
179
179
  _clear_pending
180
180
  if ! _check_hooks_integrity; then
181
- "${ADAPTER_INSTALLER}" >/dev/null 2>&1
182
- tmux display-message "⚠ ${TOOL_ID} hooks 已自动修复(检测到缺失)" 2>/dev/null || true
181
+ if [ -f "${_SUPPRESS_DIR}/suppressed-${TOOL_ID}" ]; then
182
+ _ai_log "REPAIR: suppressed by uninstall marker, skip (${TOOL_ID})"
183
+ else
184
+ "${ADAPTER_INSTALLER}" >/dev/null 2>&1
185
+ tmux display-message "⚠ ${TOOL_ID} hooks 已自动修复(检测到缺失)" 2>/dev/null || true
186
+ fi
183
187
  fi
184
188
  ;;
185
189
  SessionEnd)
@@ -45,6 +45,9 @@ function buildTools(scriptsDir) {
45
45
  installer: join(scriptsDir, 'install-codex-hooks.sh'),
46
46
  hookMarker: 'tmux-ai-status codex',
47
47
  minHookCount: 6,
48
+ // 无 cc-switch 真源(hooks.json 即文件真源),但统一走 wrapper check 以获得
49
+ // 脚本存活校验(死路径 → missing → repair 收敛),与 bash 侧同一份真相。
50
+ checkViaWrapper: true,
48
51
  },
49
52
  {
50
53
  id: 'opencode',
@@ -62,6 +65,9 @@ function buildTools(scriptsDir) {
62
65
  installer: join(scriptsDir, 'install-opencode-hooks.sh'),
63
66
  hookMarker: 'tmux-ai-status (managed by tmux-ai-hooks-status)',
64
67
  minHookCount: 1,
68
+ // 统一走 wrapper check:bash 侧含 HOOK_SCRIPT 存活校验(死路径 → missing →
69
+ // repair 收敛),JS 文件计数判不出死路径。
70
+ checkViaWrapper: true,
65
71
  },
66
72
  ];
67
73
  }
package/src/detect.js CHANGED
@@ -121,10 +121,11 @@ function hooksInstalled(tool) {
121
121
  }
122
122
  }
123
123
 
124
- // 异步判定:声明 checkViaWrapper 且 cc-switch db 存在时,走 bash wrapper check
125
- // (bash 侧按 sqlite 通用配置判定真源);否则回落本地文件计数。
124
+ // 异步判定:声明 checkViaWrapper 时走 bash wrapper check(cc-switch 模式在 bash 侧
125
+ // 按 sqlite 通用配置判定真源;所有 adapter check 均含脚本存活校验);未声明的工具
126
+ // 回落本地文件计数。
126
127
  async function hooksInstalledAsync(tool) {
127
- if (tool.checkViaWrapper && tool.ccSwitchDb && existsSync(tool.ccSwitchDb)) {
128
+ if (tool.checkViaWrapper && (!tool.ccSwitchDb || existsSync(tool.ccSwitchDb))) {
128
129
  try {
129
130
  const { stdout, exitCode } = await execa('bash', [tool.installer, 'check'], {
130
131
  reject: false,
package/src/update.js CHANGED
@@ -12,8 +12,9 @@ export const CURRENT_VERSION = packageJson.version;
12
12
  // `npm install -g` 在版本管理器(volta 等)环境下不会更新其管理的 shim:
13
13
  // volta 的 `~/.volta/bin` shim 指向 packages/ 镜像,而 npm -g 只写 node
14
14
  // 工具链目录,两者互不感知(实测 0.3.0 → 0.3.2 升级时用户命令仍解析到
15
- // 旧版)。install 成功后按 PATH 解析「用户实际会运行的那份」,尽力读取
16
- // 真实版本并与 registry latest 比对,无法确认或版本不符时打警告。
15
+ // 旧版)。生效收敛由 repairWithVolta 无条件完成(见下);本节自检只是
16
+ // 事后确认:按 update 进程的 PATH 解析一份,尽力读取真实版本并与
17
+ // registry latest 比对,无法确认或版本不符时打警告。
17
18
  // 验证是尽力而为的提示,不改变 update 的返回值(install 本身已成功)。
18
19
 
19
20
  function findOnPath(name, env) {
@@ -95,9 +96,12 @@ function warnNotEffective(log, result) {
95
96
 
96
97
  // volta 生效修正:`npm install -g` 只写 node 工具链目录,不更新 volta 的
97
98
  // shim 镜像(实测 volta 2.0.2:shim npm 的全局 prefix 就是工具链目录),
98
- // 用户的命令经 ~/.volta/bin shim 解析时仍是旧版。检测到 volta 可用时自动
99
- // 补一次 volta install 使更新生效。npm install -g 始终是安装语义本身,
100
- // volta 只在自检发现未生效时被自动调用,用户无需改变安装习惯。
99
+ // 用户的命令经 ~/.volta/bin shim 解析时仍是旧版。因此只要检测到 volta
100
+ // 就无条件补一次 volta install 完成生效(幂等,已是最新时秒回)。不走
101
+ // 「自检失败才修」的条件路径:自检按 update 进程自己的 PATH 找第一份,
102
+ // 可能恰好命中 npm -g 刚写的那份而误判 ok(实测 0.3.3 → 0.3.4:verify
103
+ // 判 ok,修复从未触发,用户命令仍是旧版)。npm install -g 是安装语义,
104
+ // volta install 是生效语义,两者都执行;用户无需改变安装习惯。
101
105
  export async function repairWithVolta(run, log, env = process.env) {
102
106
  const voltaHome = env.VOLTA_HOME || join(env.HOME || '', '.volta');
103
107
  const voltaBin = findOnPath('volta', env) || join(voltaHome, 'bin', 'volta');
@@ -149,19 +153,20 @@ export async function updatePackage(options = {}) {
149
153
 
150
154
  log.log(`Done. Updated to v${latestVersion}`);
151
155
 
156
+ // 生效收敛:有 volta 就无条件跑(幂等;无 volta 时静默跳过,纯 npm 场景
157
+ // npm -g 本身已生效)。任何命令行执行 update 都能升级生效的关键。
158
+ if (repairVolta) {
159
+ await repairVolta(run, log);
160
+ }
161
+
152
162
  if (verify) {
153
- let result = await verify(latestVersion);
154
- if (result.status !== 'ok') {
155
- // PATH 命中的副本未反映新版本(典型:volta shim 镜像未更新)——
156
- // 自动走 volta 完成生效步骤,然后复检。
157
- if (repairVolta && await repairVolta(run, log)) {
158
- result = await verify(latestVersion);
159
- }
160
- if (result.status === 'ok') {
161
- log.log(`Verified: tmuxclihook on your PATH now resolves to v${latestVersion}.`);
162
- } else {
163
- warnNotEffective(log, result);
164
- }
163
+ // 事后确认(尽力而为):两处落点都应已是新版,命中哪份都该 ok;
164
+ // 不 ok(如 PATH 上另有第三份旧安装)只警告,不影响返回值。
165
+ const result = await verify(latestVersion);
166
+ if (result.status === 'ok') {
167
+ log.log(`Verified: tmuxclihook on your PATH now resolves to v${latestVersion}.`);
168
+ } else {
169
+ warnNotEffective(log, result);
165
170
  }
166
171
  }
167
172
 
@@ -58,8 +58,10 @@ if [ -z "$(tmux show-option -gv @claude_hooks_reload_registered 2>/dev/null)" ];
58
58
  fi
59
59
 
60
60
  # --- 自动注册 Claude hooks(幂等,每次插件加载时确保 hooks 存在)---
61
+ # auto 模式:用户显式卸载过(suppressed-claude 标记在)时静默跳过,尊重卸载意图;
62
+ # 显式安装(prefix+C-h / TUI)才会清除标记恢复自动守护。
61
63
  # 注:codex/opencode hooks 不自动安装(非人人安装),用 prefix+M-h / prefix+C-p 手动装。
62
- "${CURRENT_DIR}/scripts/install-claude-hooks.sh" >/dev/null 2>&1 || true
64
+ "${CURRENT_DIR}/scripts/install-claude-hooks.sh" auto >/dev/null 2>&1 || true
63
65
 
64
66
  # --- 启动 tmux server 级状态监控器 ---
65
67
  # Codex/opencode 均无 SessionEnd(codex 的 SessionStart 还延迟到首个 turn);