cc-dev-template 0.1.103 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-dev-template",
3
- "version": "0.1.103",
3
+ "version": "0.1.104",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
@@ -21,9 +21,10 @@ const { homedir } = require('os');
21
21
  // Usage API cache
22
22
  const USAGE_CACHE_PATH = join(homedir(), '.claude', '.usage-cache.json');
23
23
  const USAGE_LOCK_PATH = join(homedir(), '.claude', '.usage-cache.lock');
24
- const USAGE_CACHE_TTL = 120000; // 2 minutes
25
- const USAGE_LOCK_TTL = 30000; // 30s lock to prevent concurrent refresh spawns
26
- const USAGE_HISTORY_MAX = 20; // ~40 min of readings at 2min intervals
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
27
28
 
28
29
  // Background refresh mode: fetch usage data and write cache, then exit
29
30
  if (process.argv.includes('--refresh')) {
@@ -307,13 +308,33 @@ function getOAuthToken() {
307
308
  }
308
309
  }
309
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
+
310
331
  /**
311
332
  * Fetch usage data from API and write to cache (runs in background)
312
333
  */
313
334
  function refreshUsageCache() {
314
335
  try {
315
336
  const token = getOAuthToken();
316
- if (!token) return;
337
+ if (!token) { writeErrorBackoff(); return; }
317
338
 
318
339
  const result = spawnSync('curl', [
319
340
  '-s', '--max-time', '3',
@@ -323,59 +344,62 @@ function refreshUsageCache() {
323
344
  '-H', 'Content-Type: application/json',
324
345
  ], { encoding: 'utf-8', timeout: 5000 });
325
346
 
326
- if (result.status === 0 && result.stdout) {
327
- const data = JSON.parse(result.stdout.trim());
328
- if (data.five_hour && data.seven_day) {
329
- // Load existing history and append new reading
330
- let history = [];
331
- try {
332
- const existing = JSON.parse(readFileSync(USAGE_CACHE_PATH, 'utf-8'));
333
- if (Array.isArray(existing.history)) history = existing.history;
334
- } catch {}
335
-
336
- const now = Date.now();
337
- history.push({
338
- t: now,
339
- five_hour: data.five_hour.utilization,
340
- seven_day: data.seven_day.utilization,
341
- });
347
+ if (result.status !== 0 || !result.stdout) { writeErrorBackoff(); return; }
342
348
 
343
- // Keep only the last N readings
344
- if (history.length > USAGE_HISTORY_MAX) {
345
- history = history.slice(-USAGE_HISTORY_MAX);
346
- }
349
+ let responseData;
350
+ try { responseData = JSON.parse(result.stdout.trim()); } catch { writeErrorBackoff(); return; }
347
351
 
348
- writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
349
- timestamp: now,
350
- data,
351
- history,
352
- }));
353
- try { unlinkSync(USAGE_LOCK_PATH); } catch {}
354
- }
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);
355
370
  }
371
+
372
+ writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
373
+ timestamp: now,
374
+ nextRefreshAfter: now + USAGE_CACHE_TTL,
375
+ data: responseData,
376
+ history,
377
+ }));
356
378
  } catch {
357
- // Silently fail - stale cache will be used on next render
379
+ writeErrorBackoff();
380
+ } finally {
381
+ try { unlinkSync(USAGE_LOCK_PATH); } catch {}
358
382
  }
359
383
  }
360
384
 
361
385
  /**
362
- * Read cached usage data, trigger background refresh if stale
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.
363
388
  */
364
389
  function getUsageData() {
365
390
  let cacheData = null;
366
391
  let cacheHistory = null;
367
- let cacheAge = Infinity;
392
+ let shouldRefresh = true; // refresh if no cache exists
368
393
 
369
394
  try {
370
395
  const raw = readFileSync(USAGE_CACHE_PATH, 'utf-8');
371
396
  const cache = JSON.parse(raw);
372
397
  cacheData = cache.data;
373
398
  cacheHistory = cache.history || null;
374
- cacheAge = Date.now() - cache.timestamp;
399
+ shouldRefresh = Date.now() > (cache.nextRefreshAfter || 0);
375
400
  } catch {}
376
401
 
377
- // Trigger background refresh if cache is stale and no other refresh is running
378
- if (cacheAge > USAGE_CACHE_TTL) {
402
+ if (shouldRefresh) {
379
403
  let lockHeld = false;
380
404
  try {
381
405
  const lockTime = parseInt(readFileSync(USAGE_LOCK_PATH, 'utf-8'), 10);