@vibe-cafe/vibe-usage 0.1.1 → 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/hooks.js +1 -1
- package/src/init.js +2 -2
- package/src/parsers/openclaw.js +74 -50
package/package.json
CHANGED
package/src/hooks.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
|
|
5
|
-
const SYNC_CMD = 'npx vibe-usage sync 2>/dev/null &';
|
|
5
|
+
const SYNC_CMD = 'npx @vibe-cafe/vibe-usage sync 2>/dev/null &';
|
|
6
6
|
|
|
7
7
|
function hasVibeUsageHook(hooks) {
|
|
8
8
|
if (!Array.isArray(hooks)) return false;
|
package/src/init.js
CHANGED
|
@@ -86,7 +86,7 @@ export async function runInit() {
|
|
|
86
86
|
console.log(`Hooks installed for: ${hooked.join(', ')}`);
|
|
87
87
|
}
|
|
88
88
|
for (const name of manualOnly) {
|
|
89
|
-
console.log(`${name} detected — use \`npx vibe-usage sync\` to sync manually.`);
|
|
89
|
+
console.log(`${name} detected — use \`npx @vibe-cafe/vibe-usage sync\` to sync manually.`);
|
|
90
90
|
}
|
|
91
91
|
if (tools.length === 0) {
|
|
92
92
|
console.log('No AI coding tools detected. Install one and re-run init.');
|
|
@@ -95,5 +95,5 @@ export async function runInit() {
|
|
|
95
95
|
console.log('\nRunning initial sync...');
|
|
96
96
|
await runSync();
|
|
97
97
|
|
|
98
|
-
console.log(
|
|
98
|
+
console.log(`\nSetup complete! View your dashboard at: ${apiUrl}/usage`);
|
|
99
99
|
}
|
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
|
}
|