cc-usage-bar 0.1.0 → 0.4.1
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/AGENTS.md +233 -0
- package/README.md +15 -3
- package/README.zh-CN.md +15 -3
- package/bin/cc-usage-bar-wrap.js +7 -0
- package/dist/src/cli.js +23 -23
- package/dist/src/fetch.js +80 -10
- package/dist/src/format.js +188 -51
- package/dist/src/providers/anthropic.js +1 -1
- package/dist/src/providers/deepseek.js +1 -1
- package/dist/src/providers/glm.js +1 -1
- package/dist/src/providers/kimi.js +1 -1
- package/dist/src/providers/minimax.js +1 -1
- package/dist/src/providers/novita.js +1 -1
- package/dist/src/providers/openrouter.js +1 -1
- package/dist/src/providers/siliconflow.js +1 -1
- package/dist/src/providers/stepfun.js +1 -1
- package/dist/src/render.js +1 -0
- package/dist/src/settings.js +61 -35
- package/dist/src/token.js +54 -0
- package/dist/src/wrap.js +222 -0
- package/package.json +4 -2
package/AGENTS.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# AGENTS.md — cc-usage-bar 全功能说明
|
|
2
|
+
|
|
3
|
+
> 这份文件是面向 AI 助手(Claude Code、Cursor、Codex 等)的安装/配置说明。如果用户对你说「装 cc-usage-bar」「帮我配 statusline 用量条」「装下 cc-usage-bar 帮我配好」之类的话,请按本文档完成全部步骤。**默认无需向用户追加确认**;只有用户没登录过 Claude Code(凭证查不到)时才停下来让用户先登录。
|
|
4
|
+
|
|
5
|
+
## 1. 这个包是什么
|
|
6
|
+
|
|
7
|
+
`cc-usage-bar` 把 Claude Code 订阅用量(5 小时滑窗 + 7 天滑窗)渲染到 statusline。也支持余额型 provider(Kimi / GLM / MiniMax / DeepSeek / StepFun / SiliconFlow / OpenRouter / Novita)。
|
|
8
|
+
|
|
9
|
+
**核心 bin**:
|
|
10
|
+
|
|
11
|
+
| bin | 用途 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `cc-usage-fetch` | 单纯渲染一段用量字符串到 stdout(最常用) |
|
|
14
|
+
| `cc-usage-bar-wrap` | 跨平台编排器:tee stdin 给「前缀命令」+ `cc-usage-fetch`,按真实终端宽度自动单行/换行 |
|
|
15
|
+
| `cc-usage-bar` / `cc-usage-statusline` | 安装器(自动改 settings.json),带 `agents`/`install`/`uninstall`/`status` 子命令 |
|
|
16
|
+
|
|
17
|
+
## 2. 一句话目标
|
|
18
|
+
|
|
19
|
+
> **用户只描述偏好,你把 `statusLine.command` 配成 `cc-usage-bar-wrap`,并保留用户原有前缀。**
|
|
20
|
+
|
|
21
|
+
如果已有 `statusLine.command`、`statusline-command.sh`、自定义 shell 脚本、git/path/model 展示等前缀内容,**一律视为要保留的前缀**:按 3.4 抽出它,并用 `cc-usage-bar-wrap --prefix=...` 串起来。不要询问是否覆盖、追加或暂不修改;追加保留是本项目默认行为。
|
|
22
|
+
|
|
23
|
+
## 3. 安装流程(必走)
|
|
24
|
+
|
|
25
|
+
按顺序执行,每一步执行前简要告知用户:
|
|
26
|
+
|
|
27
|
+
### 3.1 判断平台
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
node -e "console.log(process.platform)"
|
|
31
|
+
// → "darwin" | "linux" | "win32"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3.2 安装 npm 包
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm i -g cc-usage-bar
|
|
38
|
+
# 验证(macOS/Linux)
|
|
39
|
+
which cc-usage-bar-wrap && which cc-usage-fetch
|
|
40
|
+
# 验证(Windows)
|
|
41
|
+
where.exe cc-usage-bar-wrap
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
要求:`cc-usage-bar` ≥ **0.4.1**(自适应换行 + 周限额上色 + countdown 预设 + 颜色 ramp 自定义 + tint reverse 样式)。已装旧版的话用 `npm i -g cc-usage-bar@latest` 升级。
|
|
45
|
+
|
|
46
|
+
### 3.3 读取现有 statusLine.command
|
|
47
|
+
|
|
48
|
+
- **macOS / Linux**:`~/.claude/settings.json`,字段 `statusLine.command`
|
|
49
|
+
- **Windows**:`%USERPROFILE%\.claude\settings.json` 同上
|
|
50
|
+
|
|
51
|
+
不存在或为空 → 视为「无前缀」。
|
|
52
|
+
|
|
53
|
+
### 3.4 剥离旧的 cc-usage-fetch / cc-usage-bar-wrap
|
|
54
|
+
|
|
55
|
+
如果原命令包含 `cc-usage-fetch` 或 `cc-usage-bar-wrap` 字样,**先剥掉那部分**,剩下的才是用户真正的前缀脚本(可能是 `sh ~/.claude/statusline-command.sh` 这种)。常见模式:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
sh -c '<前缀脚本>; printf " "; cc-usage-fetch ...'
|
|
59
|
+
```
|
|
60
|
+
要把 `<前缀脚本>` 抽出来作为新的 `--prefix=`。
|
|
61
|
+
|
|
62
|
+
### 3.5 备份 settings.json
|
|
63
|
+
|
|
64
|
+
- macOS/Linux:`cp ~/.claude/settings.json ~/.claude/settings.json.bak.$(date +%s)`
|
|
65
|
+
- Windows:`Copy-Item $env:USERPROFILE\.claude\settings.json "$env:USERPROFILE\.claude\settings.json.bak.$([DateTimeOffset]::Now.ToUnixTimeSeconds())"`
|
|
66
|
+
|
|
67
|
+
### 3.6 写回 statusLine.command
|
|
68
|
+
|
|
69
|
+
**保留 settings.json 其它字段不动**,只更新这一项;`refreshInterval: 30`。
|
|
70
|
+
|
|
71
|
+
| 情况 | 新 command |
|
|
72
|
+
|---|---|
|
|
73
|
+
| 有前缀脚本 | `cc-usage-bar-wrap --prefix='<前缀>' --format=bar-countdown` |
|
|
74
|
+
| 无前缀(只显示用量条) | `cc-usage-fetch --format=bar-countdown` |
|
|
75
|
+
|
|
76
|
+
`bar-countdown` 是 v0.4.0 起的默认预设(既显示进度条又显示倒计时)。如果用户偏好旧风格,把它换成 `--format=bar-time`、`--format=bar`、`--format=compact` 即可。
|
|
77
|
+
|
|
78
|
+
**关键禁忌**:
|
|
79
|
+
|
|
80
|
+
- ❌ 不要再写 `sh -c '...'` 包裹 `cc-usage-bar-wrap` —— wrap 内部已处理 stdin tee + 子进程 + 自适应换行。
|
|
81
|
+
- ❌ 前缀脚本结尾不能有多余 `printf "\n"`(会让用量条挤到第二行被裁剪,wrap 会自己 trim 但仍最好清掉)。
|
|
82
|
+
- ❌ Windows cmd 里单引号无效,前缀有特殊字符时改用双引号 + 反斜杠转义,或用环境变量 `CC_STATUSLINE_PREFIX` 喂给 wrap。
|
|
83
|
+
|
|
84
|
+
### 3.7 自检
|
|
85
|
+
|
|
86
|
+
跑一次假数据:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
echo '{"workspace":{"current_dir":"."},"model":{"display_name":"Opus"}}' \
|
|
90
|
+
| cc-usage-bar-wrap --prefix='<原前缀>' --layout=single --format=bar-time
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
输出应包含 `[`、`█`、`░` 等字符。报 `No Claude Code credentials found` 时让用户先 `claude` 登录一次。
|
|
94
|
+
|
|
95
|
+
## 4. 自适应换行
|
|
96
|
+
|
|
97
|
+
`cc-usage-bar-wrap` 默认 `--layout=auto`:
|
|
98
|
+
|
|
99
|
+
- 通过 `stty size </dev/tty`(Unix)或 `mode con`(Windows)拿真实终端列数
|
|
100
|
+
- 剥 ANSI 数字符(UTF-8 字符数,不是字节数)
|
|
101
|
+
- 单行能装下 → `prefix bar`;装不下 → `prefix\nbar`
|
|
102
|
+
- 拿不到列数 → 回退多行(最安全)
|
|
103
|
+
|
|
104
|
+
强制行为:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
cc-usage-bar-wrap --layout=single # 永远单行
|
|
108
|
+
cc-usage-bar-wrap --layout=multi # 永远换行
|
|
109
|
+
# 或用环境变量
|
|
110
|
+
CC_STATUSLINE_LAYOUT=single
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 5. 风格预设(`--format`)
|
|
114
|
+
|
|
115
|
+
| 预设 | 渲染 |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `compact`(默认) | `5h 47% Wk 59%` |
|
|
118
|
+
| `numeric` | `47% / 59%` |
|
|
119
|
+
| `time` | `47% until 18:23 / 59% until 5/12 09:00` |
|
|
120
|
+
| `countdown` | `47% in 1h23m / 59% in 2d6h` |
|
|
121
|
+
| `bar` | `[█████░░░░░] 47% / [██████░░░░] 59%` |
|
|
122
|
+
| `bar-time` | `[█████░░░░░] 47% until 18:23 / [██████░░░░] 59% until 5/12 09:00` |
|
|
123
|
+
| `bar-countdown` | `[█████░░░░░] 47% in 1h23m / [██████░░░░] 59% in 2d6h` |
|
|
124
|
+
|
|
125
|
+
自定义模板(环境变量,覆盖 `--format`):
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
CC_USAGE_TEMPLATE='{label} {percent}% ({countdown} left)'
|
|
129
|
+
# 占位符: {label} {percent} {bar} {expiry} {countdown} {provider} {amount}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 6. 颜色 ramp(5h / Wk / 余额各自可定制)
|
|
133
|
+
|
|
134
|
+
默认(**v0.4.0 起 5h 和 Wk 都默认上色**):
|
|
135
|
+
|
|
136
|
+
| 区间 | 颜色 |
|
|
137
|
+
|---|---|
|
|
138
|
+
| 0–60% | `green` |
|
|
139
|
+
| 60–85% | `yellow` |
|
|
140
|
+
| 85–100% | `red` |
|
|
141
|
+
|
|
142
|
+
环境变量覆盖:
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
# 5 小时窗口
|
|
146
|
+
CC_USAGE_COLORS_5H='0:green,60:yellow,85:red'
|
|
147
|
+
# 7 天窗口(可以更激进,比如临界变粗红)
|
|
148
|
+
CC_USAGE_COLORS_WK='0:green,60:yellow,85:boldRed'
|
|
149
|
+
# 余额型 provider
|
|
150
|
+
CC_USAGE_COLORS_BALANCE='0:cyan,60:yellow,90:red'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
格式:`<min>:<color>` 用逗号分隔,`min` 可以是小数。
|
|
154
|
+
|
|
155
|
+
**颜色 token 三种形态**:
|
|
156
|
+
|
|
157
|
+
| 类型 | 例 | 说明 |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| 命名色 | `red` `boldRed` `dim` 等 | 16 色调色板 + bold 变体 |
|
|
160
|
+
| Hex | `#ff3333` `#fa0` | 24-bit truecolor,需要终端支持 |
|
|
161
|
+
| `none` | `none` | 这一区间不上色 |
|
|
162
|
+
|
|
163
|
+
完整命名色:`green` `yellow` `red` `blue` `magenta` `cyan` `white` `gray` `dim`、
|
|
164
|
+
`boldGreen` `boldYellow` `boldRed` `boldBlue` `boldMagenta` `boldCyan` `boldWhite`。
|
|
165
|
+
|
|
166
|
+
例:`CC_USAGE_COLORS_WK='0:#888888,50:#ffaa00,80:#ff3333,95:boldRed'`
|
|
167
|
+
|
|
168
|
+
## 7. 进度条样式(`--bar-spec`,**5h 和 Wk 共用**)
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
# 自定义填充/空槽字符(cells)
|
|
172
|
+
--bar-spec='{"mode":"cells","filled":"▰","empty":"▱","width":12}'
|
|
173
|
+
|
|
174
|
+
# 单色渐变(tint)—— 完成部分上色,剩余部分变暗
|
|
175
|
+
--bar-spec='{"mode":"tint","text":"████████","emptyStyle":"dim"}'
|
|
176
|
+
|
|
177
|
+
# 反色填充(reverse)—— 完成部分用反色底块增强对比
|
|
178
|
+
--bar-spec='{"mode":"tint","text":"Ciallo~(∠・ω< )⌒★","style":"reverse"}'
|
|
179
|
+
|
|
180
|
+
# 动画帧(frames)—— 按百分比从帧序列里挑一帧
|
|
181
|
+
--bar-spec='{"mode":"frames","frames":["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]}'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
环境变量 `CC_USAGE_BAR_SPEC` 同上。Windows cmd 里单引号无效,改 `"{\"mode\":...}"`。
|
|
185
|
+
|
|
186
|
+
## 8. 凭证查找顺序
|
|
187
|
+
|
|
188
|
+
| 平台 | 1 | 2 | 3 |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| macOS | 钥匙串 `Claude Code-credentials` | `~/.claude/.credentials.json` | — |
|
|
191
|
+
| Windows | Credential Manager `Claude Code-credentials` | `%USERPROFILE%\.claude\.credentials.json` | — |
|
|
192
|
+
| Linux | `~/.claude/.credentials.json` | — | — |
|
|
193
|
+
|
|
194
|
+
非 Anthropic provider 用 `ANTHROPIC_AUTH_TOKEN` 环境变量。
|
|
195
|
+
|
|
196
|
+
## 9. 缓存与请求节流
|
|
197
|
+
|
|
198
|
+
- 成功结果缓存 30 秒(`/tmp/cc-oauth-usage.json` 或 OS tmpdir)
|
|
199
|
+
- 鉴权失败缓存 60 秒
|
|
200
|
+
- 限流(429)/ 5xx 缓存 60 秒(v0.3.0 起)—— 防止重试风暴
|
|
201
|
+
- Claude Code 注入 stdin `rate_limits` 时直接用,**0 次** API 调用
|
|
202
|
+
|
|
203
|
+
诊断:`CC_USAGE_LOG=/tmp/ccu.log` 让 `cc-usage-fetch` 每次写一行 JSON(`source` / `cacheStatus` / `error`)。
|
|
204
|
+
|
|
205
|
+
## 10. 排障
|
|
206
|
+
|
|
207
|
+
| 现象 | 处理 |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `No Claude Code credentials found` | `claude` 登录一次 |
|
|
210
|
+
| 用量条没颜色 | v0.3.x 之前 Wk 默认无色,升级到 0.4.0+ |
|
|
211
|
+
| 窄屏被截 | 确认 statusLine.command 用的是 `cc-usage-bar-wrap`,不是直接 `cc-usage-fetch` |
|
|
212
|
+
| 单行/换行不切换 | 确认 `--layout=auto`(默认),并确认环境里有 `/dev/tty`(Unix)或可用 console(Windows) |
|
|
213
|
+
| Windows cmd 看不到颜色 | 换 Windows Terminal / PowerShell 7,或加 `--plain` |
|
|
214
|
+
| 余额 provider 显示「No credits」 | 检查 `ANTHROPIC_AUTH_TOKEN` 是否对应该 provider |
|
|
215
|
+
| settings.json 写坏 | `cp ~/.claude/settings.json.bak.<timestamp> ~/.claude/settings.json` |
|
|
216
|
+
|
|
217
|
+
## 11. 完成后该说什么
|
|
218
|
+
|
|
219
|
+
告诉用户:
|
|
220
|
+
|
|
221
|
+
> ✅ 装好了。下次 statusline 刷新(≤ 30 秒)就能看到,不需要重启 Claude Code。试着拉宽 / 拉窄窗口,进度条会单行 ↔ 换行自动切换。
|
|
222
|
+
|
|
223
|
+
## 12. 用户偏好默认值(用户没说就这么用)
|
|
224
|
+
|
|
225
|
+
- `--format=bar-countdown`(v0.4.0 起的默认)
|
|
226
|
+
- `--bar-width=10`
|
|
227
|
+
- `--layout=auto`
|
|
228
|
+
- 颜色保留,按默认 ramp(绿/黄/红)
|
|
229
|
+
- 不加自定义 `--bar-spec`
|
|
230
|
+
|
|
231
|
+
如用户说「极简」「窄屏」「我屏幕只有 80 列」→ 改 `--format=compact`。
|
|
232
|
+
如用户说「我想看还剩多久」→ 改 `--format=countdown` 或 `--format=bar-countdown`。
|
|
233
|
+
如用户说「Wk 颜色不一样」「Wk 紧迫的时候要更醒目」→ 设 `CC_USAGE_COLORS_WK='0:gray,80:boldRed'` 之类。
|
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# cc-usage-bar
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cc-usage-bar)
|
|
4
|
+
[](https://www.npmjs.com/package/cc-usage-bar)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](https://github.com/TuYv/cc-usage-bar)
|
|
8
|
+
|
|
3
9
|
[中文说明](README.zh-CN.md)
|
|
4
10
|
|
|
5
11
|
Show your Claude Code subscription usage in the statusline. Works with the official Anthropic subscription and 8 alternative providers (Kimi, GLM/Zhipu, MiniMax, DeepSeek, StepFun, SiliconFlow, OpenRouter, Novita).
|
|
@@ -43,7 +49,7 @@ A Claude Code statusline is just a shell command — its stdout is rendered to t
|
|
|
43
49
|
|
|
44
50
|
1. **stdin `rate_limits`** — Claude Code now passes a `rate_limits` object to statusline stdin when you're a Claude.ai subscriber. Zero auth, zero network.
|
|
45
51
|
2. **Local cache** — `/tmp/cc-oauth-usage.json`, 30 s TTL on success, 60 s on auth failure (so a stale token doesn't hammer the API).
|
|
46
|
-
3. **Provider HTTP query** — picked from `ANTHROPIC_BASE_URL`. For Anthropic, the OAuth token is read
|
|
52
|
+
3. **Provider HTTP query** — picked from `ANTHROPIC_BASE_URL`. For Anthropic, the OAuth token is read by platform: macOS keychain → Windows Credential Manager → `~/.claude/.credentials.json`. For the rest, `ANTHROPIC_AUTH_TOKEN` from the env.
|
|
47
53
|
|
|
48
54
|
## Supported providers
|
|
49
55
|
|
|
@@ -111,6 +117,12 @@ cc-usage-bar install --format=bar-time \
|
|
|
111
117
|
{"mode":"tint","text":"Ciallo~(∠・ω< )⌒★"}
|
|
112
118
|
```
|
|
113
119
|
|
|
120
|
+
For stronger contrast, add `style:"reverse"` so the completed prefix is rendered as a reversed-color block:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{"mode":"tint","text":"Ciallo~(∠・ω< )⌒★","style":"reverse"}
|
|
124
|
+
```
|
|
125
|
+
|
|
114
126
|
`cells` replaces the default block characters:
|
|
115
127
|
|
|
116
128
|
```json
|
|
@@ -172,7 +184,7 @@ base url: <not set, defaults to anthropic>
|
|
|
172
184
|
|
|
173
185
|
## Privacy & API stability
|
|
174
186
|
|
|
175
|
-
This tool calls **
|
|
187
|
+
This tool calls **one undocumented Anthropic endpoint**: `/api/oauth/usage` (subscription quota), and reads OAuth tokens from local OS-native credential stores (macOS keychain, Windows Credential Manager, or the JSON file). The `cc-switch` project (Tauri/Rust) has used the same approach in production for months. **The endpoint may change without notice** — open an issue if you see a regression.
|
|
176
188
|
|
|
177
189
|
Tokens are read **only on your machine**. Nothing is uploaded anywhere. The tool makes one HTTPS request per refresh interval to the provider you've configured (or zero, when `rate_limits` is in stdin).
|
|
178
190
|
|
|
@@ -180,7 +192,7 @@ For non-Anthropic providers, the same is true: the API key from `ANTHROPIC_AUTH_
|
|
|
180
192
|
|
|
181
193
|
## Why no `jq` / `curl` dependency?
|
|
182
194
|
|
|
183
|
-
Other statusline tools (e.g. `ccusage`) shell out to `jq` and `curl`. This one is pure Node.js (built-in `fetch` from Node 18+, `child_process` for the macOS keychain
|
|
195
|
+
Other statusline tools (e.g. `ccusage`) shell out to `jq` and `curl`. This one is pure Node.js (built-in `fetch` from Node 18+, `child_process` only for the macOS keychain or Windows Credential Manager call). One `npm install -g` and you're done — no system tooling required, works on macOS / Linux / Windows.
|
|
184
196
|
|
|
185
197
|
## Acknowledgements
|
|
186
198
|
|
package/README.zh-CN.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# 中文说明
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cc-usage-bar)
|
|
4
|
+
[](https://www.npmjs.com/package/cc-usage-bar)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](https://github.com/TuYv/cc-usage-bar)
|
|
8
|
+
|
|
3
9
|
[English](README.md)
|
|
4
10
|
|
|
5
11
|
`cc-usage-bar` 可以把 Claude Code 的订阅用量显示在底部 statusline。它支持官方 Anthropic 订阅,也支持 Kimi、GLM/Zhipu、MiniMax、DeepSeek、StepFun、SiliconFlow、OpenRouter、Novita 等替代 provider。
|
|
@@ -45,7 +51,7 @@ Claude Code 的 statusline 本质上是一条 shell 命令,命令的 stdout
|
|
|
45
51
|
|
|
46
52
|
1. **stdin `rate_limits`**:Claude Code 会把订阅用户的 `rate_limits` 传给 statusline,无需鉴权、无需网络。
|
|
47
53
|
2. **本地缓存**:`/tmp/cc-oauth-usage.json`,成功缓存 30 秒,鉴权失败缓存 60 秒。
|
|
48
|
-
3. **Provider HTTP 查询**:根据 `ANTHROPIC_BASE_URL` 自动选择 provider。Anthropic
|
|
54
|
+
3. **Provider HTTP 查询**:根据 `ANTHROPIC_BASE_URL` 自动选择 provider。Anthropic 按平台读取凭证:macOS 钥匙串 → Windows Credential Manager → `~/.claude/.credentials.json`;其他 provider 使用环境变量 `ANTHROPIC_AUTH_TOKEN`。
|
|
49
55
|
|
|
50
56
|
## 支持的 Provider
|
|
51
57
|
|
|
@@ -116,6 +122,12 @@ cc-usage-bar install --format=bar-time \
|
|
|
116
122
|
{"mode":"tint","text":"Ciallo~(∠・ω< )⌒★"}
|
|
117
123
|
```
|
|
118
124
|
|
|
125
|
+
如果想让完成部分更像“填充底块”,可以加 `style:"reverse"`,完成部分会用反色块增强对比:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{"mode":"tint","text":"Ciallo~(∠・ω< )⌒★","style":"reverse"}
|
|
129
|
+
```
|
|
130
|
+
|
|
119
131
|
#### cells:替换默认块字符
|
|
120
132
|
|
|
121
133
|
```json
|
|
@@ -179,7 +191,7 @@ base url: <not set, defaults to anthropic>
|
|
|
179
191
|
|
|
180
192
|
## 隐私与 API 稳定性
|
|
181
193
|
|
|
182
|
-
Anthropic 订阅用量依赖未公开接口 `/api/oauth/usage
|
|
194
|
+
Anthropic 订阅用量依赖未公开接口 `/api/oauth/usage`,并按平台读取本机 OAuth token:macOS 钥匙串、Windows Credential Manager、或 `~/.claude/.credentials.json`。这些接口可能变化。
|
|
183
195
|
|
|
184
196
|
Token 只在你的机器上读取,不会上传到本项目的任何服务。工具只会请求你配置的 provider。使用 stdin `rate_limits` 时不需要网络。
|
|
185
197
|
|
|
@@ -187,7 +199,7 @@ Token 只在你的机器上读取,不会上传到本项目的任何服务。
|
|
|
187
199
|
|
|
188
200
|
## 为什么不依赖 jq / curl
|
|
189
201
|
|
|
190
|
-
这个工具是纯 Node.js 实现,Node 18+ 自带 `fetch
|
|
202
|
+
这个工具是纯 Node.js 实现,Node 18+ 自带 `fetch`,只在读取 macOS 钥匙串或 Windows Credential Manager 时使用 `child_process`。安装后直接可用,不需要额外安装 `jq` 或 `curl`,跨 macOS / Linux / Windows。
|
|
191
203
|
|
|
192
204
|
## 致谢
|
|
193
205
|
|
package/dist/src/cli.js
CHANGED
|
@@ -49,25 +49,6 @@ function readPkgVersion() {
|
|
|
49
49
|
return '0.0.0';
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
function formatCountdown(iso) {
|
|
53
|
-
if (!iso)
|
|
54
|
-
return 'unknown';
|
|
55
|
-
const target = new Date(iso).getTime();
|
|
56
|
-
if (Number.isNaN(target))
|
|
57
|
-
return 'unknown';
|
|
58
|
-
const diffMs = target - Date.now();
|
|
59
|
-
if (diffMs <= 0)
|
|
60
|
-
return 'now';
|
|
61
|
-
const totalMin = Math.floor(diffMs / 60_000);
|
|
62
|
-
const days = Math.floor(totalMin / (60 * 24));
|
|
63
|
-
const hours = Math.floor((totalMin % (60 * 24)) / 60);
|
|
64
|
-
const mins = totalMin % 60;
|
|
65
|
-
if (days > 0)
|
|
66
|
-
return `${days}d ${hours}h ${mins}m`;
|
|
67
|
-
if (hours > 0)
|
|
68
|
-
return `${hours}h ${mins}m`;
|
|
69
|
-
return `${mins}m`;
|
|
70
|
-
}
|
|
71
52
|
function loadSettingsOrExit() {
|
|
72
53
|
try {
|
|
73
54
|
return (0, settings_1.readSettings)();
|
|
@@ -169,9 +150,9 @@ async function runStatus() {
|
|
|
169
150
|
if (result.data.planName)
|
|
170
151
|
console.log(`plan: ${result.data.planName}`);
|
|
171
152
|
if (fh)
|
|
172
|
-
console.log(`5-hour: ${Math.round(fh.utilization)}% (resets in ${formatCountdown(fh.resets_at)})`);
|
|
153
|
+
console.log(`5-hour: ${Math.round(fh.utilization)}% (resets in ${(0, format_1.formatCountdown)(fh.resets_at)})`);
|
|
173
154
|
if (wk)
|
|
174
|
-
console.log(`7-day: ${Math.round(wk.utilization)}% (resets in ${formatCountdown(wk.resets_at)})`);
|
|
155
|
+
console.log(`7-day: ${Math.round(wk.utilization)}% (resets in ${(0, format_1.formatCountdown)(wk.resets_at)})`);
|
|
175
156
|
}
|
|
176
157
|
else {
|
|
177
158
|
const d = result.data;
|
|
@@ -193,12 +174,12 @@ program
|
|
|
193
174
|
.command('install')
|
|
194
175
|
.description('Install statusline integration into ~/.claude/settings.json')
|
|
195
176
|
.option('-f, --force', 'overwrite if already installed')
|
|
196
|
-
.option('--format <preset>', `render preset (${format_1.FORMAT_PRESETS.join(' | ')})`, '
|
|
177
|
+
.option('--format <preset>', `render preset (${format_1.FORMAT_PRESETS.join(' | ')})`, 'bar-countdown')
|
|
197
178
|
.option('--bar-width <n>', 'bar width when format includes a bar (1-50)', '10')
|
|
198
179
|
.option('--bar-spec <json>', 'custom bar JSON spec (cells, tint, or frames)')
|
|
199
180
|
.action(async (opts) => {
|
|
200
181
|
const installOpts = {};
|
|
201
|
-
if (opts.format && opts.format !== '
|
|
182
|
+
if (opts.format && opts.format !== 'bar-countdown') {
|
|
202
183
|
if (!(0, format_1.isValidPreset)(opts.format)) {
|
|
203
184
|
console.error(`error: --format must be one of ${format_1.FORMAT_PRESETS.join(', ')}`);
|
|
204
185
|
process.exit(1);
|
|
@@ -235,6 +216,25 @@ program
|
|
|
235
216
|
.action(async () => {
|
|
236
217
|
await runStatus();
|
|
237
218
|
});
|
|
219
|
+
program
|
|
220
|
+
.command('agents')
|
|
221
|
+
.description('Print the AI-readable install guide (for piping into Claude Code, Cursor, etc.)')
|
|
222
|
+
.action(() => {
|
|
223
|
+
const candidates = [
|
|
224
|
+
path.join(__dirname, '..', '..', 'AGENTS.md'),
|
|
225
|
+
path.join(__dirname, '..', 'AGENTS.md'),
|
|
226
|
+
];
|
|
227
|
+
for (const p of candidates) {
|
|
228
|
+
try {
|
|
229
|
+
process.stdout.write(fs.readFileSync(p, 'utf8'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
console.error('AGENTS.md not found in package.');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
});
|
|
238
238
|
program.parseAsync(process.argv).catch((e) => {
|
|
239
239
|
console.error(e instanceof Error ? e.message : String(e));
|
|
240
240
|
process.exit(1);
|
package/dist/src/fetch.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.readStdinJson = readStdinJson;
|
|
37
37
|
exports.extractFromStdin = extractFromStdin;
|
|
38
38
|
exports.run = run;
|
|
39
|
+
exports.parseArg = parseArg;
|
|
39
40
|
exports.main = main;
|
|
40
41
|
const fs = __importStar(require("node:fs"));
|
|
41
42
|
const os = __importStar(require("node:os"));
|
|
@@ -46,6 +47,7 @@ const format_1 = require("./format");
|
|
|
46
47
|
const CACHE_PATH = path.join(os.tmpdir(), 'cc-oauth-usage.json');
|
|
47
48
|
const SUCCESS_TTL_MS = 30_000;
|
|
48
49
|
const AUTH_FAIL_TTL_MS = 60_000;
|
|
50
|
+
const TRANSIENT_FAIL_TTL_MS = 60_000;
|
|
49
51
|
const STDIN_TIMEOUT_MS = 500;
|
|
50
52
|
async function readStdinJson(timeoutMs = STDIN_TIMEOUT_MS) {
|
|
51
53
|
if (process.stdin.isTTY)
|
|
@@ -147,15 +149,19 @@ function writeCache(entry) {
|
|
|
147
149
|
}
|
|
148
150
|
function isCacheFresh(entry) {
|
|
149
151
|
const age = Date.now() - entry.fetched_at;
|
|
152
|
+
if (age < 0)
|
|
153
|
+
return false;
|
|
150
154
|
if (entry.status === 'ok')
|
|
151
|
-
return age
|
|
155
|
+
return age < SUCCESS_TTL_MS;
|
|
152
156
|
if (entry.status === 'auth_failed')
|
|
153
|
-
return age
|
|
157
|
+
return age < AUTH_FAIL_TTL_MS;
|
|
158
|
+
if (entry.status === 'rate_limited')
|
|
159
|
+
return age < TRANSIENT_FAIL_TTL_MS;
|
|
154
160
|
return false;
|
|
155
161
|
}
|
|
156
162
|
async function run(opts = {}) {
|
|
157
163
|
if (!opts.skipStdin) {
|
|
158
|
-
const stdinJson = await readStdinJson();
|
|
164
|
+
const stdinJson = opts.stdinJson !== undefined ? opts.stdinJson : await readStdinJson();
|
|
159
165
|
const fromStdin = stdinAsSubscription(extractFromStdin(stdinJson));
|
|
160
166
|
if (fromStdin) {
|
|
161
167
|
return {
|
|
@@ -181,12 +187,18 @@ async function run(opts = {}) {
|
|
|
181
187
|
}
|
|
182
188
|
const cache = opts.forceFresh ? null : readCache();
|
|
183
189
|
if (cache && cache.adapter_id === adapter.id && isCacheFresh(cache)) {
|
|
190
|
+
let cacheStatusReport = 'hit';
|
|
191
|
+
if (cache.status === 'auth_failed')
|
|
192
|
+
cacheStatusReport = 'auth_failed_cached';
|
|
193
|
+
else if (cache.status === 'rate_limited')
|
|
194
|
+
cacheStatusReport = 'rate_limited_cached';
|
|
184
195
|
return {
|
|
185
196
|
data: cache.data,
|
|
186
197
|
source: 'cache',
|
|
187
198
|
adapterId: adapter.id,
|
|
188
|
-
cacheStatus:
|
|
199
|
+
cacheStatus: cacheStatusReport,
|
|
189
200
|
authFailed: cache.status === 'auth_failed',
|
|
201
|
+
error: cache.error,
|
|
190
202
|
};
|
|
191
203
|
}
|
|
192
204
|
const cacheStatusOnMiss = cache && cache.adapter_id !== adapter.id ? 'adapter_changed' : cache ? 'expired' : 'miss';
|
|
@@ -202,7 +214,13 @@ async function run(opts = {}) {
|
|
|
202
214
|
};
|
|
203
215
|
}
|
|
204
216
|
if (result.authFailed) {
|
|
205
|
-
writeCache({
|
|
217
|
+
writeCache({
|
|
218
|
+
fetched_at: Date.now(),
|
|
219
|
+
status: 'auth_failed',
|
|
220
|
+
adapter_id: adapter.id,
|
|
221
|
+
data: null,
|
|
222
|
+
error: result.error,
|
|
223
|
+
});
|
|
206
224
|
return {
|
|
207
225
|
data: null,
|
|
208
226
|
source: 'api',
|
|
@@ -212,6 +230,17 @@ async function run(opts = {}) {
|
|
|
212
230
|
error: result.error,
|
|
213
231
|
};
|
|
214
232
|
}
|
|
233
|
+
const httpStatus = result.status ?? 0;
|
|
234
|
+
const isTransientHttp = httpStatus === 429 || (httpStatus >= 500 && httpStatus < 600);
|
|
235
|
+
if (isTransientHttp) {
|
|
236
|
+
writeCache({
|
|
237
|
+
fetched_at: Date.now(),
|
|
238
|
+
status: 'rate_limited',
|
|
239
|
+
adapter_id: adapter.id,
|
|
240
|
+
data: null,
|
|
241
|
+
error: result.error,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
215
244
|
return {
|
|
216
245
|
data: null,
|
|
217
246
|
source: 'api',
|
|
@@ -221,6 +250,17 @@ async function run(opts = {}) {
|
|
|
221
250
|
error: result.error,
|
|
222
251
|
};
|
|
223
252
|
}
|
|
253
|
+
function logEvent(event) {
|
|
254
|
+
const target = process.env.CC_USAGE_LOG;
|
|
255
|
+
if (!target)
|
|
256
|
+
return;
|
|
257
|
+
try {
|
|
258
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), pid: process.pid, ...event }) + '\n';
|
|
259
|
+
fs.appendFileSync(target, line);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
}
|
|
263
|
+
}
|
|
224
264
|
function parseArg(args, name) {
|
|
225
265
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
226
266
|
if (eq)
|
|
@@ -250,14 +290,27 @@ Custom template (env, overrides --format):
|
|
|
250
290
|
CC_USAGE_BAR_SPEC Same JSON shape as --bar-spec
|
|
251
291
|
|
|
252
292
|
Presets render like:
|
|
253
|
-
compact
|
|
254
|
-
numeric
|
|
255
|
-
time
|
|
256
|
-
|
|
257
|
-
bar
|
|
293
|
+
compact 5h 47% Wk 59%
|
|
294
|
+
numeric 47% / 59%
|
|
295
|
+
time 47% until 18:23 / 59% until 5/12 09:00
|
|
296
|
+
countdown 47% in 1h23m / 59% in 2d6h
|
|
297
|
+
bar [█████░░░░░] 47% / [██████░░░░] 59%
|
|
298
|
+
bar-time [█████░░░░░] 47% until 18:23 / [██████░░░░] 59% until 5/12 09:00
|
|
299
|
+
bar-countdown [█████░░░░░] 47% in 1h23m / [██████░░░░] 59% in 2d6h
|
|
300
|
+
|
|
301
|
+
Per-tier color ramps (override defaults — comma-separated "<min>:<color>"):
|
|
302
|
+
CC_USAGE_COLORS_5H e.g. '0:green,60:yellow,85:#ff3333'
|
|
303
|
+
CC_USAGE_COLORS_WK e.g. '0:#888,60:#ffaa00,85:boldRed'
|
|
304
|
+
CC_USAGE_COLORS_BALANCE used by balance providers (DeepSeek/OpenRouter/...)
|
|
305
|
+
Color tokens:
|
|
306
|
+
- named: green/yellow/red/blue/magenta/cyan/white/gray/dim,
|
|
307
|
+
boldGreen/boldYellow/boldRed/boldBlue/boldMagenta/boldCyan/boldWhite
|
|
308
|
+
- hex: '#RRGGBB' or '#RGB' (24-bit truecolor; needs a truecolor terminal)
|
|
309
|
+
- 'none': skip color in that band
|
|
258
310
|
|
|
259
311
|
Other:
|
|
260
312
|
NO_COLOR=1 Force-disable colors
|
|
313
|
+
CC_USAGE_LOG=<path> Append one JSON line per invocation (source / cacheStatus / error) for diagnosing call frequency
|
|
261
314
|
--help Show this message
|
|
262
315
|
`;
|
|
263
316
|
async function main() {
|
|
@@ -287,7 +340,24 @@ async function main() {
|
|
|
287
340
|
const barSpec = (0, format_1.parseBarSpec)(barSpecArg ?? barSpecEnv);
|
|
288
341
|
if (barSpec)
|
|
289
342
|
fmt.barSpec = barSpec;
|
|
343
|
+
const RAMP_ENV = [
|
|
344
|
+
['colorRamp5h', 'CC_USAGE_COLORS_5H'],
|
|
345
|
+
['colorRampWk', 'CC_USAGE_COLORS_WK'],
|
|
346
|
+
['colorRampBalance', 'CC_USAGE_COLORS_BALANCE'],
|
|
347
|
+
];
|
|
348
|
+
for (const [key, envName] of RAMP_ENV) {
|
|
349
|
+
const ramp = (0, format_1.parseColorRamp)(process.env[envName]);
|
|
350
|
+
if (ramp)
|
|
351
|
+
fmt[key] = ramp;
|
|
352
|
+
}
|
|
290
353
|
const result = await run({ skipStdin });
|
|
354
|
+
logEvent({
|
|
355
|
+
source: result.source,
|
|
356
|
+
cacheStatus: result.cacheStatus,
|
|
357
|
+
adapterId: result.adapterId,
|
|
358
|
+
authFailed: result.authFailed,
|
|
359
|
+
error: result.error ?? null,
|
|
360
|
+
});
|
|
291
361
|
if (wantJson) {
|
|
292
362
|
process.stdout.write(JSON.stringify({
|
|
293
363
|
data: result.data,
|