@cxmyu/ccglance 1.3.0 → 1.4.0

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
@@ -29,6 +29,17 @@ the Hour, Week, and account Limit usage windows.
29
29
  Older Claude Code versions keep working through optional fields and the legacy
30
30
  cache calculation.
31
31
 
32
+ Updated for Claude Code 2.1.309: model window badges use the actual context
33
+ capacity even without an `[1m]` suffix, thinking-only transcript entries stay
34
+ in the thinking state, and gateway limits optionally include dollar amounts:
35
+
36
+ ```text
37
+ 🤖 Opus 5.5 1M | 🧠 high | 💭
38
+ 📊 Limit 🌔 63% $314.00/$500.00 monthly
39
+ ```
40
+
41
+ The platform images above remain the Claude Code 2.1.258 emoji reference samples.
42
+
32
43
  **Docs:** [Overview](./docs/overview.md) · [Segments & icons](./docs/segments.md) · [Development](./docs/development.md)
33
44
 
34
45
  ## Terminal Font
@@ -103,15 +114,17 @@ Add a `statusLine` block to `~/.claude/settings.json`:
103
114
  "statusLine": {
104
115
  "type": "command",
105
116
  "command": "ccglance",
106
- "padding": 0
117
+ "padding": 0,
118
+ "refreshInterval": 5
107
119
  }
108
120
  }
109
121
  ```
110
122
 
111
- On Claude Code >= 2.1.97, you can optionally add `"refreshInterval": 30`
112
- inside `statusLine` to keep elapsed time, quota resets, version hints, and other
113
- time-based fields moving while the main session is idle. Keep the base block
114
- above when compatibility with older Claude Code versions matters.
123
+ `refreshInterval` is in seconds and requires Claude Code >= 2.1.97. This optional
124
+ 5-second refresh keeps transcript-derived status, elapsed time and countdowns
125
+ moving while the main session is idle. Remove it for event-only refreshes or
126
+ older Claude Code versions. It does not make API usage update before the next
127
+ response, and submitting a prompt does not guarantee an immediate redraw.
115
128
 
116
129
  If you didn't install globally, point Node at the compiled file directly:
117
130
 
@@ -124,7 +137,8 @@ If you didn't install globally, point Node at the compiled file directly:
124
137
  }
125
138
  ```
126
139
 
127
- On Windows use a full path, e.g. `node D:\\path\\to\\ccglance\\dist\\cli.js`.
140
+ On Windows use forward slashes and quote paths with spaces, e.g.
141
+ `"command": "node \"D:/path with spaces/ccglance/dist/cli.js\""`.
128
142
 
129
143
  > **Cross-platform paths:** Claude Code v2.1.47+ expands a leading `~` in the
130
144
  > command on every platform, so `node ~/path/to/dist/cli.js` works too. Prefer
@@ -133,7 +147,7 @@ On Windows use a full path, e.g. `node D:\\path\\to\\ccglance\\dist\\cli.js`.
133
147
  > indent so the row starts at the left edge.
134
148
 
135
149
  > **Windows tip:** if status-line startup latency matters, point `command`
136
- > directly at the compiled file (`node D:\\absolute\\path\\to\\ccglance\\dist\\cli.js`)
150
+ > directly at the compiled file (`node D:/absolute/path/to/ccglance/dist/cli.js`)
137
151
  > instead of the global `ccglance` command — it usually starts a bit faster.
138
152
 
139
153
  ## Contributing
package/README.zh-CN.md CHANGED
@@ -27,6 +27,16 @@
27
27
  token 流量、缓存冷热状态,以及 Hour、Week 和账户 Limit 三类用量窗口。旧版 Claude Code 缺少这些可选字段时,
28
28
  仍会自动沿用原有缓存计算逻辑。
29
29
 
