cc-api-statusline 1.0.0 → 1.0.2

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
@@ -1,5 +1,7 @@
1
1
  # cc-api-statusline
2
2
 
3
+ English | [简体中文](README.zh-CN.md)
4
+
3
5
  A high-performance TUI statusline tool that polls API usage data from Claude API proxy services (sub2api, claude-relay-service, or custom providers) and renders a configurable one-line status display.
4
6
 
5
7
  ## Features
@@ -177,7 +179,7 @@ All variables are optional at the shell level — `ANTHROPIC_BASE_URL` and `ANTH
177
179
  | `ANTHROPIC_AUTH_TOKEN` | Yes | API key or token |
178
180
  | `CC_STATUSLINE_PROVIDER` | Yes | Override provider detection (`sub2api`, `claude-relay-service`, or custom) |
179
181
  | `CC_STATUSLINE_POLL` | Yes | Override poll interval (seconds, min 5) |
180
- | `CC_STATUSLINE_TIMEOUT` | Yes | Piped mode timeout (milliseconds, default 1000) |
182
+ | `CC_STATUSLINE_TIMEOUT` | Yes | Piped mode timeout (milliseconds, default 5000) |
181
183
  | `DEBUG` or `CC_STATUSLINE_DEBUG` | Yes | Enable debug logging to `~/.claude/cc-api-statusline/debug.log` |
182
184
 
183
185
  ## Troubleshooting
@@ -219,7 +221,7 @@ cc-api-statusline --once
219
221
  DEBUG=1 cc-api-statusline --once
220
222
  ```
221
223
 
222
- Verify `pipedRequestTimeoutMs` in config (default 800ms) and check `~/.claude/cc-api-statusline/cache-*.json` exists.
224
+ Verify `pipedRequestTimeoutMs` in config (default 3000ms) and check `~/.claude/cc-api-statusline/cache-*.json` exists.
223
225
 
224
226
  ### Widget shows `[Exit: 1]` in Claude Code
225
227
 
