@vibe-cafe/vibe-usage 0.1.2 → 0.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-cafe/vibe-usage",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Track your AI coding tool token usage and sync to vibecafe.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,76 +3,100 @@ import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { aggregateToBuckets } from './index.js';
5
5
 
6
- const AGENTS_DIR = join(homedir(), '.openclaw', 'agents');
6
+ // OpenClaw stores data at ~/.openclaw/agents/<agentId>/sessions/*.jsonl
7
+ // Legacy paths: ~/.clawdbot, ~/.moltbot, ~/.moldbot
8
+ const POSSIBLE_ROOTS = [
9
+ join(homedir(), '.openclaw'),
10
+ join(homedir(), '.clawdbot'),
11
+ join(homedir(), '.moltbot'),
12
+ join(homedir(), '.moldbot'),
13
+ ];
7
14
 
8
- export async function parse(lastSync) {
9
- if (!existsSync(AGENTS_DIR)) return [];
15
+ /** Normalize usage fields — OpenClaw supports multiple naming conventions */
16
+ function getTokens(usage, ...keys) {
17
+ for (const key of keys) {
18
+ if (usage[key] != null && usage[key] > 0) return usage[key];
19
+ }
20
+ return 0;
21
+ }
10
22
 
23
+ export async function parse(lastSync) {
11
24
  const entries = [];
12
- let agentDirs;
13
- try {
14
- agentDirs = readdirSync(AGENTS_DIR, { withFileTypes: true })
15
- .filter(d => d.isDirectory());
16
- } catch {
17
- return [];
18
- }
19
25
 
20
- for (const agentDir of agentDirs) {
21
- const project = agentDir.name;
22
- const sessionsDir = join(AGENTS_DIR, agentDir.name, 'sessions');
23
- if (!existsSync(sessionsDir)) continue;
26
+ for (const root of POSSIBLE_ROOTS) {
27
+ const agentsDir = join(root, 'agents');
28
+ if (!existsSync(agentsDir)) continue;
24
29
 
25
- let files;
30
+ let agentDirs;
26
31
  try {
27
- files = readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
32
+ agentDirs = readdirSync(agentsDir, { withFileTypes: true })
33
+ .filter(d => d.isDirectory());
28
34
  } catch {
29
35
  continue;
30
36
  }
31
37
 
32
- for (const file of files) {
33
- const filePath = join(sessionsDir, file);
34
- if (lastSync) {
35
- try {
36
- const stat = statSync(filePath);
37
- if (stat.mtime <= new Date(lastSync)) continue;
38
- } catch {
39
- continue;
40
- }
41
- }
38
+ for (const agentDir of agentDirs) {
39
+ const project = agentDir.name;
40
+ const sessionsDir = join(agentsDir, agentDir.name, 'sessions');
41
+ if (!existsSync(sessionsDir)) continue;
42
42
 
43
- let content;
43
+ let files;
44
44
  try {
45
- content = readFileSync(filePath, 'utf-8');
45
+ files = readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
46
46
  } catch {
47
47
  continue;
48
48
  }
49
49
 
50
- for (const line of content.split('\n')) {
51
- if (!line.trim()) continue;
50
+ for (const file of files) {
51
+ const filePath = join(sessionsDir, file);
52
+ if (lastSync) {
53
+ try {
54
+ const stat = statSync(filePath);
55
+ if (stat.mtime <= new Date(lastSync)) continue;
56
+ } catch {
57
+ continue;
58
+ }
59
+ }
60
+
61
+ let content;
52
62
  try {
53
- const obj = JSON.parse(line);
63
+ content = readFileSync(filePath, 'utf-8');
64
+ } catch {
65
+ continue;
66
+ }
54
67
 
55
- const usage = obj.usage || obj.message?.usage;
56
- if (!usage) continue;
68
+ for (const line of content.split('\n')) {
69
+ if (!line.trim()) continue;
70
+ try {
71
+ const obj = JSON.parse(line);
57
72
 
58
- const timestamp = obj.timestamp || obj.created_at;
59
- if (!timestamp) continue;
60
- const ts = new Date(timestamp);
61
- if (isNaN(ts.getTime())) continue;
62
- if (lastSync && ts <= new Date(lastSync)) continue;
73
+ // Only process message entries with assistant role
74
+ if (obj.type !== 'message') continue;
75
+ const msg = obj.message;
76
+ if (!msg || msg.role !== 'assistant') continue;
63
77
 
64
- entries.push({
65
- source: 'openclaw',
66
- model: obj.model || obj.message?.model || 'unknown',
67
- project,
68
- timestamp: ts,
69
- inputTokens: usage.input_tokens || 0,
70
- outputTokens: usage.output_tokens || 0,
71
- cachedInputTokens: usage.cache_read_input_tokens || 0,
72
- reasoningOutputTokens: 0,
73
- });
74
- } catch {
75
- continue;
78
+ const usage = msg.usage;
79
+ if (!usage) continue;
80
+
81
+ const timestamp = obj.timestamp || msg.timestamp;
82
+ if (!timestamp) continue;
83
+ const ts = new Date(typeof timestamp === 'number' ? timestamp : timestamp);
84
+ if (isNaN(ts.getTime())) continue;
85
+ if (lastSync && ts <= new Date(lastSync)) continue;
86
+
87
+ entries.push({
88
+ source: 'openclaw',
89
+ model: msg.model || obj.model || 'unknown',
90
+ project,
91
+ timestamp: ts,
92
+ inputTokens: getTokens(usage, 'input', 'inputTokens', 'input_tokens', 'promptTokens', 'prompt_tokens'),
93
+ outputTokens: getTokens(usage, 'output', 'outputTokens', 'output_tokens', 'completionTokens', 'completion_tokens'),
94
+ cachedInputTokens: getTokens(usage, 'cacheRead', 'cache_read', 'cache_read_input_tokens'),
95
+ reasoningOutputTokens: 0,
96
+ });
97
+ } catch {
98
+ continue;
99
+ }
76
100
  }
77
101
  }
78
102
  }