token-usage-sync 1.2.0 → 1.3.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/index.js CHANGED
@@ -145,6 +145,109 @@ function calculateUsageForPeriod(entries, hoursAgo) {
145
145
  };
146
146
  }
147
147
 
148
+ // セッションブロックを特定する
149
+ // Claude Codeは5時間ブロック方式: セッション開始から5時間でリセット
150
+ function identifySessionBlocks(entries, sessionDurationHours = 5) {
151
+ const sorted = entries
152
+ .filter(e => e.timestamp || e.message?.timestamp)
153
+ .map(e => ({
154
+ ...e,
155
+ _ts: new Date(e.timestamp || e.message?.timestamp).getTime()
156
+ }))
157
+ .sort((a, b) => a._ts - b._ts);
158
+
159
+ const blocks = [];
160
+ let currentBlock = null;
161
+
162
+ for (const entry of sorted) {
163
+ const entryTime = entry._ts;
164
+
165
+ if (!currentBlock) {
166
+ currentBlock = {
167
+ startTime: entryTime,
168
+ endTime: entryTime + sessionDurationHours * 60 * 60 * 1000,
169
+ entries: [entry]
170
+ };
171
+ } else if (entryTime <= currentBlock.endTime) {
172
+ currentBlock.entries.push(entry);
173
+ } else {
174
+ blocks.push(currentBlock);
175
+ currentBlock = {
176
+ startTime: entryTime,
177
+ endTime: entryTime + sessionDurationHours * 60 * 60 * 1000,
178
+ entries: [entry]
179
+ };
180
+ }
181
+ }
182
+
183
+ if (currentBlock) {
184
+ blocks.push(currentBlock);
185
+ }
186
+
187
+ return blocks;
188
+ }
189
+
190
+ // 現在のアクティブブロックの使用量を計算
191
+ function calculateActiveBlockUsage(entries, now = new Date()) {
192
+ const blocks = identifySessionBlocks(entries, 5);
193
+ const nowTime = now.getTime();
194
+
195
+ const activeBlock = blocks.find(b => nowTime <= b.endTime && nowTime >= b.startTime);
196
+
197
+ if (!activeBlock) {
198
+ return {
199
+ inputTokens: 0,
200
+ outputTokens: 0,
201
+ cacheCreationTokens: 0,
202
+ cacheReadTokens: 0,
203
+ totalTokens: 0,
204
+ weightedTokens: 0,
205
+ blockStartTime: null,
206
+ blockEndTime: null,
207
+ elapsedMinutes: 0,
208
+ remainingMinutes: 0,
209
+ };
210
+ }
211
+
212
+ let inputTokens = 0;
213
+ let outputTokens = 0;
214
+ let cacheCreationTokens = 0;
215
+ let cacheReadTokens = 0;
216
+
217
+ for (const entry of activeBlock.entries) {
218
+ const usage = entry.message?.usage || entry.usage;
219
+ if (!usage) continue;
220
+
221
+ inputTokens += usage.input_tokens || 0;
222
+ outputTokens += usage.output_tokens || 0;
223
+ cacheCreationTokens += usage.cache_creation_input_tokens || 0;
224
+ cacheReadTokens += usage.cache_read_input_tokens || 0;
225
+ }
226
+
227
+ const weightedTokens = Math.round(
228
+ inputTokens * 1.0 +
229
+ outputTokens * 1.0 +
230
+ cacheCreationTokens * 1.0 +
231
+ cacheReadTokens * 0
232
+ );
233
+
234
+ const elapsedMinutes = Math.round((nowTime - activeBlock.startTime) / 60000);
235
+ const remainingMinutes = Math.round((activeBlock.endTime - nowTime) / 60000);
236
+
237
+ return {
238
+ inputTokens,
239
+ outputTokens,
240
+ cacheCreationTokens,
241
+ cacheReadTokens,
242
+ totalTokens: inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens,
243
+ weightedTokens,
244
+ blockStartTime: new Date(activeBlock.startTime),
245
+ blockEndTime: new Date(activeBlock.endTime),
246
+ elapsedMinutes,
247
+ remainingMinutes,
248
+ };
249
+ }
250
+
148
251
  // 使用量を集計
149
252
  function aggregateUsage(entries) {
150
253
  let totalInputTokens = 0;
@@ -198,6 +301,9 @@ function aggregateUsage(entries) {
198
301
  }
199
302
  }
200
303
 
304
+ // セッションブロック方式の使用量を計算(Claude Code準拠)
305
+ const activeBlock = calculateActiveBlockUsage(entries);
306
+
201
307
  // ローリングウィンドウの使用量を計算
202
308
  const last5Hours = calculateUsageForPeriod(entries, 5);
203
309
  const last24Hours = calculateUsageForPeriod(entries, 24);
@@ -212,7 +318,9 @@ function aggregateUsage(entries) {
212
318
  cacheReadInputTokens: totalCacheReadInputTokens,
213
319
  messageCount,
214
320
  },
