token-usage-sync 1.2.0 → 1.4.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 +127 -14
- package/lib/calculations.js +118 -2
- package/package.json +1 -1
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
|
-
//
|
|
321
|
-
console.log('===
|
|
322
|
-
|
|
323
|
-
|
|
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===
|
|
326
|
-
console.log(`
|
|
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()}`);
|
package/lib/calculations.js
CHANGED
|
@@ -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,
|