@cxmyu/ccglance 1.1.0 → 1.1.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/README.md +3 -6
- package/README.zh-CN.md +2 -4
- package/dist/runtime/bg.js +58 -25
- package/dist/runtime/refresh.js +14 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,12 +102,9 @@ On Windows use a full path, e.g. `node D:\\path\\to\\ccglance\\dist\\cli.js`.
|
|
|
102
102
|
> command needs no path at all. `"padding": 0` drops Claude Code's default leading
|
|
103
103
|
> indent so the row starts at the left edge.
|
|
104
104
|
|
|
105
|
-
> **Windows
|
|
106
|
-
>
|
|
107
|
-
>
|
|
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`).
|
|
105
|
+
> **Windows tip:** if status-line startup latency matters, point `command`
|
|
106
|
+
> directly at the compiled file (`node D:\\absolute\\path\\to\\ccglance\\dist\\cli.js`)
|
|
107
|
+
> instead of the global `ccglance` command — it usually starts a bit faster.
|
|
111
108
|
|
|
112
109
|
## Contributing
|
|
113
110
|
|
package/README.zh-CN.md
CHANGED
|
@@ -97,10 +97,8 @@ Windows 用完整路径,例如 `node D:\\path\\to\\ccglance\\dist\\cli.js`。
|
|
|
97
97
|
> 全局的 `ccglance` 命令则完全不需要写路径。`"padding": 0` 去掉 Claude Code 默认的左侧缩进,
|
|
98
98
|
> 让状态栏从最左开始。
|
|
99
99
|
|
|
100
|
-
> **Windows
|
|
101
|
-
>
|
|
102
|
-
> 建议 `command` 直接写 `node D:\\绝对路径\\ccglance\\dist\\cli.js`(全局安装的包在
|
|
103
|
-
> `<npm 全局目录>\\node_modules\\@cxmyu\\ccglance\\dist\\cli.js`)。
|
|
100
|
+
> **Windows 提速小贴士**:若在意状态栏启动延时,可把 `command` 直接指向编译产物
|
|
101
|
+
> `node D:\\绝对路径\\ccglance\\dist\\cli.js`,通常比走全局 `ccglance` 命令更快一些。
|
|
104
102
|
|
|
105
103
|
## 贡献
|
|
106
104
|
|
package/dist/runtime/bg.js
CHANGED
|
@@ -4,19 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
// 后台刷新 helper —— 由 refresh.ts 以 detached 进程启动,绝不被状态栏进程等待。
|
|
7
|
-
// 任务列表从 argv[2]
|
|
7
|
+
// 任务列表从 argv[2](JSON)读入,全部尽力而为、静默失败:
|
|
8
8
|
// git —— 单次有界 `git status --porcelain=v2 --branch`,解析后原子写缓存
|
|
9
9
|
// version —— 拉 npm registry 的 Claude Code 最新版本,原子写缓存
|
|
10
10
|
// 每个任务无论成败都要移除自己的 .refresh marker,否则只能等节流窗口过期。
|
|
11
|
+
// 每次运行还顺手清理孤儿产物:过期的 .refresh / .tmp(及历史遗留的 tasks/ 目录)。
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
13
14
|
const paths_1 = require("../utils/paths");
|
|
14
15
|
const GIT_TIMEOUT_MS = 700;
|
|
15
|
-
//
|
|
16
|
-
//
|
|
16
|
+
// 孤儿瞬时产物阈值:.refresh/.tmp 本应几秒内被删(helper 最长约 5s);超过此值即视为
|
|
17
|
+
// 夭折进程的残留。每次运行开头 + 结尾各清一次,故取较短的 2 分钟让残留更快被接管清理。
|
|
18
|
+
const STALE_TRANSIENT_MS = 2 * 60 * 1000;
|
|
19
|
+
// 死缓存清理:缓存 key 是仓库/transcript 路径哈希,临时目录一去不返时对应 json 便成死条目。
|
|
20
|
+
// 每天最多清一次(stamp 节流),删除长期未刷新的缓存 json(活跃条目会被刷新持续续命)。
|
|
17
21
|
const PRUNE_INTERVAL_MS = 24 * 3600 * 1000;
|
|
18
22
|
const PRUNE_MAX_AGE_MS = 30 * 24 * 3600 * 1000;
|
|
19
|
-
const PRUNE_TRANSIENT_MAX_AGE_MS = 3600 * 1000; // 残留 .refresh 标记 / .tmp 半成品 / task 文件
|
|
20
23
|
// git status --porcelain=v2 --branch 输出解析(与渲染端 GitCache 结构保持一致)。
|
|
21
24
|
function parseStatus(out) {
|
|
22
25
|
let head = '';
|
|
@@ -123,9 +126,40 @@ function runVersionTask(cacheFile, done) {
|
|
|
123
126
|
finish('');
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
function
|
|
129
|
+
// 每次运行都做的轻清理:删除过期的孤儿瞬时产物(.refresh / .tmp),以及历史遗留的
|
|
130
|
+
// tasks/ 目录内容。成本很低(这些目录文件很少),能让残留不再累积。
|
|
131
|
+
function pruneStaleTransient(nowMs = Date.now()) {
|
|
132
|
+
const root = (0, paths_1.appCacheDir)();
|
|
133
|
+
for (const sub of ['git', 'version', 'transcript', 'tasks']) {
|
|
134
|
+
const dir = path_1.default.join(root, sub);
|
|
135
|
+
let names;
|
|
136
|
+
try {
|
|
137
|
+
names = fs_1.default.readdirSync(dir);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
for (const name of names) {
|
|
143
|
+
// tasks/ 已弃用:其中任何文件都算历史遗留;其他目录只清 .refresh / .tmp 瞬时产物。
|
|
144
|
+
if (sub !== 'tasks' && !name.endsWith('.refresh') && !name.endsWith('.tmp'))
|
|
145
|
+
continue;
|
|
146
|
+
const file = path_1.default.join(dir, name);
|
|
147
|
+
try {
|
|
148
|
+
if (nowMs - fs_1.default.statSync(file).mtimeMs > STALE_TRANSIENT_MS)
|
|
149
|
+
fs_1.default.rmSync(file, { force: true });
|
|
150
|
+
}
|
|
151
|
+
catch { /* ignore */ }
|
|
152
|
+
}
|
|
153
|
+
if (sub === 'tasks') {
|
|
154
|
+
try {
|
|
155
|
+
fs_1.default.rmdirSync(dir);
|
|
156
|
+
}
|
|
157
|
+
catch { /* 非空/不存在都忽略 */ }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 每天最多一次(stamp 节流):删除长期未刷新的死缓存 json。
|
|
162
|
+
function pruneDeadCaches(nowMs = Date.now()) {
|
|
129
163
|
const root = (0, paths_1.appCacheDir)();
|
|
130
164
|
const stamp = path_1.default.join(root, 'prune.stamp');
|
|
131
165
|
try {
|
|
@@ -140,7 +174,7 @@ function pruneCaches(nowMs = Date.now()) {
|
|
|
140
174
|
catch {
|
|
141
175
|
return;
|
|
142
176
|
}
|
|
143
|
-
for (const sub of ['git', 'transcript', 'version'
|
|
177
|
+
for (const sub of ['git', 'transcript', 'version']) {
|
|
144
178
|
const dir = path_1.default.join(root, sub);
|
|
145
179
|
let names;
|
|
146
180
|
try {
|
|
@@ -150,26 +184,27 @@ function pruneCaches(nowMs = Date.now()) {
|
|
|
150
184
|
continue;
|
|
151
185
|
}
|
|
152
186
|
for (const name of names) {
|
|
187
|
+
if (!name.endsWith('.json'))
|
|
188
|
+
continue;
|
|
153
189
|
const file = path_1.default.join(dir, name);
|
|
154
190
|
try {
|
|
155
|
-
|
|
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)) {
|
|
191
|
+
if (nowMs - fs_1.default.statSync(file).mtimeMs > PRUNE_MAX_AGE_MS)
|
|
161
192
|
fs_1.default.rmSync(file, { force: true });
|
|
162
|
-
}
|
|
163
193
|
}
|
|
164
194
|
catch { /* ignore */ }
|
|
165
195
|
}
|
|
166
196
|
}
|
|
167
197
|
}
|
|
168
198
|
function main() {
|
|
169
|
-
const
|
|
199
|
+
const payload = process.argv[2] || '';
|
|
200
|
+
// 开头先接管清理上一轮遗留的孤儿(含被强杀进程没来得及删的 marker):即便本次任务中途
|
|
201
|
+
// 被杀、finally 没跑到,这些残留也会在下一次后台刷新一启动时被清掉,不会长期堆积。
|
|
170
202
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
pruneStaleTransient();
|
|
204
|
+
}
|
|
205
|
+
catch { /* best effort */ }
|
|
206
|
+
try {
|
|
207
|
+
const tasks = payload ? JSON.parse(payload) : [];
|
|
173
208
|
for (const task of tasks) {
|
|
174
209
|
if (task.kind === 'git') {
|
|
175
210
|
try {
|
|
@@ -189,17 +224,15 @@ function main() {
|
|
|
189
224
|
}
|
|
190
225
|
}
|
|
191
226
|
catch {
|
|
192
|
-
//
|
|
227
|
+
// payload 缺失 / 损坏:静默退出,仍走 finally 清理。
|
|
193
228
|
}
|
|
194
229
|
finally {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
fs_1.default.rmSync(taskFile, { force: true });
|
|
198
|
-
}
|
|
199
|
-
catch { /* ignore */ }
|
|
230
|
+
try {
|
|
231
|
+
pruneStaleTransient();
|
|
200
232
|
}
|
|
233
|
+
catch { /* best effort */ }
|
|
201
234
|
try {
|
|
202
|
-
|
|
235
|
+
pruneDeadCaches();
|
|
203
236
|
}
|
|
204
237
|
catch { /* best effort */ }
|
|
205
238
|
}
|
package/dist/runtime/refresh.js
CHANGED
|
@@ -6,16 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.queueRefreshTask = queueRefreshTask;
|
|
7
7
|
exports.flushRefreshTasks = flushRefreshTasks;
|
|
8
8
|
// 后台刷新调度 —— 渲染期间各段只「入队」(调用方先抢占各自的 .refresh marker),
|
|
9
|
-
// stdout 写出后由 cli 调 flushRefreshTasks() 一次性 spawn
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// (CreateProcess + 杀软扫描,实测 ~80-110ms),冷缓存首帧会连发两个,直接拖慢状态栏。
|
|
13
|
-
// 合并为一次,并在 Windows 上经 cmd `start /b` 蹦床发起(实测 ~17ms):node 的真实
|
|
14
|
-
// 启动成本由蹦床在本进程退出后承担,不再占用状态栏进程的生命周期。
|
|
15
|
-
// 任务负载写入 ~/.claude/ccglance/tasks/task-*.json,helper 只接收任务文件路径。
|
|
9
|
+
// stdout 写出后由 cli 调 flushRefreshTasks() 一次性 spawn 单个后台 helper(bg.js)。
|
|
10
|
+
// 合并为一次 spawn,避免冷缓存首帧连发两个后台进程;缓存都新鲜时 queue 为空、直接返回,
|
|
11
|
+
// 常规重绘不起任何后台进程。任务数据经 argv 直接传给 helper(不经 shell、无需临时文件)。
|
|
16
12
|
const fs_1 = __importDefault(require("fs"));
|
|
17
13
|
const path_1 = __importDefault(require("path"));
|
|
18
|
-
const paths_1 = require("../utils/paths");
|
|
19
14
|
const queue = [];
|
|
20
15
|
// 入队前调用方必须已独占抢到对应 marker(多进程节流语义保持不变)。
|
|
21
16
|
function queueRefreshTask(task) {
|
|
@@ -29,50 +24,29 @@ function removeMarkers(tasks) {
|
|
|
29
24
|
catch { /* ignore */ }
|
|
30
25
|
}
|
|
31
26
|
}
|
|
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
27
|
// 渲染输出完成后调用:把积累的刷新任务交给一个 detached 后台 helper 进程。
|
|
45
28
|
function flushRefreshTasks() {
|
|
46
29
|
if (!queue.length)
|
|
47
30
|
return;
|
|
48
31
|
const tasks = queue.splice(0);
|
|
49
|
-
let taskFile = '';
|
|
50
32
|
try {
|
|
51
33
|
const helper = path_1.default.join(__dirname, 'bg.js');
|
|
52
|
-
|
|
34
|
+
const payload = JSON.stringify(tasks);
|
|
53
35
|
const { spawn } = require('child_process');
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
child.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
catch { /* ignore */ }
|
|
65
|
-
removeMarkers(tasks);
|
|
36
|
+
// 直接 detached spawn node,不经 shell:
|
|
37
|
+
// - 任务数据用一个 argv 参数(JSON)传给 helper;spawn 不走 shell,无引号/转义问题,
|
|
38
|
+
// 因此不需要临时任务文件(历史上用过 task-*.json,是残留主因,已弃用)。
|
|
39
|
+
// - detached 让 helper 脱离父进程独立跑完,并在结束时清理自己的 marker,避免残留。
|
|
40
|
+
// - Windows 关键坑:绝不同时设 detached + windowsHide —— 两个 flag 冲突会意外弹出
|
|
41
|
+
// 控制台黑窗。detached 单独即 DETACHED_PROCESS,本就不分配控制台,因此静默。
|
|
42
|
+
const child = spawn(process.execPath, [helper, payload], {
|
|
43
|
+
detached: true,
|
|
44
|
+
stdio: 'ignore',
|
|
66
45
|
});
|
|
46
|
+
child.on('error', () => removeMarkers(tasks));
|
|
67
47
|
child.unref();
|
|
68
48
|
}
|
|
69
49
|
catch {
|
|
70
|
-
if (taskFile) {
|
|
71
|
-
try {
|
|
72
|
-
fs_1.default.rmSync(taskFile, { force: true });
|
|
73
|
-
}
|
|
74
|
-
catch { /* ignore */ }
|
|
75
|
-
}
|
|
76
50
|
removeMarkers(tasks);
|
|
77
51
|
}
|
|
78
52
|
}
|
package/package.json
CHANGED