cc-dev-template 0.1.102 → 0.1.104
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/package.json +1 -1
- package/src/scripts/statusline.js +76 -48
package/package.json
CHANGED
|
@@ -13,15 +13,18 @@
|
|
|
13
13
|
* Output: Formatted status to stdout
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const { readFileSync, writeFileSync, readdirSync, statSync } = require('fs');
|
|
16
|
+
const { readFileSync, writeFileSync, readdirSync, statSync, unlinkSync } = require('fs');
|
|
17
17
|
const { join, basename } = require('path');
|
|
18
18
|
const { execSync, spawnSync, spawn } = require('child_process');
|
|
19
19
|
const { homedir } = require('os');
|
|
20
20
|
|
|
21
21
|
// Usage API cache
|
|
22
22
|
const USAGE_CACHE_PATH = join(homedir(), '.claude', '.usage-cache.json');
|
|
23
|
-
const
|
|
24
|
-
const
|
|
23
|
+
const USAGE_LOCK_PATH = join(homedir(), '.claude', '.usage-cache.lock');
|
|
24
|
+
const USAGE_CACHE_TTL = 30000; // 30s normal refresh interval
|
|
25
|
+
const USAGE_ERROR_TTL = 300000; // 5 min backoff on errors (rate limit, network, etc.)
|
|
26
|
+
const USAGE_LOCK_TTL = 15000; // 15s lock (curl timeout is 5s, 15s is generous)
|
|
27
|
+
const USAGE_HISTORY_MAX = 20; // ~10 min of readings at 30s intervals
|
|
25
28
|
|
|
26
29
|
// Background refresh mode: fetch usage data and write cache, then exit
|
|
27
30
|
if (process.argv.includes('--refresh')) {
|
|
@@ -305,13 +308,33 @@ function getOAuthToken() {
|
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Write error backoff to cache — preserves existing data/history,
|
|
313
|
+
* but delays next refresh attempt by USAGE_ERROR_TTL.
|
|
314
|
+
*/
|
|
315
|
+
function writeErrorBackoff() {
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
try {
|
|
318
|
+
const existing = JSON.parse(readFileSync(USAGE_CACHE_PATH, 'utf-8'));
|
|
319
|
+
existing.nextRefreshAfter = now + USAGE_ERROR_TTL;
|
|
320
|
+
writeFileSync(USAGE_CACHE_PATH, JSON.stringify(existing));
|
|
321
|
+
} catch {
|
|
322
|
+
writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
|
|
323
|
+
timestamp: now,
|
|
324
|
+
nextRefreshAfter: now + USAGE_ERROR_TTL,
|
|
325
|
+
data: null,
|
|
326
|
+
history: [],
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
308
331
|
/**
|
|
309
332
|
* Fetch usage data from API and write to cache (runs in background)
|
|
310
333
|
*/
|
|
311
334
|
function refreshUsageCache() {
|
|
312
335
|
try {
|
|
313
336
|
const token = getOAuthToken();
|
|
314
|
-
if (!token) return;
|
|
337
|
+
if (!token) { writeErrorBackoff(); return; }
|
|
315
338
|
|
|
316
339
|
const result = spawnSync('curl', [
|
|
317
340
|
'-s', '--max-time', '3',
|
|
@@ -321,72 +344,77 @@ function refreshUsageCache() {
|
|
|
321
344
|
'-H', 'Content-Type: application/json',
|
|
322
345
|
], { encoding: 'utf-8', timeout: 5000 });
|
|
323
346
|
|
|
324
|
-
if (result.status
|
|
325
|
-
const data = JSON.parse(result.stdout.trim());
|
|
326
|
-
if (data.five_hour && data.seven_day) {
|
|
327
|
-
// Load existing history and append new reading
|
|
328
|
-
let history = [];
|
|
329
|
-
try {
|
|
330
|
-
const existing = JSON.parse(readFileSync(USAGE_CACHE_PATH, 'utf-8'));
|
|
331
|
-
if (Array.isArray(existing.history)) history = existing.history;
|
|
332
|
-
} catch {}
|
|
333
|
-
|
|
334
|
-
const now = Date.now();
|
|
335
|
-
history.push({
|
|
336
|
-
t: now,
|
|
337
|
-
five_hour: data.five_hour.utilization,
|
|
338
|
-
seven_day: data.seven_day.utilization,
|
|
339
|
-
});
|
|
347
|
+
if (result.status !== 0 || !result.stdout) { writeErrorBackoff(); return; }
|
|
340
348
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
history = history.slice(-USAGE_HISTORY_MAX);
|
|
344
|
-
}
|
|
349
|
+
let responseData;
|
|
350
|
+
try { responseData = JSON.parse(result.stdout.trim()); } catch { writeErrorBackoff(); return; }
|
|
345
351
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
+
if (!responseData.five_hour || !responseData.seven_day) { writeErrorBackoff(); return; }
|
|
353
|
+
|
|
354
|
+
// --- Success path ---
|
|
355
|
+
let history = [];
|
|
356
|
+
try {
|
|
357
|
+
const existing = JSON.parse(readFileSync(USAGE_CACHE_PATH, 'utf-8'));
|
|
358
|
+
if (Array.isArray(existing.history)) history = existing.history;
|
|
359
|
+
} catch {}
|
|
360
|
+
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
history.push({
|
|
363
|
+
t: now,
|
|
364
|
+
five_hour: responseData.five_hour.utilization,
|
|
365
|
+
seven_day: responseData.seven_day.utilization,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (history.length > USAGE_HISTORY_MAX) {
|
|
369
|
+
history = history.slice(-USAGE_HISTORY_MAX);
|
|
352
370
|
}
|
|
371
|
+
|
|
372
|
+
writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
|
|
373
|
+
timestamp: now,
|
|
374
|
+
nextRefreshAfter: now + USAGE_CACHE_TTL,
|
|
375
|
+
data: responseData,
|
|
376
|
+
history,
|
|
377
|
+
}));
|
|
353
378
|
} catch {
|
|
354
|
-
|
|
379
|
+
writeErrorBackoff();
|
|
380
|
+
} finally {
|
|
381
|
+
try { unlinkSync(USAGE_LOCK_PATH); } catch {}
|
|
355
382
|
}
|
|
356
383
|
}
|
|
357
384
|
|
|
358
385
|
/**
|
|
359
|
-
* Read cached usage data, trigger background refresh if
|
|
386
|
+
* Read cached usage data, trigger background refresh if scheduled time has passed.
|
|
387
|
+
* All instances share the same nextRefreshAfter timestamp — only one refresh per cycle.
|
|
360
388
|
*/
|
|
361
389
|
function getUsageData() {
|
|
362
390
|
let cacheData = null;
|
|
363
391
|
let cacheHistory = null;
|
|
364
|
-
let
|
|
392
|
+
let shouldRefresh = true; // refresh if no cache exists
|
|
365
393
|
|
|
366
394
|
try {
|
|
367
395
|
const raw = readFileSync(USAGE_CACHE_PATH, 'utf-8');
|
|
368
396
|
const cache = JSON.parse(raw);
|
|
369
397
|
cacheData = cache.data;
|
|
370
398
|
cacheHistory = cache.history || null;
|
|
371
|
-
|
|
399
|
+
shouldRefresh = Date.now() > (cache.nextRefreshAfter || 0);
|
|
372
400
|
} catch {}
|
|
373
401
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Optimistic lock: claim the timestamp so other sessions see fresh cache
|
|
402
|
+
if (shouldRefresh) {
|
|
403
|
+
let lockHeld = false;
|
|
377
404
|
try {
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
cache.timestamp = Date.now();
|
|
381
|
-
writeFileSync(USAGE_CACHE_PATH, JSON.stringify(cache));
|
|
382
|
-
} catch {}
|
|
383
|
-
try {
|
|
384
|
-
const child = spawn(process.execPath, [__filename, '--refresh'], {
|
|
385
|
-
detached: true,
|
|
386
|
-
stdio: 'ignore',
|
|
387
|
-
});
|
|
388
|
-
child.unref();
|
|
405
|
+
const lockTime = parseInt(readFileSync(USAGE_LOCK_PATH, 'utf-8'), 10);
|
|
406
|
+
lockHeld = (Date.now() - lockTime) < USAGE_LOCK_TTL;
|
|
389
407
|
} catch {}
|
|
408
|
+
if (!lockHeld) {
|
|
409
|
+
try { writeFileSync(USAGE_LOCK_PATH, String(Date.now())); } catch {}
|
|
410
|
+
try {
|
|
411
|
+
const child = spawn(process.execPath, [__filename, '--refresh'], {
|
|
412
|
+
detached: true,
|
|
413
|
+
stdio: 'ignore',
|
|
414
|
+
});
|
|
415
|
+
child.unref();
|
|
416
|
+
} catch {}
|
|
417
|
+
}
|
|
390
418
|
}
|
|
391
419
|
|
|
392
420
|
return { data: cacheData, history: cacheHistory };
|