tmuxclihook 0.3.1 → 0.3.3

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/README.md CHANGED
@@ -12,6 +12,10 @@ tmuxclihook
12
12
  # 检查 npm registry 并将全局安装更新到最新版
13
13
  tmuxclihook update
14
14
 
15
+ # 查看版本 / 帮助(version、--version、-v 与 help、--help、-h 等价)
16
+ tmuxclihook --version
17
+ tmuxclihook --help
18
+
15
19
  # 插件已经载入 tmux 后
16
20
  prefix + I
17
21
 
@@ -28,6 +32,8 @@ hooks 安装、tmux 集成写入的路径由「source」决定:环境变量 `T
28
32
 
29
33
  `tmuxclihook update` 会显示当前版本,通过 `npm view tmuxclihook version` 查询最新版;版本不同时直接执行 `npm install -g tmuxclihook@latest`,版本相同则不做修改。网络查询或安装失败时命令以非零状态退出。
30
34
 
35
+ 安装成功后会做一次生效自检:按 PATH 解析实际会被运行的 `tmuxclihook`,读取其真实版本与最新版比对。若本机使用 volta,`npm install -g` 只会更新 node 工具链目录里的副本、不会更新 volta 的 shim 镜像(实测 volta 2.0.2:shim npm 的全局 prefix 即工具链目录),自检发现这种情况时会自动补执行 `volta install tmuxclihook@latest` 使更新生效并复检;volta 缺失或修正失败时打警告并提示手动命令。`npm install` 始终是标准安装方式,无需因 volta 改变习惯。
36
+
31
37
  ## 功能
32
38
 
33
39
  | 菜单项 | 说明 |
package/bin/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  } from '../src/adapters-meta.js';
28
28
  import { setSource, clearSource, getPersistedSource, originLabel } from '../src/source.js';
29
29
  import { note, padDisplay } from '../src/tui.js';
30
- import { updatePackage } from '../src/update.js';
30
+ import { updatePackage, CURRENT_VERSION } from '../src/update.js';
31
31
 
32
32
  const OK = '✓';
33
33
  const NO = '✗';
@@ -365,13 +365,26 @@ async function main() {
365
365
  p.outro('完成');
366
366
  }
367
367
 
368
- if (process.argv[2] === 'update') {
368
+ const arg = process.argv[2];
369
+ if (arg === 'update') {
369
370
  updatePackage().then((ok) => {
370
371
  if (!ok) process.exitCode = 1;
371
372
  }).catch((err) => {
372
373
  console.error(err);
373
374
  process.exitCode = 1;
374
375
  });
376
+ } else if (arg === '--version' || arg === '-v' || arg === 'version') {
377
+ console.log(`tmuxclihook v${CURRENT_VERSION}`);
378
+ } else if (arg === '--help' || arg === '-h' || arg === 'help') {
379
+ console.log(
380
+ 'tmuxclihook — tmux-ai-status 交互式安装器\n' +
381
+ '\n' +
382
+ '用法:\n' +
383
+ ' tmuxclihook 打开交互式 TUI 安装器\n' +
384
+ ' tmuxclihook update 检查并更新到 npm 最新版本\n' +
385
+ ' tmuxclihook version 显示版本(--version / -v 等价)\n' +
386
+ ' tmuxclihook help 显示本帮助(--help / -h 等价)',
387
+ );
375
388
  } else {
376
389
  main().catch((err) => {
377
390
  console.error(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmuxclihook",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "交互式 TUI 安装器:环境侦测、AI CLI 侦测、tmux-ai-status hooks 装/卸/修、TPM 软链校验",
5
5
  "type": "module",
6
6
  "bin": {
package/src/update.js CHANGED
@@ -1,4 +1,5 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { readFileSync, accessSync, constants as fsConstants, realpathSync } from 'node:fs';
2
+ import { delimiter, dirname, join } from 'node:path';
2
3
  import { execa } from 'execa';
3
4
 
4
5
  const PACKAGE_NAME = 'tmuxclihook';
@@ -6,10 +7,122 @@ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.me
6
7
 
7
8
  export const CURRENT_VERSION = packageJson.version;
8
9
 
10
+ // —— 生效版本自检 ————————————————————————————————————————————————
11
+ //
12
+ // `npm install -g` 在版本管理器(volta 等)环境下不会更新其管理的 shim:
13
+ // volta 的 `~/.volta/bin` shim 指向 packages/ 镜像,而 npm -g 只写 node
14
+ // 工具链目录,两者互不感知(实测 0.3.0 → 0.3.2 升级时用户命令仍解析到
15
+ // 旧版)。install 成功后按 PATH 解析「用户实际会运行的那份」,尽力读取
16
+ // 真实版本并与 registry latest 比对,无法确认或版本不符时打警告。
17
+ // 验证是尽力而为的提示,不改变 update 的返回值(install 本身已成功)。
18
+
19
+ function findOnPath(name, env) {
20
+ for (const dir of (env.PATH || '').split(delimiter)) {
21
+ if (!dir) continue;
22
+ const candidate = join(dir, name);
23
+ try {
24
+ accessSync(candidate, fsConstants.X_OK);
25
+ return candidate;
26
+ } catch {
27
+ // 无此文件或不可执行,继续找下一个目录
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ // npm 布局下入口是 .../lib/node_modules/<pkg>/bin/cli.js(或其 symlink):
34
+ // realpath 后向上最多 3 层找 name 匹配的 package.json 读版本。版本管理器
35
+ // shim 是独立二进制,向上找不到包 → 返回 null。
36
+ function readOwnPackageVersion(entryPath) {
37
+ let real;
38
+ try {
39
+ real = realpathSync(entryPath);
40
+ } catch {
41
+ return null;
42
+ }
43
+ let dir = dirname(real);
44
+ for (let i = 0; i < 3; i += 1) {
45
+ try {
46
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
47
+ if (pkg.name === PACKAGE_NAME && typeof pkg.version === 'string') return pkg.version;
48
+ } catch {
49
+ // 该层无 package.json 或不可解析,继续向上
50
+ }
51
+ dir = dirname(dir);
52
+ }
53
+ return null;
54
+ }
55
+
56
+ // PATH 命中的是 shim 时,尝试 volta 的包镜像路径闭环确认(volta 实现细节,
57
+ // 读不到就放弃,不崩)。VOLTA_HOME 未设时用 ~/.volta。
58
+ function readVoltaImageVersion(env) {
59
+ const voltaHome = env.VOLTA_HOME || join(env.HOME || '', '.volta');
60
+ const pkgPath = join(
61
+ voltaHome, 'tools', 'image', 'packages', PACKAGE_NAME,
62
+ 'lib', 'node_modules', PACKAGE_NAME, 'package.json',
63
+ );
64
+ try {
65
+ return JSON.parse(readFileSync(pkgPath, 'utf8')).version || null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ export function verifyEffectiveVersion(latestVersion, env = process.env) {
72
+ const entry = findOnPath(PACKAGE_NAME, env);
73
+ if (!entry) return { status: 'unknown', path: null, version: null };
74
+
75
+ const ownVersion = readOwnPackageVersion(entry);
76
+ if (ownVersion) {
77
+ return { status: ownVersion === latestVersion ? 'ok' : 'mismatch', path: entry, version: ownVersion };
78
+ }
79
+
80
+ const voltaVersion = readVoltaImageVersion(env);
81
+ if (voltaVersion) {
82
+ return { status: voltaVersion === latestVersion ? 'ok' : 'mismatch', path: entry, version: voltaVersion };
83
+ }
84
+ return { status: 'unknown', path: entry, version: null };
85
+ }
86
+
87
+ function warnNotEffective(log, result) {
88
+ const where = result.path ? ` (${result.path})` : '';
89
+ log.error(
90
+ `Warning: the tmuxclihook command on your PATH${where} did not reflect the new version` +
91
+ (result.version ? ` (still v${result.version})` : '') + '.',
92
+ );
93
+ log.error(`If you use volta, run: volta install ${PACKAGE_NAME}@latest`);
94
+ }
95
+
96
+ // volta 生效修正:`npm install -g` 只写 node 工具链目录,不更新 volta 的
97
+ // shim 镜像(实测 volta 2.0.2:shim npm 的全局 prefix 就是工具链目录),
98
+ // 用户的命令经 ~/.volta/bin shim 解析时仍是旧版。检测到 volta 可用时自动
99
+ // 补一次 volta install 使更新生效。npm install -g 始终是安装语义本身,
100
+ // volta 只在自检发现未生效时被自动调用,用户无需改变安装习惯。
101
+ export async function repairWithVolta(run, log, env = process.env) {
102
+ const voltaHome = env.VOLTA_HOME || join(env.HOME || '', '.volta');
103
+ const voltaBin = findOnPath('volta', env) || join(voltaHome, 'bin', 'volta');
104
+ try {
105
+ accessSync(voltaBin, fsConstants.X_OK);
106
+ } catch {
107
+ return false;
108
+ }
109
+ log.log('Detected volta on this machine; applying the update via volta install...');
110
+ try {
111
+ await run('volta', ['install', `${PACKAGE_NAME}@latest`], { stdio: 'inherit' });
112
+ return true;
113
+ } catch (err) {
114
+ log.error(`volta install failed: ${err}`);
115
+ return false;
116
+ }
117
+ }
118
+
9
119
  export async function updatePackage(options = {}) {
10
120
  const run = options.run || execa;
11
121
  const log = options.log || console;
12
122
  const currentVersion = options.currentVersion || CURRENT_VERSION;
123
+ // 显式传 null 跳过自检/修正(测试用);默认真实现
124
+ const verify = options.verify === undefined ? verifyEffectiveVersion : options.verify;
125
+ const repairVolta = options.repairVolta === undefined ? repairWithVolta : options.repairVolta;
13
126
 
14
127
  log.log(`Current version: v${currentVersion}`);
15
128
 
@@ -35,5 +148,22 @@ export async function updatePackage(options = {}) {
35
148
  }
36
149
 
37
150
  log.log(`Done. Updated to v${latestVersion}`);
151
+
152
+ 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
+ }
165
+ }
166
+ }
167
+
38
168
  return true;
39
169
  }
@@ -73,10 +73,10 @@ fi
73
73
  # client-session-changed: attach 与 switch-client 的共同汇聚点 = 「进入」→ 记
74
74
  # @ai_entered_time(排序用:刚进入的 session 排最后;handler 内以 per-client
75
75
  # 上次 session 对比过滤自指 switch)+ 刷新聚合
76
+ tmux set-hook -g client-session-changed "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _session-switched #{hook_client_name}'" 2>/dev/null || true
76
77
  # 注:_refresh 与 TOOL_ID 无关(仅重建聚合 + 自修复 claude hooks),统一传 claude。
77
78
  tmux set-hook -g session-closed "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _refresh'"
78
79
  tmux set-hook -g client-detached "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _refresh'"
79
- tmux set-hook -g client-session-changed "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _session-switched #{hook_client_name}'" 2>/dev/null || true
80
80
  # (client-attached 不再注册:attach 场景由 client-session-changed 覆盖)
81
81
  # pane-exited: 程序自然退出 → 触发孤儿清理(#{pane_id} 在此 hook 中不可靠)
82
82
  tmux set-hook -g pane-exited "run-shell '${CURRENT_DIR}/scripts/tmux-ai-status claude _refresh'"