@vibe-cafe/vibe-usage 0.1.3 → 0.1.4

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": "@vibe-cafe/vibe-usage",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Track your AI coding tool token usage and sync to vibecafe.ai",
5
5
  "type": "module",
6
6
  "bin": {
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: 30_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 (30s)'));
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 { exec } from 'node:child_process';
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
- exec(`${cmd} ${url}`, () => {});
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({
@@ -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
- const usage = info.last_token_usage || info.total_token_usage;
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
- console.error(`Sync failed: ${err.message}`);
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
  }