@vibe-cafe/vibe-usage 0.1.3 → 0.1.5
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/api.js +5 -0
- package/src/init.js +3 -2
- package/src/parsers/claude-code.js +1 -1
- package/src/parsers/codex.js +22 -2
- package/src/sync.js +10 -3
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -18,6 +18,7 @@ export function ingest(apiUrl, apiKey, buckets) {
|
|
|
18
18
|
|
|
19
19
|
const req = mod.request(url, {
|
|
20
20
|
method: 'POST',
|
|
21
|
+
timeout: 60_000,
|
|
21
22
|
headers: {
|
|
22
23
|
'Content-Type': 'application/json',
|
|
23
24
|
'Authorization': `Bearer ${apiKey}`,
|
|
@@ -44,6 +45,10 @@ export function ingest(apiUrl, apiKey, buckets) {
|
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
req.on('error', (err) => reject(err));
|
|
48
|
+
req.on('timeout', () => {
|
|
49
|
+
req.destroy();
|
|
50
|
+
reject(new Error('Request timed out (60s)'));
|
|
51
|
+
});
|
|
47
52
|
req.write(body);
|
|
48
53
|
req.end();
|
|
49
54
|
});
|
package/src/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
|
-
import {
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
3
|
import { platform } from 'node:os';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
@@ -20,7 +20,8 @@ function prompt(question) {
|
|
|
20
20
|
function openBrowser(url) {
|
|
21
21
|
const cmds = { darwin: 'open', linux: 'xdg-open', win32: 'start' };
|
|
22
22
|
const cmd = cmds[platform()] || cmds.linux;
|
|
23
|
-
|
|
23
|
+
// Use execFile with args array to avoid shell injection via VIBE_USAGE_API_URL
|
|
24
|
+
execFile(cmd, [url], () => {});
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export async function runInit() {
|
|
@@ -14,7 +14,7 @@ export async function parse(lastSync) {
|
|
|
14
14
|
const entries = [];
|
|
15
15
|
|
|
16
16
|
for (const session of sessions) {
|
|
17
|
-
if (lastSync && session.lastActivity <= lastSync) continue;
|
|
17
|
+
if (lastSync && new Date(session.lastActivity) <= new Date(lastSync)) continue;
|
|
18
18
|
|
|
19
19
|
for (const breakdown of session.modelBreakdowns || []) {
|
|
20
20
|
entries.push({
|
package/src/parsers/codex.js
CHANGED
|
@@ -36,6 +36,9 @@ export async function parse(lastSync) {
|
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Track previous cumulative totals per model to compute deltas when only total_token_usage is available
|
|
40
|
+
const prevTotal = new Map();
|
|
41
|
+
|
|
39
42
|
for (const line of content.split('\n')) {
|
|
40
43
|
if (!line.trim()) continue;
|
|
41
44
|
try {
|
|
@@ -54,8 +57,25 @@ export async function parse(lastSync) {
|
|
|
54
57
|
if (!timestamp || isNaN(timestamp.getTime())) continue;
|
|
55
58
|
if (lastSync && timestamp <= new Date(lastSync)) continue;
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
// Prefer incremental per-request usage; compute delta from cumulative total as fallback
|
|
61
|
+
let usage = info.last_token_usage;
|
|
62
|
+
if (!usage && info.total_token_usage) {
|
|
63
|
+
const totalKey = `${info.model || payload.model || ''}`;
|
|
64
|
+
const prev = prevTotal.get(totalKey);
|
|
65
|
+
const curr = info.total_token_usage;
|
|
66
|
+
if (prev) {
|
|
67
|
+
usage = {
|
|
68
|
+
input_tokens: (curr.input_tokens || 0) - (prev.input_tokens || 0),
|
|
69
|
+
output_tokens: (curr.output_tokens || 0) - (prev.output_tokens || 0),
|
|
70
|
+
cached_input_tokens: (curr.cached_input_tokens || 0) - (prev.cached_input_tokens || 0),
|
|
71
|
+
reasoning_output_tokens: (curr.reasoning_output_tokens || 0) - (prev.reasoning_output_tokens || 0),
|
|
72
|
+
};
|
|
73
|
+
} else {
|
|
74
|
+
// First cumulative entry — use as-is (it's the first event's total)
|
|
75
|
+
usage = curr;
|
|
76
|
+
}
|
|
77
|
+
prevTotal.set(totalKey, { ...curr });
|
|
78
|
+
}
|
|
59
79
|
if (!usage) continue;
|
|
60
80
|
|
|
61
81
|
const model = info.model || payload.model || 'unknown';
|
package/src/sync.js
CHANGED
|
@@ -39,13 +39,15 @@ export async function runSync() {
|
|
|
39
39
|
const result = await ingest(apiUrl, config.apiKey, batch);
|
|
40
40
|
totalIngested += result.ingested ?? batch.length;
|
|
41
41
|
|
|
42
|
+
// Save progress after each successful batch so partial uploads survive interruptions
|
|
43
|
+
config.lastSync = new Date().toISOString();
|
|
44
|
+
saveConfig(config);
|
|
45
|
+
|
|
42
46
|
if (allBuckets.length > BATCH_SIZE) {
|
|
43
47
|
process.stdout.write(` ${Math.min(i + BATCH_SIZE, allBuckets.length)}/${allBuckets.length} buckets...\r`);
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
config.lastSync = new Date().toISOString();
|
|
48
|
-
saveConfig(config);
|
|
49
51
|
console.log(`Synced ${totalIngested} buckets.`);
|
|
50
52
|
return totalIngested;
|
|
51
53
|
} catch (err) {
|
|
@@ -53,7 +55,12 @@ export async function runSync() {
|
|
|
53
55
|
console.error('Invalid API key. Run `npx @vibe-cafe/vibe-usage init` to reconfigure.');
|
|
54
56
|
process.exit(1);
|
|
55
57
|
}
|
|
56
|
-
|
|
58
|
+
// Progress already saved per-batch — report partial success
|
|
59
|
+
if (totalIngested > 0) {
|
|
60
|
+
console.error(`Sync partially completed (${totalIngested} buckets uploaded). ${err.message}`);
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`Sync failed: ${err.message}`);
|
|
63
|
+
}
|
|
57
64
|
process.exit(1);
|
|
58
65
|
}
|
|
59
66
|
}
|