token-tracker 0.4.8__tar.gz → 0.4.9__tar.gz
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.
- {token_tracker-0.4.8/src/token_tracker.egg-info → token_tracker-0.4.9}/PKG-INFO +20 -3
- {token_tracker-0.4.8 → token_tracker-0.4.9}/README.md +18 -2
- {token_tracker-0.4.8 → token_tracker-0.4.9}/pyproject.toml +6 -1
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/config.py +2 -1
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/hooks.py +120 -88
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/i18n.py +14 -0
- token_tracker-0.4.9/src/token_tracker/iterm_split.py +233 -0
- token_tracker-0.4.9/src/token_tracker/sidebar_command.py +181 -0
- token_tracker-0.4.9/src/token_tracker/sidebar_install.py +336 -0
- token_tracker-0.4.9/src/token_tracker/skills/__init__.py +1 -0
- token_tracker-0.4.9/src/token_tracker/skills/tt_sidebar/SKILL.md +18 -0
- token_tracker-0.4.9/src/token_tracker/skills/tt_sidebar/__init__.py +1 -0
- token_tracker-0.4.9/src/token_tracker/skills/tt_sidebar/agents/openai.yaml +6 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9/src/token_tracker.egg-info}/PKG-INFO +20 -3
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker.egg-info/SOURCES.txt +10 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker.egg-info/requires.txt +3 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_hooks.py +144 -77
- token_tracker-0.4.9/tests/test_iterm_split.py +101 -0
- token_tracker-0.4.9/tests/test_sidebar_command.py +124 -0
- token_tracker-0.4.9/tests/test_sidebar_install.py +175 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/LICENSE +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/setup.cfg +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/__init__.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/__init__.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/claude.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/codex.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/rate_limits.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/registry.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/types.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/adapters/util.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/analyzer/__init__.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/analyzer/aggregator.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/analyzer/cost.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/cli.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/sidebar.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/sidebar_events.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/templates/__init__.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/templates/claude_statusline.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/templates/codex_statusline.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/tz.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/__init__.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/console.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/format.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/heatmap.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/report_stats.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/sidebar.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/sidebar_app.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/status.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/tables.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/theme.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/ui/themes.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker/wizard.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker.egg-info/dependency_links.txt +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker.egg-info/entry_points.txt +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/src/token_tracker.egg-info/top_level.txt +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_aggregator.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_cli.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_codex.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_cost.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_heatmap.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_sidebar.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_sidebar_app.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_status.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_tables.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_theme.py +0 -0
- {token_tracker-0.4.8 → token_tracker-0.4.9}/tests/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: token-tracker
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: Track token usage across local AI agents (Claude Code, Codex)
|
|
5
5
|
Author-email: stormzhang <stormzhang.dev@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -44,6 +44,7 @@ License-File: LICENSE
|
|
|
44
44
|
Requires-Dist: rich>=13.7
|
|
45
45
|
Requires-Dist: questionary<3,>=2.0
|
|
46
46
|
Requires-Dist: textual>=1.0
|
|
47
|
+
Requires-Dist: iterm2>=2.9; sys_platform == "darwin"
|
|
47
48
|
Provides-Extra: test
|
|
48
49
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
49
50
|
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
|
|
@@ -71,6 +72,7 @@ Dynamic: license-file
|
|
|
71
72
|
- **多 Agent 统一追踪** — Claude Code + Codex 统一读取,多 Agent 按来源分组
|
|
72
73
|
- **状态栏集成** — Claude Code 用官方 StatusLine 接口;**Codex 业界首创伪 statusline 方案**(hook 注入两行真彩色状态栏,把官方未开放的能力在 Codex 里做了出来)
|
|
73
74
|
- **实时侧边栏** — `tt sidebar` 窄窗格常驻面板:全部活跃会话一屏总览(状态灯 + 最近提示词 + 「下一步」建议),点击会话直达对应 iTerm2 / tmux 窗格
|
|
75
|
+
- **当前会话自动分屏** — Codex 中显式执行 `$tt-sidebar`,在原会话右侧自动打开 1/3 宽度的独立提示词侧边栏
|
|
74
76
|
- **限额监控** — 实时 5h / 7d 配额百分比 + 重置倒计时
|
|
75
77
|
- **多维成本分析** — 会话 / 日 / 周 / 月多维报表,等效成本统计
|
|
76
78
|
- **定价识别** — litellm 在线定价 + 内置官方价双层兜底,覆盖 Claude / OpenAI / Gemini / Grok 及国产主流(Kimi / GLM / Qwen / 豆包 / DeepSeek / MiniMax / MiMo);新模型自动套用同系列定价、不静默归零
|
|
@@ -157,6 +159,21 @@ Codex 官方暂不支持自定义 StatusLine。Token Tracker 通过 hook 注入
|
|
|
157
159
|
|
|
158
160
|
操作:鼠标拖拽选择文字,松开后自动复制;滚轮 / 方向键 / PgUp/PgDn 滚动,`q` / `Esc` / `Ctrl+C` 退出;`tt sidebar --once` 打印一帧快照即退。数据每 5s 刷新,只读本地会话记录、不写任何产物;加 `--claude` / `--codex` 可只看单个 agent。
|
|
159
161
|
|
|
162
|
+
### Codex 当前会话自动 1/3 分屏(`$tt-sidebar`)
|
|
163
|
+
|
|
164
|
+
`tt setup` 会把 `$tt-sidebar` 安装为用户级 Codex Skill。它与普通 `tt sidebar` 相互独立:普通命令继续显示全部活跃会话;Skill 只显示发起命令的当前 Codex 会话,右侧自动占 1/3 宽度,完整提示词按时间倒序展示,最新永远置顶。
|
|
165
|
+
|
|
166
|
+
在 Codex 输入:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
$tt-sidebar
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- 支持 iTerm2(需在 iTerm2 设置中启用 Python API)与 tmux;原会话窗格保持焦点。
|
|
173
|
+
- `tt setup` 把 Codex 的伪 statusline `Stop` 与 sidebar `UserPromptSubmit` 统一安装到用户级 `hooks.json`;后者用本地 FIFO 把新提示词推给已打开的分屏,无 sidebar 时立即返回,不轮询 transcript、不上传或持久化提示词。
|
|
174
|
+
- Codex 会要求审查非托管 Hook:安装后运行 `/hooks`,信任 Token Tracker 对应项。Skill 未立即出现时重启 Codex。
|
|
175
|
+
- `tt unsetup` 会一并移除 Token Tracker 管理的 Skill 与 Hook;若 `~/.agents/skills/tt-sidebar` 已是用户自己的同名 Skill,安装与卸载都不会覆盖它。
|
|
176
|
+
|
|
160
177
|
## 安装
|
|
161
178
|
|
|
162
179
|
```bash
|
|
@@ -165,7 +182,7 @@ curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/instal
|
|
|
165
182
|
|
|
166
183
|
脚本自动选最优安装方式(uv / pipx / 私有 venv),绕开 PEP 668、不污染系统 Python。
|
|
167
184
|
|
|
168
|
-
>
|
|
185
|
+
> **升级**:重跑上面的命令即可(脚本幂等、自动升到最新)。包含新 Agent 集成的版本升级后,再运行一次 `tt setup`;例如 `$tt-sidebar` 需要由 setup 安装到 Codex 用户级 Skill 目录。
|
|
169
186
|
> **卸载**:`tt unsetup`
|
|
170
187
|
|
|
171
188
|
**升级后 `tt --version` 还是旧版?** 多半是旧版装在别的 Python 环境里遮蔽了新版(常见于 Windows、或早期用 `pip install` 装过)。卸载旧版后重装一次即可:
|
|
@@ -178,7 +195,7 @@ curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/instal
|
|
|
178
195
|
## 使用
|
|
179
196
|
|
|
180
197
|
```bash
|
|
181
|
-
tt setup #
|
|
198
|
+
tt setup # 配置状态栏,并安装 Codex $tt-sidebar Skill / 提示词 Hook
|
|
182
199
|
tt # 过去一年 token 热力图 + 顶部三段概览(= tt daily)
|
|
183
200
|
tt daily # 同上(tt 无参即进 daily)
|
|
184
201
|
tt status # 今日消耗、5h/7d 额度与今日会话
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- **多 Agent 统一追踪** — Claude Code + Codex 统一读取,多 Agent 按来源分组
|
|
16
16
|
- **状态栏集成** — Claude Code 用官方 StatusLine 接口;**Codex 业界首创伪 statusline 方案**(hook 注入两行真彩色状态栏,把官方未开放的能力在 Codex 里做了出来)
|
|
17
17
|
- **实时侧边栏** — `tt sidebar` 窄窗格常驻面板:全部活跃会话一屏总览(状态灯 + 最近提示词 + 「下一步」建议),点击会话直达对应 iTerm2 / tmux 窗格
|
|
18
|
+
- **当前会话自动分屏** — Codex 中显式执行 `$tt-sidebar`,在原会话右侧自动打开 1/3 宽度的独立提示词侧边栏
|
|
18
19
|
- **限额监控** — 实时 5h / 7d 配额百分比 + 重置倒计时
|
|
19
20
|
- **多维成本分析** — 会话 / 日 / 周 / 月多维报表,等效成本统计
|
|
20
21
|
- **定价识别** — litellm 在线定价 + 内置官方价双层兜底,覆盖 Claude / OpenAI / Gemini / Grok 及国产主流(Kimi / GLM / Qwen / 豆包 / DeepSeek / MiniMax / MiMo);新模型自动套用同系列定价、不静默归零
|
|
@@ -101,6 +102,21 @@ Codex 官方暂不支持自定义 StatusLine。Token Tracker 通过 hook 注入
|
|
|
101
102
|
|
|
102
103
|
操作:鼠标拖拽选择文字,松开后自动复制;滚轮 / 方向键 / PgUp/PgDn 滚动,`q` / `Esc` / `Ctrl+C` 退出;`tt sidebar --once` 打印一帧快照即退。数据每 5s 刷新,只读本地会话记录、不写任何产物;加 `--claude` / `--codex` 可只看单个 agent。
|
|
103
104
|
|
|
105
|
+
### Codex 当前会话自动 1/3 分屏(`$tt-sidebar`)
|
|
106
|
+
|
|
107
|
+
`tt setup` 会把 `$tt-sidebar` 安装为用户级 Codex Skill。它与普通 `tt sidebar` 相互独立:普通命令继续显示全部活跃会话;Skill 只显示发起命令的当前 Codex 会话,右侧自动占 1/3 宽度,完整提示词按时间倒序展示,最新永远置顶。
|
|
108
|
+
|
|
109
|
+
在 Codex 输入:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
$tt-sidebar
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- 支持 iTerm2(需在 iTerm2 设置中启用 Python API)与 tmux;原会话窗格保持焦点。
|
|
116
|
+
- `tt setup` 把 Codex 的伪 statusline `Stop` 与 sidebar `UserPromptSubmit` 统一安装到用户级 `hooks.json`;后者用本地 FIFO 把新提示词推给已打开的分屏,无 sidebar 时立即返回,不轮询 transcript、不上传或持久化提示词。
|
|
117
|
+
- Codex 会要求审查非托管 Hook:安装后运行 `/hooks`,信任 Token Tracker 对应项。Skill 未立即出现时重启 Codex。
|
|
118
|
+
- `tt unsetup` 会一并移除 Token Tracker 管理的 Skill 与 Hook;若 `~/.agents/skills/tt-sidebar` 已是用户自己的同名 Skill,安装与卸载都不会覆盖它。
|
|
119
|
+
|
|
104
120
|
## 安装
|
|
105
121
|
|
|
106
122
|
```bash
|
|
@@ -109,7 +125,7 @@ curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/instal
|
|
|
109
125
|
|
|
110
126
|
脚本自动选最优安装方式(uv / pipx / 私有 venv),绕开 PEP 668、不污染系统 Python。
|
|
111
127
|
|
|
112
|
-
>
|
|
128
|
+
> **升级**:重跑上面的命令即可(脚本幂等、自动升到最新)。包含新 Agent 集成的版本升级后,再运行一次 `tt setup`;例如 `$tt-sidebar` 需要由 setup 安装到 Codex 用户级 Skill 目录。
|
|
113
129
|
> **卸载**:`tt unsetup`
|
|
114
130
|
|
|
115
131
|
**升级后 `tt --version` 还是旧版?** 多半是旧版装在别的 Python 环境里遮蔽了新版(常见于 Windows、或早期用 `pip install` 装过)。卸载旧版后重装一次即可:
|
|
@@ -122,7 +138,7 @@ curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/instal
|
|
|
122
138
|
## 使用
|
|
123
139
|
|
|
124
140
|
```bash
|
|
125
|
-
tt setup #
|
|
141
|
+
tt setup # 配置状态栏,并安装 Codex $tt-sidebar Skill / 提示词 Hook
|
|
126
142
|
tt # 过去一年 token 热力图 + 顶部三段概览(= tt daily)
|
|
127
143
|
tt daily # 同上(tt 无参即进 daily)
|
|
128
144
|
tt status # 今日消耗、5h/7d 额度与今日会话
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "token-tracker"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.9"
|
|
8
8
|
description = "Track token usage across local AI agents (Claude Code, Codex)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -30,6 +30,8 @@ dependencies = [
|
|
|
30
30
|
"questionary>=2.0,<3",
|
|
31
31
|
# 仅 tt sidebar live 模式用(cli 延迟 import,日常 tt 启动不加载);与 rich 同门,渲染对象直接复用
|
|
32
32
|
"textual>=1.0",
|
|
33
|
+
# 仅 macOS 的 $tt-sidebar 自动 iTerm2 分屏 worker 加载;其它平台走 tmux、不安装
|
|
34
|
+
"iterm2>=2.9; sys_platform == 'darwin'",
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
[project.optional-dependencies]
|
|
@@ -55,6 +57,9 @@ tt = "token_tracker.cli:main"
|
|
|
55
57
|
[tool.setuptools.packages.find]
|
|
56
58
|
where = ["src"]
|
|
57
59
|
|
|
60
|
+
[tool.setuptools.package-data]
|
|
61
|
+
"token_tracker.skills.tt_sidebar" = ["SKILL.md", "agents/*.yaml"]
|
|
62
|
+
|
|
58
63
|
[tool.ruff]
|
|
59
64
|
line-length = 120
|
|
60
65
|
target-version = "py311"
|
|
@@ -29,9 +29,10 @@ SCHEMA_VERSION = 1
|
|
|
29
29
|
# 老用户 config 里没这字段 / 旧版本号 < 当前 → 触发重新引导(真终端弹 wizard、非 tty 静默 _auto_setup)。
|
|
30
30
|
# 跟 SCHEMA_VERSION 解耦:那是数据格式版本,这是用户引导版本,bump 节奏完全不同。
|
|
31
31
|
# 2(0.4.2):强制所有现存用户(0.3.8/0.4.0=无字段=0、0.4.1=1,全 < 2)升级后重走一遍 setup。
|
|
32
|
+
# 3:安装随包分发的用户级 Codex $tt-sidebar Skill + UserPromptSubmit hook。
|
|
32
33
|
# 注:CC statusLine 变可选组件(issue #16/#17)时决定**不 bump**——小众需求不打断存量用户,
|
|
33
34
|
# 存量 tt 用户 intent 缺失时由 is_setup 按「statusLine 已是 tt 的」推断为已配,想改的手动 tt setup。
|
|
34
|
-
SETUP_VERSION =
|
|
35
|
+
SETUP_VERSION = 3
|
|
35
36
|
|
|
36
37
|
# 旧位置(独立 theme.json / lang.json),老用户首次读 config.json 不存在时自动合并迁移
|
|
37
38
|
_LEGACY_THEME_PATH = os.path.join(CONFIG_DIR, "theme.json")
|
|
@@ -7,7 +7,7 @@ import tomllib
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from importlib import resources
|
|
9
9
|
|
|
10
|
-
from . import config
|
|
10
|
+
from . import config, sidebar_install
|
|
11
11
|
from .adapters.util import claude_home, codex_home
|
|
12
12
|
from .i18n import t
|
|
13
13
|
from .ui import themes
|
|
@@ -35,7 +35,7 @@ _TT = config.CONFIG_DIR # ~/.config/token-tracker
|
|
|
35
35
|
CLAUDE_SETTINGS = os.path.join(_CLAUDE, "settings.json") # 改 Claude Code 配置,留 agent 目录
|
|
36
36
|
HOOK_SCRIPT_PATH = os.path.join(_TT, "claude-statusline.py")
|
|
37
37
|
CODEX_DIR = _CODEX
|
|
38
|
-
CODEX_CONFIG = os.path.join(CODEX_DIR, "config.toml") #
|
|
38
|
+
CODEX_CONFIG = os.path.join(CODEX_DIR, "config.toml") # 仅迁移旧内联 Stop;新 Hook 统一写 hooks.json
|
|
39
39
|
CODEX_STATUSLINE_HOOK_PATH = os.path.join(_TT, "codex-statusline.py")
|
|
40
40
|
STATUS_FILE = config.STATUS_FILE # CC statusline 缓存(单一权威定义在 config)
|
|
41
41
|
TERMINAL_MAP_FILE = config.TERMINAL_MAP_FILE # Codex Stop hook 采集的终端定位映射
|
|
@@ -61,7 +61,6 @@ def _load_template(name: str) -> str:
|
|
|
61
61
|
return (resources.files("token_tracker.templates") / name).read_text(encoding="utf-8")
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
|
|
65
64
|
# --- helpers ---
|
|
66
65
|
|
|
67
66
|
def _render_hook_script() -> str:
|
|
@@ -106,7 +105,8 @@ def _installed_codex_statusline_version() -> str | None:
|
|
|
106
105
|
return None
|
|
107
106
|
|
|
108
107
|
|
|
109
|
-
# 卸载时定位 tt
|
|
108
|
+
# 迁移 / 卸载时定位 tt 旧版追加的整段 [[hooks.Stop]]——
|
|
109
|
+
# 同时认新(codex-statusline)/ 旧(tt-statusline)两种特征码。
|
|
110
110
|
# command 值兼容三代形态:双引号 basic string(最老)、单引号 literal 裸拼接(0.4.x)、
|
|
111
111
|
# 单引号 literal 内含双引号包裹(现行,防路径空格断词,与 CC 侧 #13/#14 同一治法)
|
|
112
112
|
_CODEX_STATUSLINE_REGEX = re.compile(
|
|
@@ -118,54 +118,23 @@ _CODEX_STATUSLINE_REGEX = re.compile(
|
|
|
118
118
|
r'timeout = \d+\s*'
|
|
119
119
|
)
|
|
120
120
|
|
|
121
|
-
# tt Stop hook 的 command 值(去掉外层 TOML 引号);与删除正则同一套三代形态
|
|
122
|
-
_CODEX_COMMAND_REGEX = re.compile(
|
|
123
|
-
r'command = ("[^"\n]*(?:codex-statusline|tt-statusline)[^"\n]*"'
|
|
124
|
-
r"|'[^'\n]*(?:codex-statusline|tt-statusline)[^'\n]*')"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
121
|
|
|
128
122
|
def _has_tt_codex_statusline(content: str) -> bool:
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
return m.group(1)[1:-1] if m else None
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _install_codex_statusline(content: str, python: str) -> str:
|
|
139
|
-
"""落盘 Codex statusline 脚本 + 在 config.toml 末尾追加 Stop hook 段。
|
|
140
|
-
command 与 CC 侧同一拼法(_build_cc_command:双引号包路径 + Windows 正斜杠,#13/#14 同治)。
|
|
141
|
-
- 已有段的 command 与本次要写的完全一致 → 幂等返回;
|
|
142
|
-
- 不一致(python 升级/换环境、老名 tt-statusline、旧裸拼接格式)→ 删旧段装新段,
|
|
143
|
-
避免 command 指向已死 python 或在含空格路径上断词(症状:状态栏静默半残)。"""
|
|
144
|
-
_write_codex_statusline_script()
|
|
145
|
-
cmd = _build_cc_command(python, CODEX_STATUSLINE_HOOK_PATH)
|
|
146
|
-
if _has_tt_codex_statusline(content):
|
|
147
|
-
if _codex_hook_command(content) == cmd:
|
|
148
|
-
return content # 新格式 + python/脚本路径一致 → 幂等
|
|
149
|
-
content = _CODEX_STATUSLINE_REGEX.sub("\n", content) # 其余一律删旧装新
|
|
150
|
-
return content.rstrip() + (
|
|
151
|
-
"\n\n[[hooks.Stop]]\n\n"
|
|
152
|
-
"[[hooks.Stop.hooks]]\n"
|
|
153
|
-
'type = "command"\n'
|
|
154
|
-
# 用 TOML literal string(单引号)包裹 command,避免 Windows 反斜杠路径被当转义符
|
|
155
|
-
# 解析失败(如 `C:\Users\...` 里的 `\U` 被识别为 unicode 转义起始);
|
|
156
|
-
# 值内的双引号来自 _build_cc_command 的路径包裹,literal string 内原样合法
|
|
157
|
-
f"command = '{cmd}'\n"
|
|
158
|
-
"timeout = 10\n"
|
|
159
|
-
)
|
|
123
|
+
return _CODEX_STATUSLINE_REGEX.search(content) is not None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _migrate_codex_statusline_config(content: str) -> str:
|
|
127
|
+
"""只移除 Token Tracker 的旧内联 Stop 段;[hooks.state] 与用户其它 TOML 原样保留。"""
|
|
128
|
+
return _CODEX_STATUSLINE_REGEX.sub("\n", content)
|
|
160
129
|
|
|
161
130
|
|
|
162
131
|
def _uninstall_codex_statusline(content: str) -> str:
|
|
163
|
-
"""删 Codex statusline
|
|
132
|
+
"""删 Codex statusline 运行产物 + 旧内联 Stop 段(hooks.json 由统一 installer 管理)。"""
|
|
164
133
|
if os.path.exists(CODEX_STATUSLINE_HOOK_PATH):
|
|
165
134
|
os.remove(CODEX_STATUSLINE_HOOK_PATH)
|
|
166
135
|
if os.path.exists(TERMINAL_MAP_FILE):
|
|
167
136
|
os.remove(TERMINAL_MAP_FILE)
|
|
168
|
-
return
|
|
137
|
+
return _migrate_codex_statusline_config(content)
|
|
169
138
|
|
|
170
139
|
|
|
171
140
|
def _read_codex_config() -> tuple[str, dict] | None:
|
|
@@ -177,17 +146,26 @@ def _read_codex_config() -> tuple[str, dict] | None:
|
|
|
177
146
|
return None
|
|
178
147
|
|
|
179
148
|
|
|
149
|
+
def _codex_statusline_command(python: str | None = None) -> str:
|
|
150
|
+
return _build_cc_command(python or sys.executable or "python3", CODEX_STATUSLINE_HOOK_PATH)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _inline_codex_statusline_present() -> bool:
|
|
154
|
+
result = _read_codex_config()
|
|
155
|
+
return bool(result and _has_tt_codex_statusline(result[0]))
|
|
156
|
+
|
|
157
|
+
|
|
180
158
|
def codex_statusline_active() -> bool:
|
|
181
|
-
"""
|
|
159
|
+
"""双因素:用户意图 AND 实际装好。
|
|
160
|
+
|
|
161
|
+
迁移期兼容旧 config.toml 内联 Stop,让 needs_update() 能无打扰搬到 hooks.json;
|
|
162
|
+
新安装只认 hooks.json。
|
|
163
|
+
"""
|
|
182
164
|
if config.codex_faux_statusline_intent() is not True:
|
|
183
165
|
return False
|
|
184
166
|
if not os.path.exists(CODEX_STATUSLINE_HOOK_PATH):
|
|
185
167
|
return False
|
|
186
|
-
|
|
187
|
-
with open(CODEX_CONFIG, encoding="utf-8") as f:
|
|
188
|
-
return _has_tt_codex_statusline(f.read())
|
|
189
|
-
except OSError:
|
|
190
|
-
return False
|
|
168
|
+
return sidebar_install.statusline_hook_present() or _inline_codex_statusline_present()
|
|
191
169
|
|
|
192
170
|
|
|
193
171
|
def _settings_has_tt_statusline() -> bool:
|
|
@@ -355,26 +333,37 @@ def _sync_cc_command() -> None:
|
|
|
355
333
|
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
356
334
|
|
|
357
335
|
|
|
358
|
-
def
|
|
359
|
-
"""
|
|
360
|
-
与 CC 侧 #13/#14 同类问题,格式规则复用 _cc_command_outdated。没装 / 没段 → False。"""
|
|
361
|
-
result = _read_codex_config()
|
|
362
|
-
if not result:
|
|
363
|
-
return False
|
|
364
|
-
cmd = _codex_hook_command(result[0])
|
|
365
|
-
return cmd is not None and _cc_command_outdated(cmd)
|
|
336
|
+
def _sync_codex_managed_hooks(quiet: bool = False) -> bool:
|
|
337
|
+
"""把 Stop + UserPromptSubmit 统一同步到 hooks.json,再迁移旧内联 Stop。
|
|
366
338
|
|
|
339
|
+
先写 hooks.json、成功后才移除 config.toml 旧段,避免迁移中断导致伪 statusline 失效。
|
|
340
|
+
"""
|
|
341
|
+
p = (lambda *a, **k: None) if quiet else get_console().print
|
|
342
|
+
statusline_command = (
|
|
343
|
+
_codex_statusline_command()
|
|
344
|
+
if config.codex_faux_statusline_intent() is True
|
|
345
|
+
else None
|
|
346
|
+
)
|
|
347
|
+
try:
|
|
348
|
+
changed = sidebar_install.install_managed_hooks(statusline_command)
|
|
349
|
+
except ValueError:
|
|
350
|
+
get_console().print(
|
|
351
|
+
f"[red]{t('codex_hooks_corrupt', path=sidebar_install.CODEX_HOOKS)}[/red]"
|
|
352
|
+
)
|
|
353
|
+
return False
|
|
367
354
|
|
|
368
|
-
def _sync_codex_command() -> None:
|
|
369
|
-
"""把 config.toml 里 tt Stop hook 的 command 重写为新格式(删旧段装新段,其余内容不动)。"""
|
|
370
355
|
result = _read_codex_config()
|
|
371
|
-
if
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
356
|
+
if result:
|
|
357
|
+
content, _parsed = result
|
|
358
|
+
migrated = _migrate_codex_statusline_config(content)
|
|
359
|
+
if migrated != content:
|
|
360
|
+
with open(CODEX_CONFIG, "w", encoding="utf-8") as f:
|
|
361
|
+
f.write(migrated)
|
|
362
|
+
changed = True
|
|
363
|
+
if changed:
|
|
364
|
+
p(f"[green]✓[/green] {t('codex_hooks_synced')}")
|
|
365
|
+
p(f"[dim]{t('sidebar_hook_trust')}[/dim]")
|
|
366
|
+
return True
|
|
378
367
|
|
|
379
368
|
|
|
380
369
|
def needs_update() -> bool:
|
|
@@ -384,8 +373,20 @@ def needs_update() -> bool:
|
|
|
384
373
|
sv = _installed_codex_statusline_version()
|
|
385
374
|
if sv is not None and sv != STATUSLINE_HOOK_VERSION:
|
|
386
375
|
return True
|
|
387
|
-
|
|
388
|
-
|
|
376
|
+
# setup_version 3 起,用户级 $tt-sidebar Skill 与 UserPromptSubmit hook 也属于 setup 产物。
|
|
377
|
+
# 老用户(setup_version < 3)由 cli 的升级引导统一安装,不在这里抢跑。
|
|
378
|
+
if os.path.isdir(CODEX_DIR) and config.setup_version() >= 3:
|
|
379
|
+
statusline_command = (
|
|
380
|
+
_codex_statusline_command()
|
|
381
|
+
if config.codex_faux_statusline_intent() is True
|
|
382
|
+
else None
|
|
383
|
+
)
|
|
384
|
+
if (
|
|
385
|
+
sidebar_install.skill_needs_sync()
|
|
386
|
+
or sidebar_install.managed_hooks_need_sync(statusline_command)
|
|
387
|
+
or _inline_codex_statusline_present()
|
|
388
|
+
):
|
|
389
|
+
return True
|
|
389
390
|
return _cc_command_needs_sync() # settings.json 里 command 格式过时也算待更新(issue #13/#14)
|
|
390
391
|
|
|
391
392
|
|
|
@@ -396,8 +397,9 @@ def update_hook() -> None:
|
|
|
396
397
|
_write_codex_statusline_script()
|
|
397
398
|
if _cc_command_needs_sync():
|
|
398
399
|
_sync_cc_command()
|
|
399
|
-
if
|
|
400
|
-
|
|
400
|
+
if os.path.isdir(CODEX_DIR) and config.setup_version() >= 3:
|
|
401
|
+
_sync_codex_managed_hooks(quiet=True)
|
|
402
|
+
_setup_codex_sidebar(quiet=True)
|
|
401
403
|
|
|
402
404
|
|
|
403
405
|
# --- setup ---
|
|
@@ -431,6 +433,7 @@ def setup(auto: bool = False, components: SetupComponents | None = None, quiet:
|
|
|
431
433
|
|
|
432
434
|
if has_codex:
|
|
433
435
|
_setup_codex(components, quiet)
|
|
436
|
+
_setup_codex_sidebar(quiet)
|
|
434
437
|
else:
|
|
435
438
|
if not auto:
|
|
436
439
|
p(f"[dim]{t('codex_not_found')}[/dim]")
|
|
@@ -541,34 +544,46 @@ def _setup_claude(components: SetupComponents, quiet: bool = False) -> None:
|
|
|
541
544
|
|
|
542
545
|
|
|
543
546
|
def _setup_codex(components: SetupComponents, quiet: bool = False) -> None:
|
|
544
|
-
"""Codex
|
|
545
|
-
|
|
547
|
+
"""Codex 端同步用户级 hooks.json,**不再动 [tui].status_line**。
|
|
548
|
+
|
|
549
|
+
Stop(可选伪 statusline)与 UserPromptSubmit(sidebar)由同一个 installer 原子合并;
|
|
550
|
+
旧 config.toml 内联 Stop 在 JSON 成功落盘后迁移,[hooks.state] 原样保留。
|
|
551
|
+
"""
|
|
546
552
|
p = (lambda *a, **k: None) if quiet else get_console().print
|
|
547
|
-
|
|
548
|
-
if result:
|
|
549
|
-
content, _parsed = result
|
|
550
|
-
elif os.path.isdir(CODEX_DIR):
|
|
551
|
-
content = "" # 装了 Codex 但还没 config.toml → 新建
|
|
552
|
-
else:
|
|
553
|
+
if not os.path.isdir(CODEX_DIR):
|
|
553
554
|
return
|
|
554
555
|
|
|
555
556
|
config.save_codex_faux_statusline(components.codex_faux_statusline) # 写入意图
|
|
556
557
|
|
|
557
|
-
python = sys.executable or "python3"
|
|
558
558
|
if components.codex_faux_statusline:
|
|
559
|
-
|
|
559
|
+
_write_codex_statusline_script()
|
|
560
560
|
else:
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
with open(CODEX_CONFIG, "w", encoding="utf-8") as f:
|
|
564
|
-
f.write(content)
|
|
561
|
+
_uninstall_codex_statusline("")
|
|
565
562
|
|
|
563
|
+
_sync_codex_managed_hooks(quiet=quiet)
|
|
566
564
|
p(f"[green]✓[/green] {t('codex_configured')}")
|
|
567
565
|
if components.codex_faux_statusline:
|
|
568
566
|
p(f"[dim]{t('codex_statusline_hint')}[/dim]")
|
|
569
567
|
p(f"[dim]{t('restart_codex')}[/dim]")
|
|
570
568
|
|
|
571
569
|
|
|
570
|
+
def _setup_codex_sidebar(quiet: bool = False) -> None:
|
|
571
|
+
"""安装用户级 $tt-sidebar Skill;两个 Codex Hook 已由 _setup_codex 统一同步。"""
|
|
572
|
+
p = (lambda *a, **k: None) if quiet else get_console().print
|
|
573
|
+
try:
|
|
574
|
+
skill_changed = sidebar_install.install_skill()
|
|
575
|
+
except FileExistsError:
|
|
576
|
+
get_console().print(
|
|
577
|
+
f"[yellow]{t('sidebar_skill_conflict', path=sidebar_install.SIDEBAR_SKILL_DIR)}[/yellow]"
|
|
578
|
+
)
|
|
579
|
+
skill_changed = False
|
|
580
|
+
if skill_changed:
|
|
581
|
+
p(
|
|
582
|
+
f"[green]✓[/green] "
|
|
583
|
+
f"{t('sidebar_skill_installed', path=sidebar_install.SIDEBAR_SKILL_DIR)}"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
572
587
|
# --- unsetup ---
|
|
573
588
|
|
|
574
589
|
def unsetup() -> None:
|
|
@@ -579,6 +594,7 @@ def unsetup() -> None:
|
|
|
579
594
|
_unsetup_claude()
|
|
580
595
|
if has_codex:
|
|
581
596
|
_unsetup_codex()
|
|
597
|
+
_unsetup_codex_sidebar()
|
|
582
598
|
if not has_cc and not has_codex:
|
|
583
599
|
get_console().print(f"[dim]{t('no_agent_detected')}[/dim]")
|
|
584
600
|
|
|
@@ -610,12 +626,12 @@ def _unsetup_codex() -> None:
|
|
|
610
626
|
"""卸载 Codex 端:移除伪 statusline hook + 脚本。
|
|
611
627
|
老用户残留:如有 codex-backup.json(旧版我们改过 status_line),恢复原值;新版不再动 status_line。"""
|
|
612
628
|
result = _read_codex_config()
|
|
613
|
-
if
|
|
614
|
-
return
|
|
615
|
-
content, _parsed = result
|
|
629
|
+
content = result[0] if result else ""
|
|
616
630
|
|
|
617
|
-
#
|
|
631
|
+
# config.toml 不存在 / 损坏也必须先清运行产物;hooks.json 由 _unsetup_codex_sidebar 统一清。
|
|
618
632
|
content = _uninstall_codex_statusline(content)
|
|
633
|
+
if not result:
|
|
634
|
+
return
|
|
619
635
|
|
|
620
636
|
# 兼容老用户:旧版我们曾接管 status_line + 写 codex-backup.json。这里恢复 + 删 backup。
|
|
621
637
|
if os.path.exists(CODEX_BACKUP_LEGACY):
|
|
@@ -635,3 +651,19 @@ def _unsetup_codex() -> None:
|
|
|
635
651
|
|
|
636
652
|
with open(CODEX_CONFIG, "w", encoding="utf-8") as f:
|
|
637
653
|
f.write(content)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _unsetup_codex_sidebar() -> None:
|
|
657
|
+
if sidebar_install.uninstall_skill():
|
|
658
|
+
get_console().print(
|
|
659
|
+
f"[green]✓[/green] {t('deleted_file', path=sidebar_install.SIDEBAR_SKILL_DIR)}"
|
|
660
|
+
)
|
|
661
|
+
try:
|
|
662
|
+
hook_removed = sidebar_install.uninstall_managed_hooks()
|
|
663
|
+
except ValueError:
|
|
664
|
+
get_console().print(
|
|
665
|
+
f"[red]{t('codex_hooks_corrupt_unsetup', path=sidebar_install.CODEX_HOOKS)}[/red]"
|
|
666
|
+
)
|
|
667
|
+
return
|
|
668
|
+
if hook_removed:
|
|
669
|
+
get_console().print(f"[green]✓[/green] {t('codex_hooks_removed')}")
|
|
@@ -86,6 +86,13 @@ _STRINGS = {
|
|
|
86
86
|
"codex_configured": "Codex 已配置",
|
|
87
87
|
"codex_statusline_hint": "已启用伪 statusline(每次回答后追加一行 5h/7d/Ctx)",
|
|
88
88
|
"restart_codex": "重启 Codex 后生效",
|
|
89
|
+
"sidebar_skill_installed": "已安装 Codex $tt-sidebar Skill:{path}",
|
|
90
|
+
"sidebar_skill_conflict": "{path} 已存在非 Token Tracker 管理的同名 Skill,已保留且未覆盖",
|
|
91
|
+
"codex_hooks_synced": "已统一更新 Codex 用户级 Hooks 配置",
|
|
92
|
+
"sidebar_hook_trust": "在 Codex 中运行 /hooks 检查并信任新增或变更的 Hook;新 Skill 未出现时重启 Codex",
|
|
93
|
+
"codex_hooks_removed": "已移除 Token Tracker 的 Codex Hooks",
|
|
94
|
+
"codex_hooks_corrupt": "无法解析 {path}(JSON 损坏),Token Tracker Hooks 未改动;请修复后重跑 tt setup",
|
|
95
|
+
"codex_hooks_corrupt_unsetup": "无法解析 {path}(JSON 损坏),Token Tracker Hooks 未改动;请手动检查该文件",
|
|
89
96
|
"no_agent_detected": "未检测到 Claude Code 或 Codex",
|
|
90
97
|
"deleted_file": "已删除: {path}",
|
|
91
98
|
"cc_restored": "Claude Code statusLine 已恢复原配置",
|
|
@@ -178,6 +185,13 @@ _STRINGS = {
|
|
|
178
185
|
"codex_configured": "Codex configured",
|
|
179
186
|
"codex_statusline_hint": "Faux statusline enabled (appends 5h/7d/Ctx line after each turn)",
|
|
180
187
|
"restart_codex": "Restart Codex to take effect",
|
|
188
|
+
"sidebar_skill_installed": "Codex $tt-sidebar Skill installed: {path}",
|
|
189
|
+
"sidebar_skill_conflict": "A non-Token Tracker skill already exists at {path}; kept without changes",
|
|
190
|
+
"codex_hooks_synced": "Codex user-level Hooks configuration unified and updated",
|
|
191
|
+
"sidebar_hook_trust": "Run /hooks in Codex to review and trust new or changed hooks; restart Codex if the new Skill is not visible",
|
|
192
|
+
"codex_hooks_removed": "Token Tracker Codex hooks removed",
|
|
193
|
+
"codex_hooks_corrupt": "Cannot parse {path} (invalid JSON); Token Tracker hooks untouched — fix it and re-run tt setup",
|
|
194
|
+
"codex_hooks_corrupt_unsetup": "Cannot parse {path} (invalid JSON); Token Tracker hooks untouched — check it manually",
|
|
181
195
|
"no_agent_detected": "Claude Code or Codex not detected",
|
|
182
196
|
"deleted_file": "Deleted: {path}",
|
|
183
197
|
"cc_restored": "Claude Code statusLine restored",
|