215
- // Claude Code制限と同じローリングウィンドウ
321
+ // セッションブロック(Claude Code準拠)
322
+ activeBlock,
323
+ // ローリングウィンドウ
216
324
  limits: {
217
325
  last5Hours,
218
326
  last24Hours,
@@ -309,24 +417,29 @@ async function main() {
309
417
  console.log('\n📊 Claude Code Usage Summary\n');
310
418
 
311
419
  // 制限値
312
- // 5時間: 重み付けトークン(cache_read=0x, cache_create=1x)
313
- // 週間: 生トークン
314
- const LIMIT_5H_WEIGHTED = 4_100_000; // 5時間制限: 4.1M weighted tokens
315
420
  const LIMIT_WEEK_RAW = 195_000_000; // 週間制限: 195M raw tokens
316
421
 
317
- const pct5h = Math.round((usage.limits.last5Hours.weightedTokens / LIMIT_5H_WEIGHTED) * 100);
318
422
  const pctWeek = Math.round((usage.limits.lastWeek.totalTokens / LIMIT_WEEK_RAW) * 100);
319
423
 
320
- // ローリングウィンドウ(Claude Code制限と同じ形式)
321
- console.log('=== Usage Limits ===');
322
- console.log(`5h Session: ${usage.limits.last5Hours.weightedTokens.toLocaleString()} weighted / ${LIMIT_5H_WEIGHTED.toLocaleString()} (${pct5h}%)`);
323
- console.log(`Weekly: ${usage.limits.lastWeek.totalTokens.toLocaleString()} raw / ${LIMIT_WEEK_RAW.toLocaleString()} (${pctWeek}%)`);
424
+ // アクティブブロック(Claude Code準拠のセッションブロック方式)
425
+ console.log('=== Active Session Block ===');
426
+ if (usage.activeBlock && usage.activeBlock.blockStartTime) {
427
+ const elapsed = usage.activeBlock.elapsedMinutes;
428
+ const remaining = usage.activeBlock.remainingMinutes;
429
+ const elapsedStr = `${Math.floor(elapsed / 60)}h ${elapsed % 60}m`;
430
+ const remainingStr = `${Math.floor(remaining / 60)}h ${remaining % 60}m`;
431
+
432
+ console.log(`Status: ACTIVE (${elapsedStr} elapsed, ${remainingStr} remaining)`);
433
+ console.log(`Tokens: ${usage.activeBlock.totalTokens.toLocaleString()}`);
434
+ console.log(` Input: ${usage.activeBlock.inputTokens.toLocaleString()}`);
435
+ console.log(` Output: ${usage.activeBlock.outputTokens.toLocaleString()}`);
436
+ console.log(` Cache: ${usage.activeBlock.cacheCreationTokens.toLocaleString()} (create) + ${usage.activeBlock.cacheReadTokens.toLocaleString()} (read)`);
437
+ } else {
438
+ console.log('Status: No active block');
439
+ }
324
440
 
325
- console.log('\n=== Last 5 Hours Breakdown ===');
326
- console.log(` Input: ${usage.limits.last5Hours.inputTokens.toLocaleString()} × 1.0`);
327
- console.log(` Output: ${usage.limits.last5Hours.outputTokens.toLocaleString()} × 1.0`);
328
- console.log(` Cache Create: ${usage.limits.last5Hours.cacheCreationTokens.toLocaleString()} × 1.0`);
329
- console.log(` Cache Read: ${usage.limits.last5Hours.cacheReadTokens.toLocaleString()} × 0 (free)`);
441
+ console.log('\n=== Weekly Limit ===');
442
+ console.log(`Weekly: ${usage.limits.lastWeek.totalTokens.toLocaleString()} / ${LIMIT_WEEK_RAW.toLocaleString()} (${pctWeek}%)`);
330
443
 
331
444
  console.log('\n=== All Time Total ===');
332
445
  console.log(`Total Tokens: ${usage.summary.totalTokens.toLocaleString()}`);
@@ -1,4 +1,114 @@
1
- // 期間内の使用量を計算
1
+ // セッションブロックを特定する
2
+ // Claude Codeは5時間ブロック方式: セッション開始から5時間でリセット
3
+ export function identifySessionBlocks(entries, sessionDurationHours = 5) {
4
+ // タイムスタンプでソート
5
+ const sorted = entries
6
+ .filter(e => e.timestamp || e.message?.timestamp)
7
+ .map(e => ({
8
+ ...e,
9
+ _ts: new Date(e.timestamp || e.message?.timestamp).getTime()
10
+ }))
11
+ .sort((a, b) => a._ts - b._ts);
12
+
13
+ const blocks = [];
14
+ let currentBlock = null;
15
+
16
+ for (const entry of sorted) {
17
+ const entryTime = entry._ts;
18
+
19
+ if (!currentBlock) {
20
+ // 最初のブロック開始
21
+ currentBlock = {
22
+ startTime: entryTime,
23
+ endTime: entryTime + sessionDurationHours * 60 * 60 * 1000,
24
+ entries: [entry]
25
+ };
26
+ } else if (entryTime <= currentBlock.endTime) {
27
+ // 現在のブロック内
28
+ currentBlock.entries.push(entry);
29
+ } else {
30
+ // 新しいブロック開始
31
+ blocks.push(currentBlock);
32
+ currentBlock = {
33
+ startTime: entryTime,
34
+ endTime: entryTime + sessionDurationHours * 60 * 60 * 1000,
35
+ entries: [entry]
36
+ };
37
+ }
38
+ }
39
+
40
+ if (currentBlock) {
41
+ blocks.push(currentBlock);
42
+ }
43
+
44
+ return blocks;
45
+ }
46
+
47
+ // 現在のアクティブブロックの使用量を計算
48
+ export function calculateActiveBlockUsage(entries, now = new Date()) {
49
+ const blocks = identifySessionBlocks(entries, 5);
50
+ const nowTime = now.getTime();
51
+
52
+ // アクティブブロックを探す(現在時刻がブロックの終了時刻前)
53
+ const activeBlock = blocks.find(b => nowTime <= b.endTime && nowTime >= b.startTime);
54
+
55
+ if (!activeBlock) {
56
+ // アクティブブロックがない場合は0を返す
57
+ return {
58
+ inputTokens: 0,
59
+ outputTokens: 0,
60
+ cacheCreationTokens: 0,
61
+ cacheReadTokens: 0,
62
+ totalTokens: 0,
63
+ weightedTokens: 0,
64
+ blockStartTime: null,
65
+ blockEndTime: null,
66
+ elapsedMinutes: 0,
67
+ remainingMinutes: 0,
68
+ };
69
+ }
70
+
71
+ let inputTokens = 0;
72
+ let outputTokens = 0;
73
+ let cacheCreationTokens = 0;
74
+ let cacheReadTokens = 0;
75
+
76
+ for (const entry of activeBlock.entries) {
77
+ const usage = entry.message?.usage || entry.usage;
78
+ if (!usage) continue;
79
+
80
+ inputTokens += usage.input_tokens || 0;
81
+ outputTokens += usage.output_tokens || 0;
82
+ cacheCreationTokens += usage.cache_creation_input_tokens || 0;
83
+ cacheReadTokens += usage.cache_read_input_tokens || 0;
84
+ }
85
+
86
+ // 重み付けトークン計算(Claude Code実測ベース)
87
+ const weightedTokens = Math.round(
88
+ inputTokens * 1.0 +
89
+ outputTokens * 1.0 +
90
+ cacheCreationTokens * 1.0 +
91
+ cacheReadTokens * 0
92
+ );
93
+
94
+ const elapsedMinutes = Math.round((nowTime - activeBlock.startTime) / 60000);
95
+ const remainingMinutes = Math.round((activeBlock.endTime - nowTime) / 60000);
96
+
97
+ return {
98
+ inputTokens,
99
+ outputTokens,
100
+ cacheCreationTokens,
101
+ cacheReadTokens,
102
+ totalTokens: inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens,
103
+ weightedTokens,
104
+ blockStartTime: new Date(activeBlock.startTime),
105
+ blockEndTime: new Date(activeBlock.endTime),
106
+ elapsedMinutes,
107
+ remainingMinutes,
108
+ };
109
+ }
110
+
111
+ // 期間内の使用量を計算(後方互換性のため残す)
2
112
  export function calculateUsageForPeriod(entries, hoursAgo, now = new Date()) {
3
113
  const cutoff = new Date(now.getTime() - hoursAgo * 60 * 60 * 1000);
4
114
 
@@ -94,7 +204,10 @@ export function aggregateUsage(entries, now = new Date()) {
94
204
  }
95
205
  }
96
206
 
97
- // ローリングウィンドウの使用量を計算
207
+ // セッションブロック方式の使用量を計算(Claude Code準拠)
208
+ const activeBlock = calculateActiveBlockUsage(entries, now);
209
+
210
+ // ローリングウィンドウの使用量を計算(後方互換性)
98
211
  const last5Hours = calculateUsageForPeriod(entries, 5, now);
99
212
  const last24Hours = calculateUsageForPeriod(entries, 24, now);
100
213
  const lastWeek = calculateUsageForPeriod(entries, 24 * 7, now);
@@ -108,6 +221,9 @@ export function aggregateUsage(entries, now = new Date()) {
108
221
  cacheReadInputTokens: totalCacheReadInputTokens,
109
222
  messageCount,
110
223
  },
224
+ // セッションブロック(Claude Code準拠)
225
+ activeBlock,
226
+ // ローリングウィンドウ(後方互換性)
111
227
  limits: {
112
228
  last5Hours,
113
229
  last24Hours,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-usage-sync",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Sync Claude Code token usage to your self-hosted dashboard",
5
5
  "type": "module",
6
6
  "bin": {