@vibe-cafe/vibe-usage 0.10.8 → 0.10.9
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 +1 -1
- package/package.json +1 -1
- package/src/parsers/workbuddy.js +48 -16
- package/src/tools.js +1 -1
- package/src/workbuddy-roots.js +6 -3
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ npx @vibe-cafe/vibe-usage status # Show config & detected tools
|
|
|
75
75
|
| Roo Code | `<host>/User/globalStorage/rooveterinaryinc.roo-cline/{tasks/_index.json,tasks/<id>/{history_item,ui_messages}.json}` (walks all VSCode-fork hosts) |
|
|
76
76
|
| Trae CLI | macOS: `~/Library/Caches/trae-cli/sessions/`; Windows: `%LOCALAPPDATA%/trae-cli/cache/sessions/`; Linux: `~/.cache/trae-cli/sessions/` (CLI telemetry only; Trae IDE/Trae Work chats are not supported) |
|
|
77
77
|
| Antigravity | App 2.0 `~/.gemini/antigravity/conversations/*.db` and `agy` CLI `~/.gemini/antigravity-cli/conversations/*.db` are parsed offline (tokens, real model display name, project, sessions); legacy App `.pb` history falls back to Connect RPC while the language server is running |
|
|
78
|
-
| WorkBuddy |
|
|
78
|
+
| WorkBuddy | Current releases: `~/.workbuddy-ai/projects/**/*.jsonl`; legacy releases: `~/.workbuddy/projects/**/*.jsonl` (fixture/relocation override: `VIBE_USAGE_WORKBUDDY_DIRS`). Reads usage-bearing completed assistant and `function_call` records, using the routed model identifier exposed as `providerData.requestModelId`. Splits cache reads and reasoning from inclusive input/output totals, deduplicates copied record IDs, and extracts local session timing without uploading message content. |
|
|
79
79
|
| ZCode | `~/.zcode/cli/db/db.sqlite` (SQLite; reads the `message` table for per-message tokens, model, and project `cwd`/`root`, joined to `session.directory`) |
|
|
80
80
|
|
|
81
81
|
## How It Works
|
package/package.json
CHANGED
package/src/parsers/workbuddy.js
CHANGED
|
@@ -40,7 +40,12 @@ function projectFromFile(filePath, projectsDir) {
|
|
|
40
40
|
function projectFromRecord(record) {
|
|
41
41
|
const cwd = typeof record.cwd === 'string' ? record.cwd.trim() : '';
|
|
42
42
|
if (!cwd) return null;
|
|
43
|
-
|
|
43
|
+
const parts = cwd
|
|
44
|
+
.replace(/[\\/]+$/, '')
|
|
45
|
+
.split(/[\\/]/)
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.filter(part => !/^[a-zA-Z]:$/.test(part));
|
|
48
|
+
return parts.at(-1) || null;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
function findJsonlFiles(dir, ctx) {
|
|
@@ -115,6 +120,13 @@ function isCompletedAssistant(record) {
|
|
|
115
120
|
return status === 'completed' || status === 'complete' || status === 'success';
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
function isUsageRecord(record) {
|
|
124
|
+
return isCompletedAssistant(record)
|
|
125
|
+
|| (record.type === 'function_call'
|
|
126
|
+
&& record.providerData
|
|
127
|
+
&& typeof record.providerData === 'object');
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
function modelFor(record) {
|
|
119
131
|
const providerData = record.providerData && typeof record.providerData === 'object'
|
|
120
132
|
? record.providerData
|
|
@@ -156,20 +168,24 @@ function usageFor(record) {
|
|
|
156
168
|
|
|
157
169
|
const inputDetails = primary?.input_details
|
|
158
170
|
?? primary?.inputDetails
|
|
159
|
-
?? primary?.inputTokensDetails
|
|
171
|
+
?? primary?.inputTokensDetails
|
|
172
|
+
?? raw?.prompt_tokens_details;
|
|
160
173
|
const outputDetails = primary?.output_details
|
|
161
174
|
?? primary?.outputDetails
|
|
162
|
-
?? primary?.outputTokensDetails
|
|
175
|
+
?? primary?.outputTokensDetails
|
|
176
|
+
?? raw?.completion_tokens_details;
|
|
163
177
|
const cachedInputTokens = firstDetailValue(inputDetails, 'cached_tokens', 'cachedTokens')
|
|
164
178
|
|| finite(
|
|
165
|
-
primary?.
|
|
179
|
+
primary?.cachedInputTokens
|
|
180
|
+
?? primary?.cache_read_input_tokens
|
|
166
181
|
?? primary?.cacheReadInputTokens
|
|
167
182
|
?? raw?.prompt_cache_hit_tokens
|
|
168
183
|
?? raw?.cache_read_input_tokens
|
|
169
184
|
);
|
|
170
185
|
const reasoningOutputTokens = firstDetailValue(outputDetails, 'reasoning_tokens', 'reasoningTokens')
|
|
171
186
|
|| finite(
|
|
172
|
-
primary?.
|
|
187
|
+
primary?.reasoningOutputTokens
|
|
188
|
+
?? primary?.completion_thinking_tokens
|
|
173
189
|
?? primary?.reasoning_tokens
|
|
174
190
|
?? primary?.reasoningTokens
|
|
175
191
|
?? raw?.completion_thinking_tokens
|
|
@@ -207,10 +223,16 @@ function timestampFor(record) {
|
|
|
207
223
|
);
|
|
208
224
|
}
|
|
209
225
|
|
|
226
|
+
function sessionEventsWithPrompts(events) {
|
|
227
|
+
const sessionsWithUsers = new Set(
|
|
228
|
+
events.filter(event => event.role === 'user').map(event => event.sessionId)
|
|
229
|
+
);
|
|
230
|
+
return events.filter(event => sessionsWithUsers.has(event.sessionId));
|
|
231
|
+
}
|
|
232
|
+
|
|
210
233
|
export async function parse() {
|
|
211
234
|
const entriesById = new Map();
|
|
212
|
-
const
|
|
213
|
-
const anonymousEvents = [];
|
|
235
|
+
const eventsByKey = new Map();
|
|
214
236
|
const ctx = { skipped: false, warnings: [] };
|
|
215
237
|
const projectDirs = [...new Set(findWorkbuddyDataDirs().map(root => (
|
|
216
238
|
basename(root) === 'projects' ? root : join(root, 'projects')
|
|
@@ -226,7 +248,7 @@ export async function parse() {
|
|
|
226
248
|
continue;
|
|
227
249
|
}
|
|
228
250
|
|
|
229
|
-
const
|
|
251
|
+
const fallbackSessionId = basename(filePath, '.jsonl');
|
|
230
252
|
let project = projectFromFile(filePath, projectsDir);
|
|
231
253
|
const fileEntries = [];
|
|
232
254
|
const fileEvents = [];
|
|
@@ -236,14 +258,22 @@ export async function parse() {
|
|
|
236
258
|
const timestamp = timestampFor(record);
|
|
237
259
|
const id = recordId(record);
|
|
238
260
|
const role = roleFor(record);
|
|
261
|
+
const explicitSessionId = record.sessionId ?? record.session_id;
|
|
262
|
+
const sessionId = explicitSessionId == null || String(explicitSessionId).trim() === ''
|
|
263
|
+
? fallbackSessionId
|
|
264
|
+
: String(explicitSessionId);
|
|
239
265
|
|
|
240
|
-
|
|
241
|
-
|
|
266
|
+
const usage = isUsageRecord(record) ? usageFor(record) : null;
|
|
267
|
+
const eventRole = role === 'user'
|
|
268
|
+
? 'user'
|
|
269
|
+
: isCompletedAssistant(record) || (record.type === 'function_call' && usage)
|
|
270
|
+
? 'assistant'
|
|
271
|
+
: null;
|
|
272
|
+
if (timestamp && eventRole) {
|
|
273
|
+
fileEvents.push({ id, sessionId, timestamp, role: eventRole });
|
|
242
274
|
}
|
|
243
275
|
|
|
244
|
-
if (!id || !timestamp || !
|
|
245
|
-
const usage = usageFor(record);
|
|
246
|
-
if (!usage) return;
|
|
276
|
+
if (!id || !timestamp || !usage) return;
|
|
247
277
|
fileEntries.push({
|
|
248
278
|
id,
|
|
249
279
|
score: usage.score,
|
|
@@ -272,15 +302,17 @@ export async function parse() {
|
|
|
272
302
|
timestamp: candidate.timestamp,
|
|
273
303
|
role: candidate.role,
|
|
274
304
|
};
|
|
275
|
-
|
|
276
|
-
|
|
305
|
+
const key = candidate.id
|
|
306
|
+
? `id:${candidate.sessionId}:${candidate.id}:${candidate.role}`
|
|
307
|
+
: `fallback:${candidate.sessionId}:${candidate.role}:${candidate.timestamp.toISOString()}`;
|
|
308
|
+
eventsByKey.set(key, event);
|
|
277
309
|
}
|
|
278
310
|
}
|
|
279
311
|
}
|
|
280
312
|
|
|
281
313
|
return {
|
|
282
314
|
buckets: aggregateToBuckets([...entriesById.values()].map(({ entry }) => entry)),
|
|
283
|
-
sessions: extractSessions([...
|
|
315
|
+
sessions: extractSessions(sessionEventsWithPrompts([...eventsByKey.values()])),
|
|
284
316
|
...(ctx.skipped ? { skipped: true } : {}),
|
|
285
317
|
...(ctx.warnings.length > 0 ? { warnings: ctx.warnings } : {}),
|
|
286
318
|
};
|
package/src/tools.js
CHANGED
|
@@ -335,7 +335,7 @@ export const TOOLS = [
|
|
|
335
335
|
{
|
|
336
336
|
name: 'WorkBuddy',
|
|
337
337
|
id: 'workbuddy',
|
|
338
|
-
dataDir: join(homedir(), '.workbuddy', 'projects'),
|
|
338
|
+
dataDir: join(homedir(), '.workbuddy-ai', 'projects'),
|
|
339
339
|
detectDataDirs: () => findWorkbuddyDataDirs().filter(existsSync),
|
|
340
340
|
},
|
|
341
341
|
{
|
package/src/workbuddy-roots.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { delimiter, join } from 'node:path';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
return
|
|
4
|
+
export function getDefaultWorkbuddyProjectsDirs(home = homedir()) {
|
|
5
|
+
return [
|
|
6
|
+
join(home, '.workbuddy-ai', 'projects'),
|
|
7
|
+
join(home, '.workbuddy', 'projects'),
|
|
8
|
+
];
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
// Fixture/relocation hook. Entries may name either the WorkBuddy home or its
|
|
9
12
|
// projects/ directory; the parser normalizes both forms.
|
|
10
13
|
export function findWorkbuddyDataDirs() {
|
|
11
14
|
const override = process.env.VIBE_USAGE_WORKBUDDY_DIRS?.trim();
|
|
12
|
-
if (!override) return
|
|
15
|
+
if (!override) return getDefaultWorkbuddyProjectsDirs();
|
|
13
16
|
return [...new Set(
|
|
14
17
|
override
|
|
15
18
|
.split(delimiter)
|