@cxmyu/ccglance 1.0.3 → 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 CHANGED
@@ -56,7 +56,7 @@ Update or remove later with `npm update -g @cxmyu/ccglance` / `npm uninstall -g
56
56
  ```bash
57
57
  git clone https://github.com/CxMYu/CcGlanceLine.git
58
58
  cd CcGlanceLine
59
- npm install # installs devDeps; the `prepare` script then compiles src/ → dist/
59
+ npm install # installs devDeps; the `prepare` script then compiles src/ -> dist/
60
60
  npm link # optional: expose `ccglance` on your PATH
61
61
  ```
62
62
 
@@ -102,10 +102,17 @@ 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 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`).
111
+
105
112
  ## Contributing
106
113
 
107
114
  Issues and pull requests are welcome. For code changes, run `npm run typecheck`,
108
- `npm run build`, and `ccglance preview` before opening a PR; keep the runtime at
115
+ `npm run build`, and `npm run preview` before opening a PR; keep the runtime at
109
116
  **zero dependencies** and the style fixed (no config loader).
110
117
 
111
118
  ## Star History
package/README.zh-CN.md CHANGED
@@ -54,7 +54,7 @@ npm 会下载**预编译好的 `dist/`**(本机不跑编译器),并把 `ccglance
54
54
  ```bash
55
55
  git clone https://github.com/CxMYu/CcGlanceLine.git
56
56
  cd CcGlanceLine
57
- npm install # 安装 devDeps;随后 prepare 脚本自动编译 src/ → dist/
57
+ npm install # 安装 devDeps;随后 prepare 脚本自动编译 src/ -> dist/
58
58
  npm link # 可选:把 ccglance 链到本机 PATH
59
59
  ```
60
60
 
@@ -97,9 +97,14 @@ Windows 用完整路径,例如 `node D:\\path\\to\\ccglance\\dist\\cli.js`。
97
97
  > 全局的 `ccglance` 命令则完全不需要写路径。`"padding": 0` 去掉 Claude Code 默认的左侧缩进,
98
98
  > 让状态栏从最左开始。
99
99
 
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`)。
104
+
100
105
  ## 贡献
101
106
 
102
- 欢迎提 issue 和 PR。改代码前请先跑 `npm run typecheck`、`npm run build`、`ccglance preview`;
107
+ 欢迎提 issue 和 PR。改代码前请先跑 `npm run typecheck`、`npm run build`、`npm run preview`;
103
108
  请保持**运行时零依赖**和固定风格(不加配置加载器)。
104
109
 
105
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();
@@ -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
- try {
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
+ }
@@ -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
- const bg = "const https=require('https'),fs=require('fs'),path=require('path');" +
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",
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",