@vibe-cafe/vibe-usage 0.2.1 → 0.2.3
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 +28 -7
- package/src/hooks.js +22 -12
- package/src/parsers/codex.js +12 -2
- package/src/sync.js +15 -8
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -12,13 +12,14 @@ const INITIAL_DELAY = 1000;
|
|
|
12
12
|
* @param {string} apiUrl - Base URL (e.g. "https://vibecafe.ai")
|
|
13
13
|
* @param {string} apiKey - Bearer token (vbu_xxx)
|
|
14
14
|
* @param {Array} buckets - Array of usage bucket objects
|
|
15
|
+
* @param {{onProgress?: (sent: number, total: number) => void}} [opts]
|
|
15
16
|
* @returns {Promise<{ingested: number}>}
|
|
16
17
|
*/
|
|
17
|
-
export async function ingest(apiUrl, apiKey, buckets) {
|
|
18
|
+
export async function ingest(apiUrl, apiKey, buckets, opts) {
|
|
18
19
|
let lastError;
|
|
19
20
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
20
21
|
try {
|
|
21
|
-
return await _send(apiUrl, apiKey, buckets);
|
|
22
|
+
return await _send(apiUrl, apiKey, buckets, opts?.onProgress);
|
|
22
23
|
} catch (err) {
|
|
23
24
|
lastError = err;
|
|
24
25
|
// Don't retry auth errors or client errors
|
|
@@ -34,10 +35,11 @@ export async function ingest(apiUrl, apiKey, buckets) {
|
|
|
34
35
|
throw lastError;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
function _send(apiUrl, apiKey, buckets) {
|
|
38
|
+
function _send(apiUrl, apiKey, buckets, onProgress) {
|
|
38
39
|
return new Promise((resolve, reject) => {
|
|
39
40
|
const url = new URL('/api/usage/ingest', apiUrl);
|
|
40
|
-
const body = JSON.stringify({ buckets });
|
|
41
|
+
const body = Buffer.from(JSON.stringify({ buckets }));
|
|
42
|
+
const totalBytes = body.length;
|
|
41
43
|
const mod = url.protocol === 'https:' ? https : http;
|
|
42
44
|
|
|
43
45
|
const req = mod.request(url, {
|
|
@@ -46,7 +48,7 @@ function _send(apiUrl, apiKey, buckets) {
|
|
|
46
48
|
headers: {
|
|
47
49
|
'Content-Type': 'application/json',
|
|
48
50
|
'Authorization': `Bearer ${apiKey}`,
|
|
49
|
-
'Content-Length':
|
|
51
|
+
'Content-Length': totalBytes,
|
|
50
52
|
},
|
|
51
53
|
}, (res) => {
|
|
52
54
|
let data = '';
|
|
@@ -75,7 +77,26 @@ function _send(apiUrl, apiKey, buckets) {
|
|
|
75
77
|
req.destroy();
|
|
76
78
|
reject(new Error('Request timed out (60s)'));
|
|
77
79
|
});
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
|
|
81
|
+
// Write body in chunks to report upload progress
|
|
82
|
+
const CHUNK = 16 * 1024;
|
|
83
|
+
let sent = 0;
|
|
84
|
+
|
|
85
|
+
function writeNext() {
|
|
86
|
+
let ok = true;
|
|
87
|
+
while (ok && sent < totalBytes) {
|
|
88
|
+
const slice = body.subarray(sent, sent + CHUNK);
|
|
89
|
+
sent += slice.length;
|
|
90
|
+
if (onProgress) onProgress(sent, totalBytes);
|
|
91
|
+
ok = req.write(slice);
|
|
92
|
+
}
|
|
93
|
+
if (sent < totalBytes) {
|
|
94
|
+
req.once('drain', writeNext);
|
|
95
|
+
} else {
|
|
96
|
+
req.end();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
writeNext();
|
|
80
101
|
});
|
|
81
102
|
}
|
package/src/hooks.js
CHANGED
|
@@ -85,26 +85,36 @@ export function injectCodex() {
|
|
|
85
85
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
const notifyLine = `notify = "sh -c \\"${SYNC_CMD}\\""`;
|
|
89
|
+
|
|
88
90
|
if (content.includes('vibe-usage')) {
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
// Migrate broken [[notify]] / [notify] table format and array format from previous versions
|
|
92
|
+
// to correct string format: notify = "sh -c \"...\""
|
|
93
|
+
content = content.replace(
|
|
94
|
+
/^\[\[?notify\]\]?\n(?:command\s*=\s*["'][^"']*["']\n?)?/gm,
|
|
95
|
+
notifyLine + '\n',
|
|
96
|
+
);
|
|
97
|
+
// Migrate array format: notify = ["sh", "-c", "..."]
|
|
98
|
+
content = content.replace(
|
|
99
|
+
/^notify\s*=\s*\[.*vibe-usage.*\]$/gm,
|
|
100
|
+
notifyLine,
|
|
101
|
+
);
|
|
102
|
+
// Update existing string format notify = "..." to use latest command
|
|
92
103
|
content = content.replace(
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
/^notify\s*=\s*".*vibe-usage.*"$/gm,
|
|
105
|
+
notifyLine,
|
|
95
106
|
);
|
|
96
107
|
writeFileSync(configPath, content, 'utf-8');
|
|
97
108
|
return { injected: false, reason: 'already installed (updated)' };
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
content = content.slice(0, notifyIdx) + `[[notify]]\ncommand = "${SYNC_CMD}"` + content.slice(sectionEnd);
|
|
111
|
+
// Check if any notify line already exists
|
|
112
|
+
const hasNotify = /^notify\s*=/m.test(content);
|
|
113
|
+
if (hasNotify) {
|
|
114
|
+
// Replace existing notify value
|
|
115
|
+
content = content.replace(/^notify\s*=\s*.+$/gm, notifyLine);
|
|
106
116
|
} else {
|
|
107
|
-
content +=
|
|
117
|
+
content += `\n${notifyLine}\n`;
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
writeFileSync(configPath, content, 'utf-8');
|
package/src/parsers/codex.js
CHANGED
|
@@ -72,6 +72,8 @@ export async function parse(lastSync) {
|
|
|
72
72
|
} catch { break; }
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Track model from turn_context events (fallback when token_count lacks model)
|
|
76
|
+
let turnContextModel = 'unknown';
|
|
75
77
|
// Track previous cumulative totals per model to compute deltas when only total_token_usage is available
|
|
76
78
|
const prevTotal = new Map();
|
|
77
79
|
for (const line of content.split('\n')) {
|
|
@@ -83,7 +85,15 @@ export async function parse(lastSync) {
|
|
|
83
85
|
if (obj.type !== 'event_msg') continue;
|
|
84
86
|
|
|
85
87
|
const payload = obj.payload;
|
|
86
|
-
if (!payload
|
|
88
|
+
if (!payload) continue;
|
|
89
|
+
|
|
90
|
+
// Capture model from turn_context events
|
|
91
|
+
if (payload.type === 'turn_context' && payload.model) {
|
|
92
|
+
turnContextModel = payload.model;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (payload.type !== 'token_count') continue;
|
|
87
97
|
|
|
88
98
|
const info = payload.info;
|
|
89
99
|
if (!info) continue;
|
|
@@ -113,7 +123,7 @@ export async function parse(lastSync) {
|
|
|
113
123
|
}
|
|
114
124
|
if (!usage) continue;
|
|
115
125
|
|
|
116
|
-
const model = info.model || payload.model || sessionModel;
|
|
126
|
+
const model = info.model || payload.model || turnContextModel || sessionModel;
|
|
117
127
|
|
|
118
128
|
entries.push({
|
|
119
129
|
source: 'codex',
|
package/src/sync.js
CHANGED
|
@@ -9,6 +9,12 @@ import { TOOLS } from './hooks.js';
|
|
|
9
9
|
|
|
10
10
|
const BATCH_SIZE = 500;
|
|
11
11
|
|
|
12
|
+
function formatBytes(bytes) {
|
|
13
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
14
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
15
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
export async function runSync() {
|
|
13
19
|
// Self-heal: re-inject any missing hooks before syncing
|
|
14
20
|
ensureHooks();
|
|
@@ -54,13 +60,14 @@ export async function runSync() {
|
|
|
54
60
|
for (let i = 0; i < allBuckets.length; i += BATCH_SIZE) {
|
|
55
61
|
const batch = allBuckets.slice(i, i + BATCH_SIZE);
|
|
56
62
|
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const prefix = totalBatches > 1 ? ` [${batchNum}/${totalBatches}] ` : ' ';
|
|
64
|
+
|
|
65
|
+
const result = await ingest(apiUrl, config.apiKey, batch, {
|
|
66
|
+
onProgress(sent, total) {
|
|
67
|
+
const pct = Math.round((sent / total) * 100);
|
|
68
|
+
process.stdout.write(`${prefix}${formatBytes(sent)}/${formatBytes(total)} (${pct}%)\r`);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
64
71
|
totalIngested += result.ingested ?? batch.length;
|
|
65
72
|
|
|
66
73
|
// Save progress after each successful batch so partial uploads survive interruptions
|
|
@@ -68,7 +75,7 @@ export async function runSync() {
|
|
|
68
75
|
saveConfig(config);
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
if (totalBatches > 1) {
|
|
78
|
+
if (totalBatches > 1 || allBuckets.length > 0) {
|
|
72
79
|
process.stdout.write('\n');
|
|
73
80
|
}
|
|
74
81
|
console.log(`Synced ${totalIngested} buckets.`);
|