@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 +1 -1
- package/src/parsers/openclaw.js +74 -50
package/package.json
CHANGED
package/src/parsers/openclaw.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
|
30
|
+
let agentDirs;
|
|
26
31
|
try {
|
|
27
|
-
|
|
32
|
+
agentDirs = readdirSync(agentsDir, { withFileTypes: true })
|
|
33
|
+
.filter(d => d.isDirectory());
|
|
28
34
|
} catch {
|
|
29
35
|
continue;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
for (const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
43
|
+
let files;
|
|
44
44
|
try {
|
|
45
|
-
|
|
45
|
+
files = readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
|
|
46
46
|
} catch {
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
for (const
|
|
51
|
-
|
|
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
|
-
|
|
63
|
+
content = readFileSync(filePath, 'utf-8');
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
if (!
|
|
68
|
+
for (const line of content.split('\n')) {
|
|
69
|
+
if (!line.trim()) continue;
|
|
70
|
+
try {
|
|
71
|
+
const obj = JSON.parse(line);
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
timestamp
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
}
|