30
+ 本次按 Claude Code 2.1.309 更新:模型窗口标记优先读取实际容量,不再依赖 `[1m]` 后缀;
31
+ 纯 thinking 记录保持思考状态;企业网关 Limit 可选追加金额与周期:
32
+
33
+ ```text
34
+ 🤖 Opus 5.5 1M | 🧠 high | 💭
35
+ 📊 Limit 🌔 63% $314.00/$500.00 monthly
36
+ ```
37
+
38
+ 上方平台图片保留为 Claude Code 2.1.258 的 emoji 差异参考示例。
39
+
30
40
  **文档:** [概览](./docs/overview.zh-CN.md) · [字段与图标](./docs/segments.zh-CN.md) · [开发](./docs/development.zh-CN.md)
31
41
 
32
42
  ## 终端字体
@@ -95,14 +105,16 @@ node dist/cli.js preview
95
105
  "statusLine": {
96
106
  "type": "command",
97
107
  "command": "ccglance",
98
- "padding": 0
108
+ "padding": 0,
109
+ "refreshInterval": 5
99
110
  }
100
111
  }
101
112
  ```
102
113
 
103
- Claude Code >= 2.1.97 可在 `statusLine` 内可选增加 `"refreshInterval": 30`,
104
- 让会话时长、配额重置、版本提示等时间相关字段在主会话空闲时也能更新。
105
- 若需兼容更旧的 Claude Code,保持上面的基础配置即可。
114
+ `refreshInterval` 的单位为秒,要求 Claude Code >= 2.1.97。示例每 5 秒额外刷新一次,
115
+ 让 transcript 推断状态、会话时长和倒计时在主会话空闲时也能更新。删除该字段即可恢复
116
+ 仅按事件刷新,或兼容更旧的 Claude Code。它不会让 API 用量提前更新;发送用户消息也
117
+ 不保证立即触发重绘。
106
118
 
107
119
  若未全局安装,直接用 Node 指向编译后的文件:
108
120
 
@@ -115,7 +127,8 @@ Claude Code >= 2.1.97 可在 `statusLine` 内可选增加 `"refreshInterval": 30
115
127
  }
