@cxmyu/ccglance 1.0.2 → 1.1.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 +19 -123
- package/README.zh-CN.md +16 -102
- package/dist/cli.js +3 -0
- package/dist/runtime/bg.js +208 -0
- package/dist/runtime/git.js +3 -60
- package/dist/runtime/refresh.js +78 -0
- package/dist/runtime/version.js +3 -15
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,72 +1,23 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# ccglance
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
**A fast, zero-dependency, multi-line status line for [Claude Code](https://docs.anthropic.com/en/docs/claude-code)**
|
|
6
|
+
|
|
7
|
+
model · effort · context · cache · git · session — all at a glance
|
|
4
8
|
|
|
5
9
|
[](https://nodejs.org/)
|
|
6
10
|
[](./LICENSE)
|
|
7
11
|
|
|
8
|
-
English
|
|
9
|
-
|
|
10
|
-
Repository: [github.com/CxMYu/CcGlanceLine](https://github.com/CxMYu/CcGlanceLine)
|
|
11
|
-
|
|
12
|
-
`ccglance` uses the JSON that Claude Code pipes to its status-line command
|
|
13
|
-
on stdin as its primary data source; the session `status` segment reads only
|
|
14
|
-
the transcript tail when enabled. It's written in
|
|
15
|
-
**TypeScript** and ships as compiled JavaScript with **zero runtime
|
|
16
|
-
dependencies** (Node standard library only).
|
|
12
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md) · [GitHub](https://github.com/CxMYu/CcGlanceLine)
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [Segment & icon reference](./docs/segments.md) — every segment's data source, all icon meanings, rate-limit levels and color semantics
|
|
21
|
-
- [Development](./docs/development.md) — build, test, source layout, contribution practices and git cache internals
|
|
14
|
+

|
|
22
15
|
|
|
23
|
-
|
|
16
|
+
</div>
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
> **stdin-first · zero runtime dependencies · fixed emoji style** — renders the JSON Claude Code pipes to its status-line command; only the `status` segment reads a bounded transcript tail. Written in TypeScript, shipped as compiled JavaScript; runs on Node.js ≥ 22 or Bun.
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## Features
|
|
30
|
-
|
|
31
|
-
- **Compact multi-line layout** — runtime, quota and project/session state are
|
|
32
|
-
separate logical rows; empty rows disappear.
|
|
33
|
-
- **Zero runtime dependencies** — Node standard library only; TypeScript is a
|
|
34
|
-
build-time tool, never shipped in the published package.
|
|
35
|
-
- **stdin-first** — core segments consume the JSON Claude Code provides; status
|
|
36
|
-
reads a bounded transcript tail only when used.
|
|
37
|
-
- **Rich context block** — usage %, this-turn input/output tokens, tokens left.
|
|
38
|
-
- **Prompt-cache block** — cache hit rate, read/write tokens.
|
|
39
|
-
- **Git segment** — branch, clean/dirty/conflict glyph, ahead/behind counts,
|
|
40
|
-
using one bounded `git status` call plus a short local cache.
|
|
41
|
-
- **Session segment** — elapsed time plus lines added/removed.
|
|
42
|
-
- **Claude Code version + update hint** — shows the `version` field from
|
|
43
|
-
Claude Code stdin and appends `↑latest` when the 4h local cache reports a
|
|
44
|
-
newer Claude Code release. Refresh runs after stdout and never blocks.
|
|
45
|
-
- **Additional session context** — 5h/7d rate-limit quota, USD cost and
|
|
46
|
-
worktree name next to the git branch when Claude Code provides it.
|
|
47
|
-
- **Responsive multi-line layout** — probes the terminal width
|
|
48
|
-
(`CCGLANCE_WIDTH` → `COLUMNS` → TTY → `tput`/`stty`) and wraps each row into
|
|
49
|
-
as many lines as fit, down to a minimum of **one segment per line** so
|
|
50
|
-
nothing gets hidden; display-width aware (CJK/emoji safe).
|
|
51
|
-
- **Fixed built-in style** — no user config file and no external style file;
|
|
52
|
-
the status line uses ccglance's own emoji-first style.
|
|
53
|
-
|
|
54
|
-
## Requirements
|
|
55
|
-
|
|
56
|
-
- Node.js **>= 22**
|
|
57
|
-
- `git` on `PATH` (optional — only for the git segment)
|
|
58
|
-
|
|
59
|
-
Claude Code compatibility:
|
|
60
|
-
|
|
61
|
-
| Claude Code CLI | ccglance behavior |
|
|
62
|
-
|---|---|
|
|
63
|
-
| >= 1.0.71 | Basic status-line stdin support |
|
|
64
|
-
| >= 2.1.80 | Subscription quota row when official `rate_limits.five_hour` / `rate_limits.seven_day` fields are present |
|
|
65
|
-
| >= 2.1.153 | Preferred terminal-width sizing through `COLUMNS` / `LINES`; older versions fall back to TTY width or 80 columns |
|
|
66
|
-
|
|
67
|
-
The quota row is subscription-only. Claude.ai Pro/Max-style sessions can expose
|
|
68
|
-
`rate_limits`; API-key, Bedrock, Vertex, and other usage-based sessions usually
|
|
69
|
-
do not, so ccglance hides the quota row instead of inferring one.
|
|
20
|
+
**Docs:** [Overview](./docs/overview.md) · [Segments & icons](./docs/segments.md) · [Development](./docs/development.md)
|
|
70
21
|
|
|
71
22
|
## Install
|
|
72
23
|
|
|
@@ -76,6 +27,7 @@ do not, so ccglance hides the quota row instead of inferring one.
|
|
|
76
27
|
npm install -g @cxmyu/ccglance # npm
|
|
77
28
|
yarn global add @cxmyu/ccglance # or yarn
|
|
78
29
|
pnpm add -g @cxmyu/ccglance # or pnpm
|
|
30
|
+
bun add -g @cxmyu/ccglance # or bun
|
|
79
31
|
```
|
|
80
32
|
|
|
81
33
|
Behind a slow registry? Use a mirror:
|
|
@@ -104,7 +56,7 @@ Update or remove later with `npm update -g @cxmyu/ccglance` / `npm uninstall -g
|
|
|
104
56
|
```bash
|
|
105
57
|
git clone https://github.com/CxMYu/CcGlanceLine.git
|
|
106
58
|
cd CcGlanceLine
|
|
107
|
-
npm install # installs devDeps; the `prepare` script then compiles src/
|
|
59
|
+
npm install # installs devDeps; the `prepare` script then compiles src/ -> dist/
|
|
108
60
|
npm link # optional: expose `ccglance` on your PATH
|
|
109
61
|
```
|
|
110
62
|
|
|
@@ -150,73 +102,17 @@ On Windows use a full path, e.g. `node D:\\path\\to\\ccglance\\dist\\cli.js`.
|
|
|
150
102
|
> command needs no path at all. `"padding": 0` drops Claude Code's default leading
|
|
151
103
|
> indent so the row starts at the left edge.
|
|
152
104
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
3. **Project / session** — 📁 dir · 🌿 git · 🏷️ session name · ⏱️ session · 💰 cost · 💩 version
|
|
160
|
-
|
|
161
|
-
**Full reference** — every segment's stdin source, all icon meanings, rate-limit
|
|
162
|
-
moon-phase levels, session-status icons and color semantics — lives in
|
|
163
|
-
**[docs/segments.md](./docs/segments.md)**.
|
|
164
|
-
|
|
165
|
-
## How it works
|
|
166
|
-
|
|
167
|
-
On every redraw Claude Code runs your status-line `command` and pipes a JSON
|
|
168
|
-
object to its **stdin** (model, effort, context window, cost, workspace,
|
|
169
|
-
version, …). `ccglance` reads that once via `fs.readFileSync(0)`, formats its
|
|
170
|
-
fixed rows, and **prints them first**. The `status` segment reads only a
|
|
171
|
-
bounded transcript tail. The version segment synchronously
|
|
172
|
-
reads only a tiny local cache; after stdout is written, ccglance refreshes the
|
|
173
|
-
Claude Code latest-version cache in a detached background process when it is
|
|
174
|
-
older than 4 hours. If the JSON can't be parsed it exits silently so it can never
|
|
175
|
-
break the CLI. Text from stdin, transcript, and git is sanitized before being
|
|
176
|
-
printed so terminal control sequences cannot escape the status line. The main
|
|
177
|
-
external calls are local `git` (single bounded status call with a short cache),
|
|
178
|
-
bounded transcript-tail reads, and the post-render detached npm registry check.
|
|
179
|
-
|
|
180
|
-
## Development
|
|
181
|
-
|
|
182
|
-
Source build, tests, benchmark, source layout, contribution practices and git
|
|
183
|
-
cache internals live in **[docs/development.md](./docs/development.md)**. Quick start:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
git clone https://github.com/CxMYu/CcGlanceLine.git
|
|
187
|
-
cd CcGlanceLine
|
|
188
|
-
npm install # devDeps + prepare build
|
|
189
|
-
npm test # build + node:test
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## Cache Files
|
|
193
|
-
|
|
194
|
-
ccglance stores only disposable runtime caches: git status, transcript-derived
|
|
195
|
-
status, and the Claude Code latest-version check. Files are grouped under
|
|
196
|
-
`git/`, `transcript/`, and `version/`.
|
|
197
|
-
|
|
198
|
-
Caches live under Claude Code's own config directory on every platform, so they
|
|
199
|
-
travel with your Claude setup and are trivial to clear:
|
|
200
|
-
|
|
201
|
-
```text
|
|
202
|
-
~/.claude/ccglance/
|
|
203
|
-
├── git/ # git status snapshots (one per repo)
|
|
204
|
-
├── transcript/ # transcript-derived session status
|
|
205
|
-
└── version/ # Claude Code latest-version check
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
If you set `CLAUDE_CONFIG_DIR` to relocate Claude Code's config, ccglance places
|
|
209
|
-
its caches under that directory instead. Deleting the whole `ccglance/` folder is
|
|
210
|
-
safe — every file is regenerated on the next render.
|
|
211
|
-
|
|
212
|
-
Git cache behavior (cache key, `HEAD` fallback, 20-minute TTL, detached refresh,
|
|
213
|
-
atomic writes) is documented in
|
|
214
|
-
[docs/development.md](./docs/development.md#git-cache-internals).
|
|
105
|
+
> **Windows latency tip:** the global `ccglance` command is an npm `.cmd` shim,
|
|
106
|
+
> which adds a cmd batch layer plus a PATH lookup on every redraw — tens to
|
|
107
|
+
> hundreds of milliseconds depending on antivirus and system cache state. For
|
|
108
|
+
> the lowest latency, point `command` directly at the compiled file:
|
|
109
|
+
> `node D:\\absolute\\path\\to\\ccglance\\dist\\cli.js` (a global install lives
|
|
110
|
+
> at `<npm global dir>\\node_modules\\@cxmyu\\ccglance\\dist\\cli.js`).
|
|
215
111
|
|
|
216
112
|
## Contributing
|
|
217
113
|
|
|
218
114
|
Issues and pull requests are welcome. For code changes, run `npm run typecheck`,
|
|
219
|
-
`npm run build`, and `
|
|
115
|
+
`npm run build`, and `npm run preview` before opening a PR; keep the runtime at
|
|
220
116
|
**zero dependencies** and the style fixed (no config loader).
|
|
221
117
|
|
|
222
118
|
## Star History
|
package/README.zh-CN.md
CHANGED
|
@@ -1,61 +1,23 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# ccglance
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
**面向 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 的快速、零依赖多行状态栏**
|
|
6
|
+
|
|
7
|
+
模型 · effort · 上下文 · 缓存 · git · 会话 —— 一眼尽览
|
|
4
8
|
|
|
5
9
|
[](https://nodejs.org/)
|
|
6
10
|
[](./LICENSE)
|
|
7
11
|
|
|
8
|
-
[English](./README.md)
|
|
9
|
-
|
|
10
|
-
仓库:[github.com/CxMYu/CcGlanceLine](https://github.com/CxMYu/CcGlanceLine)
|
|
11
|
-
|
|
12
|
-
`ccglance` 以 Claude Code 通过 stdin 传入的 JSON 为主数据源;`status`
|
|
13
|
-
段只在启用时读取 transcript(会话记录)尾部。它用 **TypeScript** 编写,以编译后的
|
|
14
|
-
JavaScript 发布,**运行时零依赖**(仅用 Node 标准库)。
|
|
15
|
-
|
|
16
|
-
## 文档
|
|
17
|
-
|
|
18
|
-
- [字段与图标参考](./docs/segments.zh-CN.md) —— 每个段的数据来源、所有图标含义、配额档位与颜色语义
|
|
19
|
-
- [开发](./docs/development.zh-CN.md) —— 构建、测试、代码结构、贡献范式与 git 缓存内部
|
|
20
|
-
|
|
21
|
-
## 预览
|
|
12
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md) · [GitHub](https://github.com/CxMYu/CcGlanceLine)
|
|
22
13
|
|
|
23
14
|

|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## 特性
|
|
16
|
+
</div>
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
- **运行时零依赖** —— 仅用 Node 标准库;TypeScript 仅为构建期工具,不进发布包。
|
|
31
|
-
- **stdin 优先** —— 核心段消费 Claude Code 提供的 JSON;status 只读 transcript 尾部固定字节。
|
|
32
|
-
- **上下文块** —— 占用百分比、本轮输入/输出 token、剩余 token。
|
|
33
|
-
- **缓存块** —— 缓存命中率、读取/写入 token。
|
|
34
|
-
- **git 段** —— 分支、干净/脏/冲突标记、领先/落后计数;只跑一次有界 `git status`,并带短缓存。
|
|
35
|
-
- **会话段** —— 耗时 + 新增/删除行数。
|
|
36
|
-
- **Claude Code 版本 + 更新提示** —— 显示 stdin 里的 Claude Code `version`;
|
|
37
|
-
本地 4 小时缓存发现新版时追加 `↑latest`。刷新在 stdout 写出后异步进行,不阻塞状态栏。
|
|
38
|
-
- **额外会话信息** —— 5小时/7天配额、美元成本,以及 Claude Code 提供时显示在 git 分支右侧的 worktree 名。
|
|
39
|
-
- **响应式多行布局** —— 读终端宽度(`COLUMNS`),把每行按能容纳的宽度自动折成多行;
|
|
40
|
-
正确处理 CJK/emoji 显示宽度。
|
|
41
|
-
- **固定内置风格** —— 不读取用户配置文件,也不读取外部样式文件;状态栏使用 ccglance 自己的 emoji 优先风格。
|
|
18
|
+
> **stdin 优先 · 运行时零依赖 · 固定 emoji 风格** —— 渲染 Claude Code 经 stdin 传入的 JSON;仅 `status` 段读取 transcript 尾部固定字节。TypeScript 编写,以编译后的 JavaScript 发布,可在 Node.js ≥ 22 或 Bun 上运行。
|
|
42
19
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- Node.js **>= 22**
|
|
46
|
-
- `git`(可选,仅 git 段需要)
|
|
47
|
-
|
|
48
|
-
Claude Code 兼容性:
|
|
49
|
-
|
|
50
|
-
| Claude Code CLI | ccglance 行为 |
|
|
51
|
-
|---|---|
|
|
52
|
-
| >= 1.0.71 | 支持基础 statusline stdin |
|
|
53
|
-
| >= 2.1.80 | 官方 `rate_limits.five_hour` / `rate_limits.seven_day` 字段存在时显示订阅配额行 |
|
|
54
|
-
| >= 2.1.153 | 优先使用 `COLUMNS` / `LINES` 做终端宽度适配;更旧版本回退到 TTY 宽度或 80 列 |
|
|
55
|
-
|
|
56
|
-
配额行只面向订阅场景。Claude.ai Pro/Max 这类订阅会话可能提供 `rate_limits`;
|
|
57
|
-
API key、Bedrock、Vertex 等按量/外部网关场景通常没有该字段,ccglance 会隐藏配额行,
|
|
58
|
-
不会自行推断或伪造。
|
|
20
|
+
**文档:** [概览](./docs/overview.zh-CN.md) · [字段与图标](./docs/segments.zh-CN.md) · [开发](./docs/development.zh-CN.md)
|
|
59
21
|
|
|
60
22
|
## 安装
|
|
61
23
|
|
|
@@ -65,6 +27,7 @@ API key、Bedrock、Vertex 等按量/外部网关场景通常没有该字段,c
|
|
|
65
27
|
npm install -g @cxmyu/ccglance # npm
|
|
66
28
|
yarn global add @cxmyu/ccglance # 或 yarn
|
|
67
29
|
pnpm add -g @cxmyu/ccglance # 或 pnpm
|
|
30
|
+
bun add -g @cxmyu/ccglance # 或 bun
|
|
68
31
|
```
|
|
69
32
|
|
|
70
33
|
registry 慢?用国内镜像:
|
|
@@ -91,7 +54,7 @@ npm 会下载**预编译好的 `dist/`**(本机不跑编译器),并把 `ccglance
|
|
|
91
54
|
```bash
|
|
92
55
|
git clone https://github.com/CxMYu/CcGlanceLine.git
|
|
93
56
|
cd CcGlanceLine
|
|
94
|
-
npm install # 安装 devDeps;随后 prepare 脚本自动编译 src/
|
|
57
|
+
npm install # 安装 devDeps;随后 prepare 脚本自动编译 src/ -> dist/
|
|
95
58
|
npm link # 可选:把 ccglance 链到本机 PATH
|
|
96
59
|
```
|
|
97
60
|
|
|
@@ -134,63 +97,14 @@ Windows 用完整路径,例如 `node D:\\path\\to\\ccglance\\dist\\cli.js`。
|
|
|
134
97
|
> 全局的 `ccglance` 命令则完全不需要写路径。`"padding": 0` 去掉 Claude Code 默认的左侧缩进,
|
|
135
98
|
> 让状态栏从最左开始。
|
|
136
99
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
1. **运行时** —— 🤖 模型 · 🧠 effort · 状态 · 🚀 fast · ⚡️ 上下文 · 💾 缓存 · 🎯 风格
|
|
142
|
-
2. **配额**(仅订阅会话)—— 📊 Hour / Week 配额仪表
|
|
143
|
-
3. **项目 / 会话** —— 📁 目录 · 🌿 git · 🏷️ 会话名 · ⏱️ 会话 · 💰 成本 · 💩 版本
|
|
144
|
-
|
|
145
|
-
**完整参考** —— 每个段的 stdin 来源、所有图标含义、配额月相档位、会话状态图标与颜色语义 ——
|
|
146
|
-
见 **[docs/segments.zh-CN.md](./docs/segments.zh-CN.md)**。
|
|
147
|
-
|
|
148
|
-
## 原理
|
|
149
|
-
|
|
150
|
-
每次刷新时,Claude Code 会运行你配置的 `command`,并把一段 JSON(模型、effort、
|
|
151
|
-
上下文窗口、cost、workspace、版本……)从 **stdin** 传入。`ccglance` 用
|
|
152
|
-
`fs.readFileSync(0)` 一次性读入、按固定行格式化并**先打印**;`status`
|
|
153
|
-
只会读取 transcript 尾部固定字节。版本段同步阶段只读本地小缓存;stdout 写出之后,如果
|
|
154
|
-
Claude Code 最新版缓存超过 4 小时,才 detached 后台刷新 npm registry。若 JSON 解析失败则
|
|
155
|
-
静默退出,绝不影响 CLI。来自 stdin、transcript、git 的文本在输出前会清理终端控制序列,
|
|
156
|
-
避免逃逸到状态栏之外。主要外部调用是本地 `git`(单次有界 status 调用并带短缓存)、按需
|
|
157
|
-
transcript 尾部读取,以及渲染后的后台版本检查。
|
|
158
|
-
|
|
159
|
-
## 开发
|
|
160
|
-
|
|
161
|
-
源码构建、测试、基准、代码结构、贡献范式与 git 缓存内部细节见
|
|
162
|
-
**[docs/development.zh-CN.md](./docs/development.zh-CN.md)**。快速开始:
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
git clone https://github.com/CxMYu/CcGlanceLine.git
|
|
166
|
-
cd CcGlanceLine
|
|
167
|
-
npm install # devDeps + prepare 构建
|
|
168
|
-
npm test # 构建 + node:test
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## 缓存文件
|
|
172
|
-
|
|
173
|
-
`ccglance` 只落盘可丢弃的运行缓存:git 状态、transcript 派生状态、Claude Code 最新版本缓存。
|
|
174
|
-
文件按 `git/`、`transcript/`、`version/` 分组。
|
|
175
|
-
|
|
176
|
-
所有平台都统一落在 Claude Code 自己的配置目录下,随 Claude 配置一起走、清理也方便:
|
|
177
|
-
|
|
178
|
-
```text
|
|
179
|
-
~/.claude/ccglance/
|
|
180
|
-
├── git/ # git 状态快照(每个仓库一个)
|
|
181
|
-
├── transcript/ # transcript 派生的会话状态
|
|
182
|
-
└── version/ # Claude Code 最新版本检查
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
若设置了 `CLAUDE_CONFIG_DIR` 改变 Claude Code 配置目录,ccglance 的缓存会跟着落到该目录下。
|
|
186
|
-
整个 `ccglance/` 目录随时可安全删除 —— 下次渲染会自动重建。
|
|
187
|
-
|
|
188
|
-
git 缓存逻辑(缓存 key、`HEAD` 兜底、20 分钟 TTL、detached 刷新、原子写)见
|
|
189
|
-
[docs/development.zh-CN.md](./docs/development.zh-CN.md#git-缓存内部细节)。
|
|
100
|
+
> **Windows 延时提示**:全局命令 `ccglance` 实际是 npm 的 `.cmd` shim,每次重绘要多走一层
|
|
101
|
+
> cmd 批处理 + PATH 查找,视杀软/系统缓存状态可能多几十到上百毫秒。追求最低延时时,
|
|
102
|
+
> 建议 `command` 直接写 `node D:\\绝对路径\\ccglance\\dist\\cli.js`(全局安装的包在
|
|
103
|
+
> `<npm 全局目录>\\node_modules\\@cxmyu\\ccglance\\dist\\cli.js`)。
|
|
190
104
|
|
|
191
105
|
## 贡献
|
|
192
106
|
|
|
193
|
-
欢迎提 issue 和 PR。改代码前请先跑 `npm run typecheck`、`npm run build`、`
|
|
107
|
+
欢迎提 issue 和 PR。改代码前请先跑 `npm run typecheck`、`npm run build`、`npm run preview`;
|
|
194
108
|
请保持**运行时零依赖**和固定风格(不加配置加载器)。
|
|
195
109
|
|
|
196
110
|
## Star History
|
package/dist/cli.js
CHANGED
|
@@ -70,6 +70,7 @@ if (argv[0] === 'preview') {
|
|
|
70
70
|
const { scheduleRefresh } = require('./runtime/version');
|
|
71
71
|
scheduleRefresh();
|
|
72
72
|
}
|
|
73
|
+
require('./runtime/refresh').flushRefreshTasks();
|
|
73
74
|
process.exit(0);
|
|
74
75
|
}
|
|
75
76
|
// 在真实终端直接运行(stdin 是 TTY、没有管道喂 JSON)时,显示帮助而非阻塞等待 stdin。
|
|
@@ -89,3 +90,5 @@ if (data.version && hasEnabledSegment(DEFAULT_SPEC, 'version')) {
|
|
|
89
90
|
const { scheduleRefresh } = require('./runtime/version');
|
|
90
91
|
scheduleRefresh();
|
|
91
92
|
}
|
|
93
|
+
// 渲染期间入队的后台刷新(git / version)最后一次性 spawn,不阻塞状态栏输出。
|
|
94
|
+
require('./runtime/refresh').flushRefreshTasks();
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// 后台刷新 helper —— 由 refresh.ts 以 detached 进程启动,绝不被状态栏进程等待。
|
|
7
|
+
// 任务列表从 argv[2] 指向的 task-*.json 读入,全部尽力而为、静默失败:
|
|
8
|
+
// git —— 单次有界 `git status --porcelain=v2 --branch`,解析后原子写缓存
|
|
9
|
+
// version —— 拉 npm registry 的 Claude Code 最新版本,原子写缓存
|
|
10
|
+
// 每个任务无论成败都要移除自己的 .refresh marker,否则只能等节流窗口过期。
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const paths_1 = require("../utils/paths");
|
|
14
|
+
const GIT_TIMEOUT_MS = 700;
|
|
15
|
+
// 缓存清理:每天最多一次(stamp 节流)。缓存 key 是仓库/transcript 路径的哈希,
|
|
16
|
+
// 临时目录一去不返时对应 json 便成死条目,按 mtime 过期删除(活跃条目会被刷新持续续命)。
|
|
17
|
+
const PRUNE_INTERVAL_MS = 24 * 3600 * 1000;
|
|
18
|
+
const PRUNE_MAX_AGE_MS = 30 * 24 * 3600 * 1000;
|
|
19
|
+
const PRUNE_TRANSIENT_MAX_AGE_MS = 3600 * 1000; // 残留 .refresh 标记 / .tmp 半成品 / task 文件
|
|
20
|
+
// git status --porcelain=v2 --branch 输出解析(与渲染端 GitCache 结构保持一致)。
|
|
21
|
+
function parseStatus(out) {
|
|
22
|
+
let head = '';
|
|
23
|
+
let oid = '';
|
|
24
|
+
let ahead = 0;
|
|
25
|
+
let behind = 0;
|
|
26
|
+
let dirty = false;
|
|
27
|
+
let conflict = false;
|
|
28
|
+
for (const raw of out.split(/\r?\n/)) {
|
|
29
|
+
const line = raw.trimEnd();
|
|
30
|
+
if (!line)
|
|
31
|
+
continue;
|
|
32
|
+
if (line.startsWith('# branch.head ')) {
|
|
33
|
+
head = line.slice('# branch.head '.length).trim();
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (line.startsWith('# branch.oid ')) {
|
|
37
|
+
oid = line.slice('# branch.oid '.length).trim();
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (line.startsWith('# branch.ab ')) {
|
|
41
|
+
const m = /# branch\.ab \+(\d+) -(\d+)/.exec(line);
|
|
42
|
+
if (m) {
|
|
43
|
+
ahead = parseInt(m[1], 10) || 0;
|
|
44
|
+
behind = parseInt(m[2], 10) || 0;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (line[0] === '#')
|
|
49
|
+
continue;
|
|
50
|
+
if (line.startsWith('u '))
|
|
51
|
+
conflict = true;
|
|
52
|
+
if (!line.startsWith('! '))
|
|
53
|
+
dirty = true;
|
|
54
|
+
}
|
|
55
|
+
const branch = head && head !== '(detached)' ? head : oid.slice(0, 8);
|
|
56
|
+
if (!branch)
|
|
57
|
+
return null;
|
|
58
|
+
return { branch, glyph: conflict ? '⚠' : dirty ? '●' : '✓', ahead, behind };
|
|
59
|
+
}
|
|
60
|
+
function atomicWrite(file, data) {
|
|
61
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
62
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
63
|
+
fs_1.default.writeFileSync(tmp, data);
|
|
64
|
+
fs_1.default.renameSync(tmp, file);
|
|
65
|
+
}
|
|
66
|
+
function rmMarker(marker) {
|
|
67
|
+
try {
|
|
68
|
+
fs_1.default.rmSync(marker, { force: true });
|
|
69
|
+
}
|
|
70
|
+
catch { /* ignore */ }
|
|
71
|
+
}
|
|
72
|
+
function runGitTask(root, cacheFile) {
|
|
73
|
+
const { execFileSync } = require('child_process');
|
|
74
|
+
const out = execFileSync('git', ['--no-optional-locks', '-C', root, 'status', '--porcelain=v2', '--branch'], {
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
77
|
+
timeout: GIT_TIMEOUT_MS,
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
});
|
|
80
|
+
const info = parseStatus(out);
|
|
81
|
+
if (info)
|
|
82
|
+
atomicWrite(cacheFile, JSON.stringify({ ...info, checkedAt: Date.now() }));
|
|
83
|
+
}
|
|
84
|
+
function runVersionTask(cacheFile, done) {
|
|
85
|
+
let prev = '';
|
|
86
|
+
try {
|
|
87
|
+
prev = JSON.parse(fs_1.default.readFileSync(cacheFile, 'utf8')).latest || '';
|
|
88
|
+
}
|
|
89
|
+
catch { /* no previous cache */ }
|
|
90
|
+
let finished = false;
|
|
91
|
+
const finish = (latest) => {
|
|
92
|
+
if (finished)
|
|
93
|
+
return;
|
|
94
|
+
finished = true;
|
|
95
|
+
try {
|
|
96
|
+
atomicWrite(cacheFile, JSON.stringify({ latest: latest || prev, checkedAt: Date.now() }));
|
|
97
|
+
}
|
|
98
|
+
catch { /* ignore */ }
|
|
99
|
+
done();
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
const https = require('https');
|
|
103
|
+
const req = https.get('https://registry.npmjs.org/@anthropic-ai/claude-code/latest', (r) => {
|
|
104
|
+
if (r.statusCode !== 200) {
|
|
105
|
+
r.resume();
|
|
106
|
+
return finish('');
|
|
107
|
+
}
|
|
108
|
+
let d = '';
|
|
109
|
+
r.on('data', (c) => { d += c; });
|
|
110
|
+
r.on('end', () => {
|
|
111
|
+
try {
|
|
112
|
+
finish(JSON.parse(d).version || '');
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
finish('');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
req.on('error', () => finish(''));
|
|
120
|
+
req.setTimeout(5000, () => { req.destroy(); finish(''); });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
finish('');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// 过期清理:正常缓存 json 超过 30 天未被刷新(mtime)视为死条目;
|
|
127
|
+
// .refresh / .tmp / tasks 超过 1 小时视为进程夭折的残留。逐文件尽力而为。
|
|
128
|
+
function pruneCaches(nowMs = Date.now()) {
|
|
129
|
+
const root = (0, paths_1.appCacheDir)();
|
|
130
|
+
const stamp = path_1.default.join(root, 'prune.stamp');
|
|
131
|
+
try {
|
|
132
|
+
if (nowMs - fs_1.default.statSync(stamp).mtimeMs < PRUNE_INTERVAL_MS)
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
catch { /* 无 stamp → 首次清理 */ }
|
|
136
|
+
try {
|
|
137
|
+
fs_1.default.mkdirSync(root, { recursive: true });
|
|
138
|
+
fs_1.default.writeFileSync(stamp, String(nowMs));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const sub of ['git', 'transcript', 'version', 'tasks']) {
|
|
144
|
+
const dir = path_1.default.join(root, sub);
|
|
145
|
+
let names;
|
|
146
|
+
try {
|
|
147
|
+
names = fs_1.default.readdirSync(dir);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const name of names) {
|
|
153
|
+
const file = path_1.default.join(dir, name);
|
|
154
|
+
try {
|
|
155
|
+
const st = fs_1.default.statSync(file);
|
|
156
|
+
if (!st.isFile())
|
|
157
|
+
continue;
|
|
158
|
+
const age = nowMs - st.mtimeMs;
|
|
159
|
+
const transient = sub === 'tasks' || name.endsWith('.refresh') || name.endsWith('.tmp');
|
|
160
|
+
if (age > (transient ? PRUNE_TRANSIENT_MAX_AGE_MS : PRUNE_MAX_AGE_MS)) {
|
|
161
|
+
fs_1.default.rmSync(file, { force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function main() {
|
|
169
|
+
const taskFile = process.argv[2] || '';
|
|
170
|
+
try {
|
|
171
|
+
// 任务文件已由 refresh.ts 原子写入;读损坏时 markers 交给节流窗口 + prune 兜底。
|
|
172
|
+
const tasks = taskFile ? JSON.parse(fs_1.default.readFileSync(taskFile, 'utf8')) : [];
|
|
173
|
+
for (const task of tasks) {
|
|
174
|
+
if (task.kind === 'git') {
|
|
175
|
+
try {
|
|
176
|
+
runGitTask(task.root, task.cacheFile);
|
|
177
|
+
}
|
|
178
|
+
catch { /* best effort */ }
|
|
179
|
+
rmMarker(task.marker);
|
|
180
|
+
}
|
|
181
|
+
else if (task.kind === 'version') {
|
|
182
|
+
try {
|
|
183
|
+
runVersionTask(task.cacheFile, () => rmMarker(task.marker));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
rmMarker(task.marker);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// 任务文件缺失 / 损坏:静默退出,仍走 finally 清理。
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
if (taskFile) {
|
|
196
|
+
try {
|
|
197
|
+
fs_1.default.rmSync(taskFile, { force: true });
|
|
198
|
+
}
|
|
199
|
+
catch { /* ignore */ }
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
pruneCaches();
|
|
203
|
+
}
|
|
204
|
+
catch { /* best effort */ }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (require.main === module)
|
|
208
|
+
main();
|
package/dist/runtime/git.js
CHANGED
|
@@ -9,9 +9,9 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const colors_1 = require("../render/colors");
|
|
11
11
|
const paths_1 = require("../utils/paths");
|
|
12
|
+
const refresh_1 = require("./refresh");
|
|
12
13
|
const CACHE_DIR = (0, paths_1.cacheDir)('git');
|
|
13
14
|
const DEFAULT_TTL_MS = 20 * 60 * 1000;
|
|
14
|
-
const GIT_TIMEOUT_MS = 700;
|
|
15
15
|
const REFRESH_THROTTLE_MS = 5000;
|
|
16
16
|
function resolveGitPaths(cwd) {
|
|
17
17
|
const abs = path_1.default.resolve(cwd);
|
|
@@ -83,45 +83,6 @@ function shouldRefresh(paths) {
|
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
function parseStatus(out) {
|
|
87
|
-
let head = '';
|
|
88
|
-
let oid = '';
|
|
89
|
-
let ahead = 0;
|
|
90
|
-
let behind = 0;
|
|
91
|
-
let dirty = false;
|
|
92
|
-
let conflict = false;
|
|
93
|
-
for (const raw of out.split(/\r?\n/)) {
|
|
94
|
-
const line = raw.trimEnd();
|
|
95
|
-
if (!line)
|
|
96
|
-
continue;
|
|
97
|
-
if (line.startsWith('# branch.head ')) {
|
|
98
|
-
head = line.slice('# branch.head '.length).trim();
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (line.startsWith('# branch.oid ')) {
|
|
102
|
-
oid = line.slice('# branch.oid '.length).trim();
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (line.startsWith('# branch.ab ')) {
|
|
106
|
-
const m = /# branch\.ab \+(\d+) -(\d+)/.exec(line);
|
|
107
|
-
if (m) {
|
|
108
|
-
ahead = parseInt(m[1], 10) || 0;
|
|
109
|
-
behind = parseInt(m[2], 10) || 0;
|
|
110
|
-
}
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (line[0] === '#')
|
|
114
|
-
continue;
|
|
115
|
-
if (line.startsWith('u '))
|
|
116
|
-
conflict = true;
|
|
117
|
-
if (!line.startsWith('! '))
|
|
118
|
-
dirty = true;
|
|
119
|
-
}
|
|
120
|
-
const branch = head && head !== '(detached)' ? head : oid.slice(0, 8);
|
|
121
|
-
if (!branch)
|
|
122
|
-
return null;
|
|
123
|
-
return { branch, glyph: conflict ? '⚠' : dirty ? '●' : '✓', ahead, behind };
|
|
124
|
-
}
|
|
125
86
|
function readHeadFallback(root) {
|
|
126
87
|
if (!root)
|
|
127
88
|
return null;
|
|
@@ -149,29 +110,11 @@ function branchHintFallback(branchHint) {
|
|
|
149
110
|
const branch = branchHint && branchHint.trim();
|
|
150
111
|
return branch ? { branch, ahead: 0, behind: 0 } : null;
|
|
151
112
|
}
|
|
113
|
+
// 只入队不 spawn:gitSegment 在渲染期间被调用,spawn 留到 stdout 写出后统一 flush。
|
|
152
114
|
function scheduleGitRefresh(paths) {
|
|
153
115
|
if (!paths.root || !shouldRefresh(paths))
|
|
154
116
|
return;
|
|
155
|
-
|
|
156
|
-
const bg = "const fs=require('fs'),path=require('path'),{execFileSync}=require('child_process');" +
|
|
157
|
-
"const cwd=" + JSON.stringify(paths.root) + ";" +
|
|
158
|
-
"const cf=" + JSON.stringify(paths.cacheFile) + ";" +
|
|
159
|
-
"const marker=" + JSON.stringify(paths.refreshFile) + ";" +
|
|
160
|
-
"const parseStatus=" + parseStatus.toString() + ";" +
|
|
161
|
-
"const save=obj=>{const tmp=cf+'.'+process.pid+'.tmp';fs.mkdirSync(path.dirname(cf),{recursive:true});fs.writeFileSync(tmp,JSON.stringify(obj));fs.renameSync(tmp,cf)};" +
|
|
162
|
-
"try{" +
|
|
163
|
-
"const out=execFileSync('git',['--no-optional-locks','-C',cwd,'status','--porcelain=v2','--branch'],{encoding:'utf8',stdio:['ignore','pipe','ignore'],timeout:" + GIT_TIMEOUT_MS + ",windowsHide:true});" +
|
|
164
|
-
"const info=parseStatus(out);if(info)save({...info,checkedAt:Date.now()});" +
|
|
165
|
-
"}catch{}finally{try{fs.rmSync(marker,{force:true})}catch{}}";
|
|
166
|
-
const { spawn } = require('child_process');
|
|
167
|
-
spawn(process.execPath, ['-e', bg], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
try {
|
|
171
|
-
fs_1.default.rmSync(paths.refreshFile, { force: true });
|
|
172
|
-
}
|
|
173
|
-
catch { /* ignore */ }
|
|
174
|
-
}
|
|
117
|
+
(0, refresh_1.queueRefreshTask)({ kind: 'git', root: paths.root, cacheFile: paths.cacheFile, marker: paths.refreshFile });
|
|
175
118
|
}
|
|
176
119
|
function formatGit(info, icon, worktreeName) {
|
|
177
120
|
const status = [];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queueRefreshTask = queueRefreshTask;
|
|
7
|
+
exports.flushRefreshTasks = flushRefreshTasks;
|
|
8
|
+
// 后台刷新调度 —— 渲染期间各段只「入队」(调用方先抢占各自的 .refresh marker),
|
|
9
|
+
// stdout 写出后由 cli 调 flushRefreshTasks() 一次性 spawn 单个 detached helper(bg.js)。
|
|
10
|
+
//
|
|
11
|
+
// 为什么不各自直接 spawn:Windows 上发起一个 node.exe 子进程的同步成本很高
|
|
12
|
+
// (CreateProcess + 杀软扫描,实测 ~80-110ms),冷缓存首帧会连发两个,直接拖慢状态栏。
|
|
13
|
+
// 合并为一次,并在 Windows 上经 cmd `start /b` 蹦床发起(实测 ~17ms):node 的真实
|
|
14
|
+
// 启动成本由蹦床在本进程退出后承担,不再占用状态栏进程的生命周期。
|
|
15
|
+
// 任务负载写入 ~/.claude/ccglance/tasks/task-*.json,helper 只接收任务文件路径。
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const paths_1 = require("../utils/paths");
|
|
19
|
+
const queue = [];
|
|
20
|
+
// 入队前调用方必须已独占抢到对应 marker(多进程节流语义保持不变)。
|
|
21
|
+
function queueRefreshTask(task) {
|
|
22
|
+
queue.push(task);
|
|
23
|
+
}
|
|
24
|
+
function removeMarkers(tasks) {
|
|
25
|
+
for (const t of tasks) {
|
|
26
|
+
try {
|
|
27
|
+
fs_1.default.rmSync(t.marker, { force: true });
|
|
28
|
+
}
|
|
29
|
+
catch { /* ignore */ }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeTaskFile(tasks) {
|
|
33
|
+
const dir = (0, paths_1.cacheDir)('tasks');
|
|
34
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const file = path_1.default.join(dir, `task-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.json`);
|
|
36
|
+
const tmp = `${file}.tmp`;
|
|
37
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify(tasks));
|
|
38
|
+
fs_1.default.renameSync(tmp, file);
|
|
39
|
+
return file;
|
|
40
|
+
}
|
|
41
|
+
function quoteCmdArg(s) {
|
|
42
|
+
return `"${s}"`;
|
|
43
|
+
}
|
|
44
|
+
// 渲染输出完成后调用:把积累的刷新任务交给一个 detached 后台 helper 进程。
|
|
45
|
+
function flushRefreshTasks() {
|
|
46
|
+
if (!queue.length)
|
|
47
|
+
return;
|
|
48
|
+
const tasks = queue.splice(0);
|
|
49
|
+
let taskFile = '';
|
|
50
|
+
try {
|
|
51
|
+
const helper = path_1.default.join(__dirname, 'bg.js');
|
|
52
|
+
taskFile = writeTaskFile(tasks);
|
|
53
|
+
const { spawn } = require('child_process');
|
|
54
|
+
// Windows:node.exe 的 CreateProcess 同步成本很高(杀软扫描),改用 cmd `start /b`
|
|
55
|
+
// 蹦床(发起 ~17ms),真正的 node 启动开销由蹦床在本进程退出后承担。
|
|
56
|
+
// 任务本身在文件里;cmd 只需要转发 node/helper/taskFile 三个路径参数。
|
|
57
|
+
const child = process.platform === 'win32'
|
|
58
|
+
? spawn('cmd.exe', ['/d', '/s', '/c', `start "" /b ${quoteCmdArg(process.execPath)} ${quoteCmdArg(helper)} ${quoteCmdArg(taskFile)}`], { detached: true, stdio: 'ignore', windowsHide: true, windowsVerbatimArguments: true })
|
|
59
|
+
: spawn(process.execPath, [helper, taskFile], { detached: true, stdio: 'ignore' });
|
|
60
|
+
child.on('error', () => {
|
|
61
|
+
try {
|
|
62
|
+
fs_1.default.rmSync(taskFile, { force: true });
|
|
63
|
+
}
|
|
64
|
+
catch { /* ignore */ }
|
|
65
|
+
removeMarkers(tasks);
|
|
66
|
+
});
|
|
67
|
+
child.unref();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
if (taskFile) {
|
|
71
|
+
try {
|
|
72
|
+
fs_1.default.rmSync(taskFile, { force: true });
|
|
73
|
+
}
|
|
74
|
+
catch { /* ignore */ }
|
|
75
|
+
}
|
|
76
|
+
removeMarkers(tasks);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/runtime/version.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.scheduleRefresh = scheduleRefresh;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const paths_1 = require("../utils/paths");
|
|
11
|
+
const refresh_1 = require("./refresh");
|
|
11
12
|
const CACHE_TTL_MS = 4 * 3600 * 1000;
|
|
12
13
|
const REFRESH_THROTTLE_MS = 60 * 1000;
|
|
13
14
|
const cacheFile = path_1.default.join((0, paths_1.cacheDir)('version'), 'claude-code.json');
|
|
@@ -48,6 +49,7 @@ function shouldRefresh() {
|
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
// 只入队不 spawn:实际后台进程由 cli 在 stdout 写出后统一 flush(见 runtime/refresh.ts)。
|
|
51
53
|
function scheduleRefresh() {
|
|
52
54
|
try {
|
|
53
55
|
const age = Date.now() - (readCache().checkedAt || 0);
|
|
@@ -55,21 +57,7 @@ function scheduleRefresh() {
|
|
|
55
57
|
return;
|
|
56
58
|
if (!shouldRefresh())
|
|
57
59
|
return;
|
|
58
|
-
|
|
59
|
-
"const f=" + JSON.stringify(cacheFile) + ";" +
|
|
60
|
-
"const marker=" + JSON.stringify(refreshFile) + ";" +
|
|
61
|
-
"let prev='';try{prev=(JSON.parse(fs.readFileSync(f,'utf8')).latest)||''}catch{}" +
|
|
62
|
-
"let done=false;const finish=v=>{if(done)return;done=true;" +
|
|
63
|
-
"try{const tmp=f+'.'+process.pid+'.tmp';fs.mkdirSync(path.dirname(f),{recursive:true});fs.writeFileSync(tmp,JSON.stringify({latest:v||prev,checkedAt:Date.now()}));fs.renameSync(tmp,f)}catch{}" +
|
|
64
|
-
"try{fs.rmSync(marker,{force:true})}catch{}};" +
|
|
65
|
-
"try{" +
|
|
66
|
-
"const req=https.get('https://registry.npmjs.org/@anthropic-ai/claude-code/latest',r=>{" +
|
|
67
|
-
"if(r.statusCode!==200){r.resume();return finish('')}" +
|
|
68
|
-
"let d='';r.on('data',c=>d+=c);r.on('end',()=>{try{finish(JSON.parse(d).version)}catch{finish('')}})" +
|
|
69
|
-
"});req.on('error',()=>finish(''));req.setTimeout(5000,()=>{req.destroy();finish('')});" +
|
|
70
|
-
"}catch{finish('')}";
|
|
71
|
-
const { spawn } = require('child_process');
|
|
72
|
-
spawn(process.execPath, ['-e', bg], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
60
|
+
(0, refresh_1.queueRefreshTask)({ kind: 'version', cacheFile, marker: refreshFile });
|
|
73
61
|
}
|
|
74
62
|
catch {
|
|
75
63
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cxmyu/ccglance",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A fast, zero-dependency, multi-line status line for Claude Code — model, effort, context, cache, git and session, all at a glance.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ccglance": "dist/cli.js"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
23
23
|
"build": "npm run clean && tsc",
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
|
+
"preview": "node dist/cli.js preview",
|
|
25
26
|
"test": "npm run build && node --test test/*.test.js",
|
|
26
27
|
"benchmark": "npm run build && node bench/latency.js",
|
|
27
28
|
"prepare": "npm run build",
|