cc-dev-template 0.1.72 → 0.1.73

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.72",
3
+ "version": "0.1.73",
4
4
  "description": "Structured AI-assisted development framework for Claude Code",
5
5
  "bin": {
6
6
  "cc-dev-template": "./bin/install.js"
@@ -21,6 +21,7 @@ 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_CACHE_TTL = 45000; // 45 seconds
24
+ const USAGE_HISTORY_MAX = 20; // ~15 min of readings at 45s intervals
24
25
 
25
26
  // Background refresh mode: fetch usage data and write cache, then exit
26
27
  if (process.argv.includes('--refresh')) {
@@ -72,6 +73,50 @@ function getContextGreyscale(percentage) {
72
73
  return '\x1b[38;5;240m'; // Dark grey - safe
73
74
  }
74
75
 
76
+ /**
77
+ * Compute burn rate (percent per minute) from usage history
78
+ * Returns null if insufficient data
79
+ */
80
+ function getUsageBurnRate(history, key) {
81
+ if (!history || history.length < 2) return null;
82
+
83
+ const oldest = history[0];
84
+ const newest = history[history.length - 1];
85
+ const minutesElapsed = (newest.t - oldest.t) / 60000;
86
+
87
+ // Need at least 2 minutes of data for a stable reading
88
+ if (minutesElapsed < 2) return null;
89
+
90
+ const deltaUtilization = newest[key] - oldest[key];
91
+ return deltaUtilization / minutesElapsed;
92
+ }
93
+
94
+ /**
95
+ * Get color for usage bar based on current level and burn rate trend
96
+ * Returns red/yellow for danger, or greyscale for safe
97
+ */
98
+ function getUsageColor(utilization, burnRate, windowType) {
99
+ const RED = '\x1b[38;5;196m';
100
+ const YELLOW = '\x1b[38;5;220m';
101
+
102
+ // Thresholds differ by window type
103
+ const redMinutes = windowType === '5h' ? 30 : 240; // 30min / 4hr
104
+ const yellowMinutes = windowType === '5h' ? 90 : 720; // 90min / 12hr
105
+
106
+ // Hard thresholds on current utilization
107
+ if (utilization >= 90) return RED;
108
+ if (utilization >= 75) return YELLOW;
109
+
110
+ // Trend-based: project time to hit 100%
111
+ if (burnRate && burnRate > 0) {
112
+ const minutesToLimit = (100 - utilization) / burnRate;
113
+ if (minutesToLimit <= redMinutes) return RED;
114
+ if (minutesToLimit <= yellowMinutes) return YELLOW;
115
+ }
116
+
117
+ return getContextGreyscale(utilization);
118
+ }
119
+
75
120
  /**
76
121
  * Count files in a directory recursively
77
122
  */
@@ -279,9 +324,29 @@ function refreshUsageCache() {
279
324
  if (result.status === 0 && result.stdout) {
280
325
  const data = JSON.parse(result.stdout.trim());
281
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
+ });
340
+
341
+ // Keep only the last N readings
342
+ if (history.length > USAGE_HISTORY_MAX) {
343
+ history = history.slice(-USAGE_HISTORY_MAX);
344
+ }
345
+
282
346
  writeFileSync(USAGE_CACHE_PATH, JSON.stringify({
283
- timestamp: Date.now(),
347
+ timestamp: now,
284
348
  data,
349
+ history,
285
350
  }));
286
351
  }
287
352
  }
@@ -295,12 +360,14 @@ function refreshUsageCache() {
295
360
  */
296
361
  function getUsageData() {
297
362
  let cacheData = null;
363
+ let cacheHistory = null;
298
364
  let cacheAge = Infinity;
299
365
 
300
366
  try {
301
367
  const raw = readFileSync(USAGE_CACHE_PATH, 'utf-8');
302
368
  const cache = JSON.parse(raw);
303
369
  cacheData = cache.data;
370
+ cacheHistory = cache.history || null;
304
371
  cacheAge = Date.now() - cache.timestamp;
305
372
  } catch {}
306
373
 
@@ -315,7 +382,7 @@ function getUsageData() {
315
382
  } catch {}
316
383
  }
317
384
 
318
- return cacheData;
385
+ return { data: cacheData, history: cacheHistory };
319
386
  }
320
387
 
321
388
  /**
@@ -474,14 +541,19 @@ function main() {
474
541
 
475
542
  // Usage limits line (5-hour session + 7-day weekly)
476
543
  const usageLines = [];
477
- const usageData = getUsageData();
478
- if (usageData && usageData.five_hour && usageData.seven_day) {
479
- const pct5h = Math.round(usageData.five_hour.utilization);
480
- const pct7d = Math.round(usageData.seven_day.utilization);
544
+ const { data: usageApiData, history: usageHistory } = getUsageData();
545
+ if (usageApiData && usageApiData.five_hour && usageApiData.seven_day) {
546
+ const pct5h = Math.round(usageApiData.five_hour.utilization);
547
+ const pct7d = Math.round(usageApiData.seven_day.utilization);
481
548
  const bar5h = generateBar(pct5h, 12);
482
549
  const bar7d = generateBar(pct7d, 12);
483
- const color5h = getContextGreyscale(pct5h);
484
- const color7d = getContextGreyscale(pct7d);
550
+
551
+ // Compute burn rates from history for trend-based coloring
552
+ const rate5h = getUsageBurnRate(usageHistory, 'five_hour');
553
+ const rate7d = getUsageBurnRate(usageHistory, 'seven_day');
554
+ const color5h = getUsageColor(pct5h, rate5h, '5h');
555
+ const color7d = getUsageColor(pct7d, rate7d, '7d');
556
+
485
557
  const str5h = pct5h.toString().padStart(3, ' ');
486
558
  const str7d = pct7d.toString().padStart(3, ' ');
487
559
  const usageDisplay = `5HR: ${color5h}[${bar5h}]${str5h}%${DIM_GREY} 7D: ${color7d}[${bar7d}]${str7d}%${DIM_GREY}`;