@vibe-cafe/vibe-usage 0.7.3 → 0.7.5
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/README.md +5 -4
- package/package.json +1 -1
- package/src/daemon-service.js +1 -1
- package/src/daemon.js +2 -2
- package/src/index.js +1 -1
- package/src/parsers/hermes.js +100 -0
- package/src/parsers/index.js +2 -0
- package/src/skill.js +1 -1
- package/src/tools.js +5 -0
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ This will:
|
|
|
19
19
|
npx @vibe-cafe/vibe-usage # Init (first run) or sync (subsequent runs)
|
|
20
20
|
npx @vibe-cafe/vibe-usage init # Re-run setup
|
|
21
21
|
npx @vibe-cafe/vibe-usage sync # Manual sync
|
|
22
|
-
npx @vibe-cafe/vibe-usage daemon # Continuous sync (every
|
|
22
|
+
npx @vibe-cafe/vibe-usage daemon # Continuous sync (every 30m, foreground)
|
|
23
23
|
npx @vibe-cafe/vibe-usage daemon install # Install background service (systemd/launchd)
|
|
24
24
|
npx @vibe-cafe/vibe-usage daemon uninstall # Remove background service
|
|
25
25
|
npx @vibe-cafe/vibe-usage daemon status # Show background service status
|
|
@@ -47,12 +47,13 @@ npx @vibe-cafe/vibe-usage status # Show config & detected tools
|
|
|
47
47
|
| Kimi Code | `~/.kimi/sessions/` |
|
|
48
48
|
| Amp | `~/.local/share/amp/threads/` |
|
|
49
49
|
| Droid | `~/.factory/sessions/` |
|
|
50
|
+
| Hermes | `~/.hermes/state.db` (SQLite) |
|
|
50
51
|
|
|
51
52
|
## How It Works
|
|
52
53
|
|
|
53
54
|
- Parses local session logs from each AI coding tool
|
|
54
55
|
- Aggregates token usage into 30-minute buckets
|
|
55
|
-
- Extracts session metadata from all
|
|
56
|
+
- Extracts session metadata from all parsers: active time (AI generation time, excluding queue/TTFT wait), total duration, message counts
|
|
56
57
|
- Uploads buckets + sessions to your vibecafe.ai dashboard
|
|
57
58
|
- Stateless: computes full totals from local logs each sync (idempotent, no state files)
|
|
58
59
|
- For continuous syncing, use `npx @vibe-cafe/vibe-usage daemon` or the [Vibe Usage Mac app](https://github.com/vibe-cafe/vibe-usage-app)
|
|
@@ -114,7 +115,7 @@ Install as a system service for automatic background syncing:
|
|
|
114
115
|
npx @vibe-cafe/vibe-usage daemon install
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
This creates a user-level service (systemd on Linux, launchd on macOS) that syncs every
|
|
118
|
+
This creates a user-level service (systemd on Linux, launchd on macOS) that syncs every 30 minutes and starts automatically on login. Manage with:
|
|
118
119
|
|
|
119
120
|
```bash
|
|
120
121
|
npx @vibe-cafe/vibe-usage daemon status
|
|
@@ -127,7 +128,7 @@ For reliable operation, install globally first: `npm install -g @vibe-cafe/vibe-
|
|
|
127
128
|
|
|
128
129
|
### Foreground mode
|
|
129
130
|
|
|
130
|
-
Run continuous syncing in the foreground (every
|
|
131
|
+
Run continuous syncing in the foreground (every 30 minutes):
|
|
131
132
|
|
|
132
133
|
```bash
|
|
133
134
|
npx @vibe-cafe/vibe-usage daemon
|
package/package.json
CHANGED
package/src/daemon-service.js
CHANGED
|
@@ -158,7 +158,7 @@ function install() {
|
|
|
158
158
|
console.log('Service loaded and started.');
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
console.log('\nDaemon installed. Usage data will sync automatically every
|
|
161
|
+
console.log('\nDaemon installed. Usage data will sync automatically every 30 minutes.');
|
|
162
162
|
console.log('Run `vibe-usage daemon status` to check.');
|
|
163
163
|
}
|
|
164
164
|
|
package/src/daemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from './config.js';
|
|
2
2
|
import { runSync } from './sync.js';
|
|
3
3
|
|
|
4
|
-
const INTERVAL =
|
|
4
|
+
const INTERVAL = 30 * 60_000; // 30 minutes
|
|
5
5
|
|
|
6
6
|
function log(msg) {
|
|
7
7
|
const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
@@ -19,7 +19,7 @@ export async function runDaemon() {
|
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
log('Daemon started (sync every
|
|
22
|
+
log('Daemon started (sync every 30m, Ctrl+C to stop)');
|
|
23
23
|
|
|
24
24
|
// eslint-disable-next-line no-constant-condition
|
|
25
25
|
while (true) {
|
package/src/index.js
CHANGED
|
@@ -142,7 +142,7 @@ export async function run(args) {
|
|
|
142
142
|
npx @vibe-cafe/vibe-usage Init (first run) or sync
|
|
143
143
|
npx @vibe-cafe/vibe-usage init Set up API key
|
|
144
144
|
npx @vibe-cafe/vibe-usage sync Manually sync usage data
|
|
145
|
-
npx @vibe-cafe/vibe-usage daemon Continuous sync (every
|
|
145
|
+
npx @vibe-cafe/vibe-usage daemon Continuous sync (every 30m, foreground)
|
|
146
146
|
npx @vibe-cafe/vibe-usage daemon install Install background service (systemd/launchd)
|
|
147
147
|
npx @vibe-cafe/vibe-usage daemon uninstall Remove background service
|
|
148
148
|
npx @vibe-cafe/vibe-usage daemon status Show background service status
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { aggregateToBuckets, extractSessions } from './index.js';
|
|
6
|
+
|
|
7
|
+
const HERMES_HOME = process.env.HERMES_HOME || join(homedir(), '.hermes');
|
|
8
|
+
const DB_PATH = join(HERMES_HOME, 'state.db');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse Hermes Agent usage data from its SQLite database (~/.hermes/state.db).
|
|
12
|
+
*
|
|
13
|
+
* Token buckets come from the sessions table (cumulative per-session totals).
|
|
14
|
+
* Session timing comes from the messages table (per-message role + timestamp).
|
|
15
|
+
*/
|
|
16
|
+
export async function parse() {
|
|
17
|
+
if (!existsSync(DB_PATH)) return { buckets: [], sessions: [] };
|
|
18
|
+
|
|
19
|
+
let sessionRows;
|
|
20
|
+
try {
|
|
21
|
+
sessionRows = queryDb(`SELECT
|
|
22
|
+
id,
|
|
23
|
+
model,
|
|
24
|
+
started_at as startedAt,
|
|
25
|
+
input_tokens as inputTokens,
|
|
26
|
+
output_tokens as outputTokens,
|
|
27
|
+
cache_read_tokens as cacheReadTokens,
|
|
28
|
+
reasoning_tokens as reasoningTokens
|
|
29
|
+
FROM sessions
|
|
30
|
+
WHERE input_tokens > 0 OR output_tokens > 0`);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err.message && err.message.includes('ENOENT')) {
|
|
33
|
+
throw new Error('sqlite3 CLI not found. Install sqlite3 to sync Hermes data.');
|
|
34
|
+
}
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const entries = [];
|
|
39
|
+
for (const row of sessionRows) {
|
|
40
|
+
// started_at is a Unix timestamp (float)
|
|
41
|
+
const timestamp = new Date(row.startedAt * 1000);
|
|
42
|
+
if (isNaN(timestamp.getTime())) continue;
|
|
43
|
+
|
|
44
|
+
// Hermes stores input_tokens exclusive of cache (Anthropic-style semantics)
|
|
45
|
+
entries.push({
|
|
46
|
+
source: 'hermes',
|
|
47
|
+
model: row.model || 'unknown',
|
|
48
|
+
project: 'unknown',
|
|
49
|
+
timestamp,
|
|
50
|
+
inputTokens: row.inputTokens || 0,
|
|
51
|
+
outputTokens: row.outputTokens || 0,
|
|
52
|
+
cachedInputTokens: row.cacheReadTokens || 0,
|
|
53
|
+
reasoningOutputTokens: row.reasoningTokens || 0,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Session events from messages table for active time calculation
|
|
58
|
+
let messageRows;
|
|
59
|
+
try {
|
|
60
|
+
messageRows = queryDb(`SELECT
|
|
61
|
+
session_id as sessionId,
|
|
62
|
+
role,
|
|
63
|
+
timestamp
|
|
64
|
+
FROM messages
|
|
65
|
+
WHERE role IN ('user', 'assistant')
|
|
66
|
+
ORDER BY timestamp`);
|
|
67
|
+
} catch {
|
|
68
|
+
// Messages query failed — return buckets only
|
|
69
|
+
return { buckets: aggregateToBuckets(entries), sessions: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sessionEvents = [];
|
|
73
|
+
for (const row of messageRows) {
|
|
74
|
+
const timestamp = new Date(row.timestamp * 1000);
|
|
75
|
+
if (isNaN(timestamp.getTime())) continue;
|
|
76
|
+
|
|
77
|
+
sessionEvents.push({
|
|
78
|
+
sessionId: row.sessionId,
|
|
79
|
+
source: 'hermes',
|
|
80
|
+
project: 'unknown',
|
|
81
|
+
timestamp,
|
|
82
|
+
role: row.role === 'user' ? 'user' : 'assistant',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { buckets: aggregateToBuckets(entries), sessions: extractSessions(sessionEvents) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function queryDb(sql) {
|
|
90
|
+
const output = execFileSync('sqlite3', [
|
|
91
|
+
'-json',
|
|
92
|
+
DB_PATH,
|
|
93
|
+
sql,
|
|
94
|
+
], { encoding: 'utf-8', maxBuffer: 100 * 1024 * 1024, timeout: 30000 });
|
|
95
|
+
|
|
96
|
+
const trimmed = output.trim();
|
|
97
|
+
if (!trimmed || trimmed === '[]') return [];
|
|
98
|
+
|
|
99
|
+
return JSON.parse(trimmed);
|
|
100
|
+
}
|
package/src/parsers/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { parse as parseKimiCode } from './kimi-code.js';
|
|
|
10
10
|
import { parse as parseAmp } from './amp.js';
|
|
11
11
|
import { parse as parseDroid } from './droid.js';
|
|
12
12
|
import { parse as parseAntigravity } from './antigravity.js';
|
|
13
|
+
import { parse as parseHermes } from './hermes.js';
|
|
13
14
|
import { parse as parsePiCodingAgent } from './pi-coding-agent.js';
|
|
14
15
|
|
|
15
16
|
export const parsers = {
|
|
@@ -24,6 +25,7 @@ export const parsers = {
|
|
|
24
25
|
'amp': parseAmp,
|
|
25
26
|
'droid': parseDroid,
|
|
26
27
|
'antigravity': parseAntigravity,
|
|
28
|
+
'hermes': parseHermes,
|
|
27
29
|
'pi-coding-agent': parsePiCodingAgent,
|
|
28
30
|
};
|
|
29
31
|
|
package/src/skill.js
CHANGED
|
@@ -63,7 +63,7 @@ Other available commands:
|
|
|
63
63
|
|---------|-------------|
|
|
64
64
|
| \`npx @vibe-cafe/vibe-usage sync\` | Sync latest usage data |
|
|
65
65
|
| \`npx @vibe-cafe/vibe-usage status\` | Show config and detected tools |
|
|
66
|
-
| \`npx @vibe-cafe/vibe-usage daemon\` | Continuous sync every
|
|
66
|
+
| \`npx @vibe-cafe/vibe-usage daemon\` | Continuous sync every 30 minutes |
|
|
67
67
|
| \`npx @vibe-cafe/vibe-usage reset\` | Delete all data and re-upload |
|
|
68
68
|
| \`npx @vibe-cafe/vibe-usage reset --local\` | Delete this host's data and re-upload |
|
|
69
69
|
|
package/src/tools.js
CHANGED