116
128
  ```
117
129
 
118
- Windows 用完整路径,例如 `node D:\\path\\to\\ccglance\\dist\\cli.js`。
130
+ Windows 建议用正斜杠;路径含空格时加引号,例如
131
+ `"command": "node \"D:/path with spaces/ccglance/dist/cli.js\""`。
119
132
 
120
133
  > **跨平台路径**:Claude Code v2.1.47+ 会在所有平台展开命令开头的 `~`,所以
121
134
  > `node ~/path/to/dist/cli.js` 也能用。优先用 `~` 而非 `%USERPROFILE%`(近版本不可靠);
@@ -123,7 +136,7 @@ Windows 用完整路径,例如 `node D:\\path\\to\\ccglance\\dist\\cli.js`。
123
136
  > 让状态栏从最左开始。
124
137
 
125
138
  > **Windows 提速小贴士**:若在意状态栏启动延时,可把 `command` 直接指向编译产物
126
- > `node D:\\绝对路径\\ccglance\\dist\\cli.js`,通常比走全局 `ccglance` 命令更快一些。
139
+ > `node D:/absolute/path/to/ccglance/dist/cli.js`,通常比走全局 `ccglance` 命令更快一些。
127
140
 
128
141
  ## 贡献
129
142
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const readers_1 = require("./readers");
5
5
  // preview 子命令用的内置样例(覆盖默认状态栏会展示的常规字段)
6
6
  const PREVIEW_SAMPLE = {
7
- model: { display_name: 'Opus 5', id: 'claude-opus-5[1m]' },
7
+ model: { display_name: 'Opus 5.5', id: 'claude-opus-5-5' },
8
8
  effort: { level: 'high' },
9
9
  thinking: { enabled: true },
10
10
  fast_mode: true,
@@ -24,13 +24,16 @@ const PREVIEW_SAMPLE = {
24
24
  rate_limits: {
25
25
  five_hour: { used_percentage: 23.5, resets_at: Math.floor(Date.now() / 1000) + 2 * 3600 },
26
26
  seven_day: { used_percentage: 41.2, resets_at: Math.floor(Date.now() / 1000) + 3 * 86400 },
27
- spend_limit: { used_percentage: 62.8, resets_at: Math.floor(Date.now() / 1000) + 30 * 86400 },
27
+ spend_limit: {
28
+ used_percentage: 62.8, resets_at: Math.floor(Date.now() / 1000) + 30 * 86400,
29
+ used_usd: 314, limit_usd: 500, period: 'monthly',
30
+ },
28
31
  },
29
32
  workspace: { current_dir: process.cwd() },
30
33
  worktree: { name: 'main', branch: 'main' },
31
34
  session_name: 'ccglance',
32
35
  cost: { total_cost_usd: 0.1234, total_duration_ms: 192000, total_lines_added: 156, total_lines_removed: 23 },
33
- version: '2.1.258',
36
+ version: '2.1.309',
34
37
  };
35
38
  const argv = process.argv.slice(2);
36
39
  // 用法 + 「安装后要做什么」指引。终端直接运行或 --help 时打印;此路径绝不读 stdin。
@@ -45,17 +48,21 @@ function printHelp() {
45
48
  ccglance --help, -h 显示本帮助 (show this help)
46
49
  ccglance --version, -v 显示版本号 (print version)
47
50
 
48
- 安装后配置 Setup — 装完必须做这一步 (required after \`npm install -g ccglance\`):
51
+ 安装后配置 Setup — 装完必须做这一步 (required after \`npm install -g @cxmyu/ccglance\`):
49
52
  把下面这段加进 ~/.claude/settings.json,然后重启 Claude Code。
50
53
  Add this to ~/.claude/settings.json, then restart Claude Code:
51
54
 
52
55
  {
53
56
  "statusLine": {
54
57
  "type": "command",
55
- "command": "ccglance"
58
+ "command": "ccglance",
59
+ "refreshInterval": 5
56
60
  }
57
61
  }
58
62
 
63
+ Claude Code >= 2.1.97: refreshInterval 的单位为秒;删除此字段则仅按事件刷新。
64
+ Refresh runs locally; API usage still updates only when Claude Code receives it.
65
+
59
66
  未全局安装?让 Node 直接指向编译产物 (not installed globally? point Node at the built file):
60
67
  "command": "node /absolute/path/to/dist/cli.js"
61
68
 
@@ -13,6 +13,7 @@ const fs_1 = __importDefault(require("fs"));
13
13
  const path_1 = __importDefault(require("path"));
14
14
  const paths_1 = require("../utils/paths");
15
15
  const TAIL_BYTES = 64 * 1024; // 尾部读取上限:足够覆盖最近若干条消息
16
+ const STATUS_CACHE_VERSION = 2;
16
17
  function cacheFileFor(p) {
17
18
  const h = hashString(path_1.default.resolve(p));
18
19
  return path_1.default.join((0, paths_1.cacheDir)('transcript'), `${h}.usage.json`);
@@ -154,15 +155,20 @@ function extractRecentStatus(lines) {
154
155
  const msg = e.message;
155
156
  if (isInterruptedEntry(e))
156
157
  return { state: 'paused' };
158
+ if (e.isMeta || !msg)
159
+ continue;
157
160
  if (e.type === 'assistant') {
161
+ if (msg.stop_reason === 'end_turn' || msg.stop_reason === 'stop_sequence')
162
+ return { state: 'idle' };
158
163
  const content = blocks(msg && msg.content);
159
164
  const toolUse = content.find((b) => b && b.type === 'tool_use');
160
165
  if (toolUse)
161
166
  return { state: 'tool', tool: toolUse.name };
162
- // 已产出文本/思考内容 = 回合结束 → idle。
167
+ // 旧版完整文本回复可能没有 stop_reason;保留文本 → idle 的近似回退。
163
168
  // 注意:Claude Code transcript 常把完成的文本回复记为 stop_reason:null(而非 end_turn),
164
169
  // 所以判 idle 看「有内容」而非 stop_reason,否则正常回复会被误判成 thinking。
165
- const hasText = content.some((b) => b && (b.type === 'text' || b.type === 'thinking'))
170
+ // 只有 thinking / redacted_thinking 时仍是思考中,不能据此判定完成。
171
+ const hasText = content.some((b) => b && b.type === 'text' && Boolean(b.text))
166
172
  || (typeof (msg && msg.content) === 'string' && String(msg && msg.content).length > 0);
167
173
  return { state: hasText ? 'idle' : 'thinking' };
168
174
  }
@@ -187,7 +193,7 @@ function getRecentStatus(transcriptPath) {
187
193
  const cf = statusCacheFileFor(transcriptPath);
188
194
  try {
189
195
  const c = JSON.parse(fs_1.default.readFileSync(cf, 'utf8'));
190
- if (c.mtimeMs === st.mtimeMs && c.size === st.size)
196
+ if (c.version === STATUS_CACHE_VERSION && c.mtimeMs === st.mtimeMs && c.size === st.size)
191
197
  return c.status;
192
198
  }
193
199
  catch {
@@ -196,7 +202,7 @@ function getRecentStatus(transcriptPath) {
196
202
  const status = extractRecentStatus(readTailLines(transcriptPath));
197
203
  try {
198
204
  fs_1.default.mkdirSync(path_1.default.dirname(cf), { recursive: true });
199
- const payload = { mtimeMs: st.mtimeMs, size: st.size, status };
205
+ const payload = { version: STATUS_CACHE_VERSION, mtimeMs: st.mtimeMs, size: st.size, status };
200
206
  fs_1.default.writeFileSync(cf, JSON.stringify(payload));
201
207
  }
202
208
  catch {
@@ -65,7 +65,7 @@ const status = (ctx) => {
65
65
  return null;
66
66
  };
67
67
  const model = (ctx) => ctx.data.model && (ctx.data.model.display_name || ctx.data.model.id)
68
- ? iconSeg(ctx, 'model', colors_1.C.model, 'model', (0, format_1.modelName)(ctx.data.model)) : null;
68
+ ? iconSeg(ctx, 'model', colors_1.C.model, 'model', (0, format_1.modelName)(ctx.data.model, ctx.data.context_window?.context_window_size)) : null;
69
69
  const agent = (ctx) => ctx.data.agent?.name ? iconSeg(ctx, 'agent', colors_1.C.agent, 'agent', ctx.data.agent.name) : null;
70
70
  // 旧版只有 effort.level;新版额外提供 thinking.enabled。字段缺失时保持旧输出。
71
71
  const effort = ({ data }) => {
@@ -160,7 +160,7 @@ const rateLimits = (ctx) => {
160
160
  : pct >= 10 ? '🌒' : '🌑';
161
161
  return (0, colors_1.paint)(color, glyph, bold);
162
162
  };
163
- const pushWindow = (label, win, allowOver100 = false) => {
163
+ const pushWindow = (label, win, allowOver100 = false, amounts) => {
164
164
  const pct = nonNegative(win?.used_percentage);
165
165
  if (pct == null)
166
166
  return;
@@ -173,6 +173,8 @@ const rateLimits = (ctx) => {
173
173
  meter(pct, winColor),
174
174
  (0, colors_1.paint)(winColor, fmtRatePct(pct, allowOver100), bold),
175
175
  ];
176
+ if (amounts)
177
+ detail.push((0, colors_1.paint)(normal, amounts, bold));
176
178
  if (reset)
177
179
  detail.push((0, colors_1.paint)(colors_1.C.rateReset, '⏳', bold), (0, colors_1.paint)(colors_1.C.rateReset, reset, bold));
178
180
  parts.push(detail.join(' '));
@@ -185,7 +187,16 @@ const rateLimits = (ctx) => {
185
187
  pushWindow(labels.seven_day, rl.seven_day);
186
188
  }
187
189
  if (wants(['all', 'spend_limit'])) {
188
- pushWindow(labels.spend_limit, rl.spend_limit, true);
190
+ const spend = rl.spend_limit;
191
+ const usedUsd = nonNegative(spend?.used_usd);
192
+ const limitUsd = positive(spend?.limit_usd);
193
+ const period = typeof spend?.period === 'string' ? spend.period.trim() : '';
194
+ // Dollar amounts are fetched separately and may lag the percentage. Never
195
+ // derive a replacement percentage from them or fabricate a missing amount.
196
+ const amounts = usedUsd != null && limitUsd != null
197
+ ? `${(0, format_1.fmtCost)(usedUsd)}/${(0, format_1.fmtCost)(limitUsd)}${period ? ` ${period}` : ''}`
198
+ : undefined;
199
+ pushWindow(labels.spend_limit, spend, true, amounts);
189
200
  }
190
201
  if (!parts.length)
191
202
  return null;
@@ -21,12 +21,20 @@ const TOKEN_CASE = {
21
21
  llm: 'LLM',
22
22
  vl: 'VL',
23
23
  };
24
- function contextSuffix(id) {
24
+ function contextSuffix(id, size) {
25
+ // New default-1M models may no longer carry [1m]. A valid live size also
26
+ // overrides a stale display-name/id hint when the session uses a smaller window.
27
+ if (typeof size === 'number' && Number.isFinite(size) && size > 0) {
28
+ return size >= 1000000 ? `${Number((size / 1000000).toFixed(2))}M` : '';
29
+ }
25
30
  return /\[1m\]/i.test(id) ? '1M' : '';
26
31
  }
27
- function appendContextSuffix(name, id) {
28
- const suffix = contextSuffix(id);
29
- return suffix && !new RegExp(`\\b${suffix}\\b`, 'i').test(name) ? `${name} ${suffix}` : name;
32
+ function appendContextSuffix(name, id, size) {
33
+ if (typeof size === 'number' && Number.isFinite(size) && size > 0) {
34
+ name = name.replace(/\s+\d+(?:\.\d+)?\s*[km]$/i, '').trim();
35
+ }
36
+ const suffix = contextSuffix(id, size);
37
+ return suffix && !new RegExp(`\\b${suffix.replace(/\./g, '\\.')}\\b`, 'i').test(name) ? `${name} ${suffix}` : name;
30
38
  }
31
39
  function matchClaudeFamily(raw) {
32
40
  const source = raw.toLowerCase();
@@ -45,7 +53,7 @@ function matchClaudeFamily(raw) {
45
53
  function normalizeDisplayName(displayName) {
46
54
  return displayName
47
55
  .replace(/^Claude\s+/i, '')
48
- .replace(/\s*\((\d+)\s*([km])(?:\s+context)?\)/i, ' $1$2')
56
+ .replace(/\s*\((\d+(?:\.\d+)?)\s*([km])(?:\s+context)?\)/i, ' $1$2')
49
57
  .replace(/\s+/g, ' ')
50
58
  .trim();
51
59
  }
@@ -85,12 +93,12 @@ function readableModelId(id) {
85
93
  return tokens.map(titleToken).join(' ').trim();
86
94
  }
87
95
  // 模型名以 Claude Code stdin 的 display_name 为准;id 只在 display_name 缺失时兜底。
88
- function modelName(model) {
96
+ function modelName(model, contextWindowSize) {
89
97
  const id = (model.id || '').trim();
90
98
  const displayName = (model.display_name || '').trim();
91
99
  const normalizedDisplay = normalizeDisplayName(displayName);
92
100
  const baseName = normalizedDisplay || matchClaudeFamily(id) || readableModelId(id);
93
- return appendContextSuffix(baseName, id);
101
+ return appendContextSuffix(baseName, id, contextWindowSize);
94
102
  }
95
103
  // 12345 → 12.3k
96
104
  const fmtTokens = (n) => n == null ? '0' : n < 1000 ? String(n)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cxmyu/ccglance",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A fast, zero-dependency Claude Code status line for model, effort, context, prompt cache, usage limits, git and session.",
5
5
  "bin": {
6
6
  "ccglance": "dist/cli.js"