@@ -0,0 +1,300 @@
1
+ # cc-api-statusline
2
+
3
+ [English](README.md) | 简体中文
4
+
5
+ 在ClaudeCode状态栏显示API用量,通过轮询 Claude API 代理服务(sub2api、claude-relay-service 或自定义提供商)获取用量数据,并以可配置显示样式。
6
+
7
+ ## 特性
8
+
9
+ - 🎨 **高度可配置** — 布局、颜色、进度条样式、显示模式任意调整
10
+ - 🔌 **提供商自动识别** — 开箱支持 sub2api、claude-relay-service 及自定义提供商
11
+ - 💾 **智能缓存** — 原子写入磁盘缓存、TTL 验证、自动垃圾回收
12
+ - 🎯 **Claude Code 集成** — 一键 `--install` 完成安装
13
+ - 📊 **多维度用量展示** — 每日/每周/每月配额、余额、Token数、速率限制
14
+ - 🔁 **热切换** — 自动感知 API 端点和凭证变更,无需重启
15
+ - 🔒 **高可靠性** — 无过期数据展示、无竞争条件写入、缓存自动清理
16
+
17
+ ## 快速上手
18
+
19
+ ### 1. 配置 API 端点
20
+
21
+ 需要准备 `ANTHROPIC_BASE_URL`(代理地址)和 `ANTHROPIC_AUTH_TOKEN`(API 密钥)两个变量。
22
+
23
+ **推荐方式:写入 `~/.claude/settings.json` 的 env 字段**(会自动传递给组件):
24
+
25
+ ```json
26
+ {
27
+ "env": {
28
+ "ANTHROPIC_BASE_URL": "https://your-proxy.example.com",
29
+ "ANTHROPIC_AUTH_TOKEN": "your-api-token"
30
+ }
31
+ }
32
+ ```
33
+
34
+ 也可以直接在 Shell 中导出:
35
+
36
+ ```bash
37
+ export ANTHROPIC_BASE_URL="https://your-proxy.example.com"
38
+ export ANTHROPIC_AUTH_TOKEN="your-api-token"
39
+ ```
40
+
41
+ ### 2. 预览效果
42
+
43
+ ```bash
44
+ bunx cc-api-statusline@latest --once
45
+ ```
46
+
47
+ ### 3. 安装为 Claude Code 状态栏组件(可选)
48
+
49
+ ```bash
50
+ bunx cc-api-statusline@latest --install
51
+ ```
52
+
53
+ 此命令会自动向 `~/.claude/settings.json` 写入以下配置:
54
+
55
+ ```json
56
+ {
57
+ "statusLine": {
58
+ "type": "command",
59
+ "command": "bunx -y cc-api-statusline@latest",
60
+ "padding": 0
61
+ }
62
+ }
63
+ ```
64
+
65
+ 使用 `bunx` 可每次自动拉取最新版本,无需全局安装。如需卸载:
66
+
67
+ ```bash
68
+ bunx cc-api-statusline --uninstall
69
+ ```
70
+
71
+ 也支持全局安装:
72
+
73
+ ```bash
74
+ bun add -g cc-api-statusline
75
+ # 或
76
+ npm install -g cc-api-statusline
77
+ ```
78
+
79
+ ## 热切换
80
+
81
+ 当 `ANTHROPIC_BASE_URL` 或 `ANTHROPIC_AUTH_TOKEN` 发生变化时(例如切换 Claude Code 配置文件或轮换密钥),cc-api-statusline 会自动检测并触发切换。切换期间会短暂显示过渡提示(`⟳ Switching provider...`),随后从新端点刷新数据,全程无需重启。
82
+
83
+ ## 配置
84
+
85
+ ### 样式配置(`~/.claude/cc-api-statusline/config.json`)
86
+
87
+ ```json
88
+ {
89
+ "display": {
90
+ "layout": "standard",
91
+ "displayMode": "text",
92
+ "progressStyle": "icon",
93
+ "barStyle": "block",
94
+ "divider": { "text": "|", "margin": 1, "color": "#555753" },
95
+ "maxWidth": 100
96
+ },
97
+ "components": {
98
+ "daily": true,
99
+ "weekly": true,
100
+ "monthly": true,
101
+ "balance": true,
102
+ "tokens": false,
103
+ "rateLimit": false
104
+ }
105
+ }
106
+ ```
107
+
108
+ 主要配置项说明:
109
+
110
+ | 配置项 | 可选值 | 默认值 | 说明 |
111
+ |--------|--------|--------|------|
112
+ | `layout` | `standard` / `percent-first` | `standard` | 标签、进度条、数值的排列顺序 |
113
+ | `displayMode` | `text` / `compact` / `emoji` / `nerd` / `hidden` | `text` | 标签样式。`nerd` 需安装 [Nerd Font](https://www.nerdfonts.com/font-downloads) |
114
+ | `progressStyle` | `bar` / `icon` / `hidden` | `icon` | 用量进度的可视化方式。`icon` 需安装 [Nerd Font](https://www.nerdfonts.com/font-downloads) |
115
+ | `barStyle` | `block` / `classic` / `dot` / `shade` / `pipe` / `braille` / `square` / `star` | `block` | 进度条字符样式 |
116
+ | `barSize` | `small` / `small-medium` / `medium` / `medium-large` / `large` | `medium` | 进度条宽度(4–12 个字符) |
117
+ | `divider` | `DividerConfig` 或 `false` | `{ text: "\|", margin: 1, color: "#555753" }` | 组件间分隔符;设为 `false` 可禁用 |
118
+ | `maxWidth` | 20–100 | `100` | 状态栏最大宽度占终端宽度的百分比 |
119
+
120
+ 完整样式参考(包含每组件独立配置、颜色别名、倒计时等高级选项)请查阅 [docs/spec-tui-style.md](docs/spec-tui-style.md)。
121
+
122
+ #### User-Agent 伪装
123
+
124
+ 部分提供商会限制非 Claude Code 客户端的访问,可启用此选项绕过:
125
+
126
+ ```json
127
+ {
128
+ "spoofClaudeCodeUA": true
129
+ }
130
+ ```
131
+
132
+ - `false` / `undefined` — 不发送 User-Agent 请求头(默认)
133
+ - `true` — 自动获取当前 Claude Code 版本,获取失败则回退至 `claude-cli/2.1.56 (external, cli)`
134
+ - `"string"` — 使用指定的自定义 User-Agent 字符串
135
+
136
+ ### API 提供商配置(`~/.claude/cc-api-statusline/api-config/`)
137
+
138
+ 在此目录下以 JSON 文件形式定义自定义提供商。添加或修改后,执行以下命令使其生效:
139
+
140
+ ```bash
141
+ cc-api-statusline --apply-config
142
+ ```
143
+
144
+ 完整 Schema 请参阅 [docs/api-config-reference.md](docs/api-config-reference.md)。
145
+
146
+ ## [ccstatusline](https://github.com/anthropics/claude-code) 自定义命令
147
+
148
+ 在 `~/.claude/ccstatusline/config.json` 中添加如下配置:
149
+
150
+ ```json
151
+ {
152
+ "customCommands": {
153
+ "usage": {
154
+ "command": "cc-api-statusline",
155
+ "description": "API usage statusline",
156
+ "type": "piped"
157
+ }
158
+ },
159
+ "widgets": [
160
+ {
161
+ "type": "customCommand",
162
+ "command": "usage",
163
+ "refreshIntervalMs": 30000,
164
+ "maxWidth": 100,
165
+ "preserveColors": true
166
+ }
167
+ ]
168
+ }
169
+ ```
170
+
171
+ ## 环境变量
172
+
173
+ 以下所有变量均为可选——`ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN` 可通过 `settings.json` 的 env 字段配置,无需在 Shell 中手动导出(详见[快速上手](#快速上手))。
174
+
175
+ | 变量 | 是否可选 | 说明 |
176
+ |------|----------|------|
177
+ | `ANTHROPIC_BASE_URL` | 是 | API 端点地址(如 `https://api.sub2api.com`) |
178
+ | `ANTHROPIC_AUTH_TOKEN` | 是 | API 密钥或Token |
179
+ | `CC_STATUSLINE_PROVIDER` | 是 | 手动指定提供商(`sub2api`、`claude-relay-service` 或自定义) |
180
+ | `CC_STATUSLINE_POLL` | 是 | 轮询间隔(秒,最小 5) |
181
+ | `CC_STATUSLINE_TIMEOUT` | 是 | 管道模式超时时间(毫秒,默认 5000) |
182
+ | `DEBUG` 或 `CC_STATUSLINE_DEBUG` | 是 | 开启调试日志,输出至 `~/.claude/cc-api-statusline/debug.log` |
183
+
184
+ ## 常见问题
185
+
186
+ ### 提示 "Missing required environment variable"
187
+
188
+ 请通过 Shell 导出或 `settings.json` env 字段设置 `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`(详见[快速上手](#快速上手))。
189
+
190
+ ### 提示 "Unknown provider"
191
+
192
+ 提供商自动识别失败,请手动指定:
193
+
194
+ ```bash
195
+ export CC_STATUSLINE_PROVIDER="sub2api"
196
+ ```
197
+
198
+ 或在 `api-config/` 目录下定义自定义提供商。
199
+
200
+ ### 显示 "[offline]" 或 "[stale]"
201
+
202
+ 通常由网络错误或缓存过期导致,可开启调试日志排查:
203
+
204
+ ```bash
205
+ DEBUG=1 cc-api-statusline --once
206
+ tail -f ~/.claude/cc-api-statusline/debug.log
207
+ ```
208
+
209
+ 排查要点:
210
+ - `ANTHROPIC_BASE_URL` 对应的网络是否可达
211
+ - API 端点是否正常响应
212
+ - Token是否有效且未过期
213
+
214
+ ### 管道模式响应较慢
215
+
216
+ ```bash
217
+ # 单独预热缓存
218
+ cc-api-statusline --once
219
+ # 查看详细耗时
220
+ DEBUG=1 cc-api-statusline --once
221
+ ```
222
+
223
+ 检查配置中的 `pipedRequestTimeoutMs`(默认 3000ms),并确认 `~/.claude/cc-api-statusline/cache-*.json` 文件已存在。
224
+
225
+ ### Claude Code 中组件显示 `[Exit: 1]`
226
+
227
+ 在 `~/.claude/settings.json` 中开启调试日志:
228
+
229
+ ```json
230
+ {
231
+ "statusLine": {
232
+ "type": "command",
233
+ "command": "DEBUG=1 bunx -y cc-api-statusline@latest",
234
+ "padding": 0
235
+ }
236
+ }
237
+ ```
238
+
239
+ 然后查看日志:`tail -f ~/.claude/cc-api-statusline/debug.log`
240
+
241
+ ## 开发
242
+
243
+ | 命令 | 说明 |
244
+ |------|------|
245
+ | `bun install` | 安装依赖 |
246
+ | `bun run start` | 单次获取(--once 模式),用于快速调试 |
247
+ | `bun run example` | 模拟管道模式 |
248
+ | `bun run test` | 运行测试 |
249
+ | `bun run lint` | 代码检查 |
250
+ | `bun run build` | 构建 |
251
+ | `bun run check` | 运行全部检查 |
252
+
253
+ ### 调试日志
254
+
255
+ 启用详细执行日志:
256
+
257
+ ```bash
258
+ # 开启调试
259
+ DEBUG=1 cc-api-statusline --once
260
+
261
+ # 用于 Claude Code 组件时,在 settings.json 中设置:
262
+ # "command": "DEBUG=1 bunx -y cc-api-statusline@latest"
263
+
264
+ # 实时追踪日志
265
+ tail -f ~/.claude/cc-api-statusline/debug.log
266
+
267
+ # 搜索错误记录
268
+ grep "ERROR" ~/.claude/cc-api-statusline/debug.log
269
+ ```
270
+
271
+ 调试日志涵盖:执行时间戳、模式检测、配置与缓存状态、执行路径(A/B/C/D)、请求耗时及错误详情。
272
+
273
+ 日志文件自动轮转(约每 20 次调用触发一次):
274
+ - `debug.log` ≥ 500 KB → 归档为 `debug.YYYY-MM-DDTHH-MM.log`
275
+ - 归档超过 24 小时 → gzip 压缩
276
+ - 压缩归档超过 3 天 → 自动删除
277
+
278
+ ## 测试
279
+
280
+ - **691 个测试**,覆盖 **39 个测试文件**
281
+ - 所有服务、渲染器及工具函数的单元测试
282
+ - 核心执行路径测试(A/B/C/D)
283
+ - 隔离环境端到端冒烟测试
284
+ - 性能测试(验证 p95 < 600ms)
285
+ - 缓存垃圾回收测试
286
+ - GitHub Actions CI/CD 流水线
287
+
288
+ 运行:`bun run check`
289
+
290
+ ## 许可证
291
+
292
+ MIT
293
+
294
+ ## 相关文档
295
+
296
+ - [实现手册](docs/implementation-handbook.md)
297
+ - [当前实现说明](docs/current-implementation.md)
298
+ - [TUI 样式规范](docs/spec-tui-style.md)
299
+ - [API 轮询规范](docs/spec-api-polling.md)
300
+ - [自定义提供商规范](docs/spec-custom-providers.md)
@@ -4,7 +4,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "cc-api-statusline",
7
- version: "1.0.0",
7
+ version: "1.0.2",
8
8
  description: "Claude Code statusline tool that polls API usage from third-party proxy backends",
9
9
  type: "module",
10
10
  bin: {
@@ -124,7 +124,7 @@ Environment Variables:
124
124
  ANTHROPIC_AUTH_TOKEN API key (required)
125
125
  CC_STATUSLINE_PROVIDER Override provider detection
126
126
  CC_STATUSLINE_POLL Override poll interval (seconds)
127
- CC_STATUSLINE_TIMEOUT Piped mode timeout (milliseconds, default 1000)
127
+ CC_STATUSLINE_TIMEOUT Piped mode timeout (milliseconds, default 5000)
128
128
  DEBUG or CC_STATUSLINE_DEBUG Enable debug logging to ~/.claude/cc-api-statusline/debug.log
129
129
 
130
130
  Config File:
@@ -178,11 +178,13 @@ import { dirname, join } from "path";
178
178
 
179
179
  // src/core/constants.ts
180
180
  var DEFAULT_FETCH_TIMEOUT_MS = 5000;
181
+ var DEFAULT_PIPED_REQUEST_TIMEOUT_MS = 3000;
181
182
  var EXIT_BUFFER_MS = 50;
182
183
  var STALENESS_THRESHOLD_MINUTES = 5;
183
184
  var VERY_STALE_THRESHOLD_MINUTES = 30;
184
185
  var GC_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
185
186
  var GC_MAX_CACHE_FILES = 20;
187
+ var GC_MAX_PROVIDER_DETECT_FILES = 20;
186
188
  var GC_ORPHAN_TMP_AGE_MS = 60 * 60 * 1000;
187
189
  var LOG_ROTATION_PROBABILITY = 0.05;
188
190
  var LOG_MAX_SIZE_BYTES = 512 * 1024;
@@ -562,7 +564,7 @@ var DEFAULT_CONFIG = {
562
564
  chill: { tiers: buildTiers(["cyan", "cyan", "blue", "blue", "magenta"]) }
563
565
  },
564
566
  pollIntervalSeconds: 30,
565
- pipedRequestTimeoutMs: 800
567
+ pipedRequestTimeoutMs: 3000
566
568
  };
567
569
  var BAR_SIZE_MAP = {
568
570
  small: 4,
@@ -2398,11 +2400,14 @@ var TEXT_PROGRESS_ICONS = [
2398
2400
  "◕",
2399
2401
  "●"
2400
2402
  ];
2403
+ function calcIconIndex(percent, bucketSize, maxIndex) {
2404
+ return Math.min(maxIndex, Math.ceil(Math.round(percent) / bucketSize));
2405
+ }
2401
2406
  function calcNerdIconIndex(percent) {
2402
- return Math.min(8, Math.ceil(percent / 12.5));
2407
+ return calcIconIndex(percent, 12.5, 8);
2403
2408
  }
2404
2409
  function calcTextIconIndex(percent) {
2405
- return Math.min(4, Math.ceil(percent / 25));
2410
+ return calcIconIndex(percent, 25, 4);
2406
2411
  }
2407
2412
  function getProgressIcon(percent, nerdFontAvailable = true) {
2408
2413
  if (!nerdFontAvailable) {
@@ -2510,7 +2515,7 @@ function renderBalanceComponent(balance, options, componentConfig, globalConfig,
2510
2515
  const isUnlimited = balance.remaining === -1;
2511
2516
  let usagePercent = null;
2512
2517
  if (!isUnlimited && balance.initial !== null && balance.initial > 0) {
2513
- usagePercent = (balance.initial - balance.remaining) / balance.initial * 100;
2518
+ usagePercent = calculateUsagePercent(balance.initial - balance.remaining, balance.initial);
2514
2519
  }
2515
2520
  const effectivePercent = isUnlimited ? 0 : usagePercent;
2516
2521
  const label = renderLabel("balance", displayMode, componentConfig);
@@ -3122,6 +3127,23 @@ function runCacheGC(cacheDir) {
3122
3127
  }
3123
3128
  }
3124
3129
  }
3130
+ const remainingProviderDetectFiles = providerDetectFiles.filter((file) => {
3131
+ const age = now - file.mtime;
3132
+ return age <= GC_MAX_AGE_MS;
3133
+ });
3134
+ if (remainingProviderDetectFiles.length > GC_MAX_PROVIDER_DETECT_FILES) {
3135
+ remainingProviderDetectFiles.sort((a, b) => a.mtime - b.mtime);
3136
+ const toDelete = remainingProviderDetectFiles.slice(0, remainingProviderDetectFiles.length - GC_MAX_PROVIDER_DETECT_FILES);
3137
+ for (const file of toDelete) {
3138
+ try {
3139
+ unlinkSync6(join12(cacheDir, file.name));
3140
+ deletedCount++;
3141
+ logger.debug("GC: Deleted provider-detect file (count limit)", { file: file.name });
3142
+ } catch (error) {
3143
+ logger.debug("GC: Failed to delete provider-detect file", { file: file.name, error: String(error) });
3144
+ }
3145
+ }
3146
+ }
3125
3147
  const remainingCacheFiles = cacheFiles.filter((file) => {
3126
3148
  const age = now - file.mtime;
3127
3149
  return age <= GC_MAX_AGE_MS;
@@ -3218,7 +3240,7 @@ async function resolveProviderWithTimeout(baseUrl, env, endpointConfigs, isPiped
3218
3240
  }
3219
3241
  function computeTimeoutBudgets(isPiped, config, timeoutMs) {
3220
3242
  const timeoutBudgetMs = isPiped ? timeoutMs : 1e4;
3221
- const fetchTimeoutMs = isPiped ? Math.min(config.pipedRequestTimeoutMs ?? 800, timeoutBudgetMs - 100) : 1e4;
3243
+ const fetchTimeoutMs = isPiped ? Math.min(config.pipedRequestTimeoutMs ?? DEFAULT_PIPED_REQUEST_TIMEOUT_MS, timeoutBudgetMs - 100) : 1e4;
3222
3244
  return { timeoutBudgetMs, fetchTimeoutMs };
3223
3245
  }
3224
3246
  async function buildExecutionContext(args, isPiped, startTime, rawTimeoutMs) {
@@ -3282,7 +3304,7 @@ async function executePipedMode(args) {
3282
3304
  logger.debug("Start time", { startTime });
3283
3305
  const isPiped = !process.stdin.isTTY;
3284
3306
  logger.debug("Mode detection", { isPiped, once: args.once });
3285
- const rawTimeoutMs = Number(process.env["CC_STATUSLINE_TIMEOUT"] ?? 1000);
3307
+ const rawTimeoutMs = Number(process.env["CC_STATUSLINE_TIMEOUT"] ?? 5000);
3286
3308
  if (isPiped) {
3287
3309
  const watchdogMs = rawTimeoutMs - 100;
3288
3310
  setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-api-statusline",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Claude Code statusline tool that polls API usage from third-party proxy backends",
5
5
  "type": "module",
6
6
  "bin": {