vibeusage 0.6.0 → 0.6.2
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
|
@@ -39,6 +39,16 @@ const { spawnSync } = require("node:child_process");
|
|
|
39
39
|
const DEFAULT_DAYS = 14;
|
|
40
40
|
const DEFAULT_THRESHOLD_PCT = 25;
|
|
41
41
|
|
|
42
|
+
// `resolveUserIdViaInsforge` and `queryDbTotalsViaInsforge` interpolate these
|
|
43
|
+
// values into a SQL string handed to `insforge db query` (argv, not shell, but
|
|
44
|
+
// the argv reaches a SQL executor with service-role authority). The three
|
|
45
|
+
// values are validated against strict whitelists so a malicious / typo flag
|
|
46
|
+
// like --user-id "foo'; DROP TABLE users; --" cannot reach the DB.
|
|
47
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
48
|
+
const SOURCE_ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
49
|
+
const ISO_TIMESTAMP_RE =
|
|
50
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
51
|
+
|
|
42
52
|
function runSourceAudit({
|
|
43
53
|
strategy,
|
|
44
54
|
days = DEFAULT_DAYS,
|
|
@@ -246,7 +256,7 @@ function nonneg(v) {
|
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
function resolveUserIdViaInsforge({ deviceId }) {
|
|
249
|
-
if (!deviceId) return null;
|
|
259
|
+
if (!deviceId || !UUID_RE.test(String(deviceId))) return null;
|
|
250
260
|
const r = spawnSync(
|
|
251
261
|
"insforge",
|
|
252
262
|
[
|
|
@@ -264,6 +274,27 @@ function resolveUserIdViaInsforge({ deviceId }) {
|
|
|
264
274
|
}
|
|
265
275
|
|
|
266
276
|
function queryDbTotalsViaInsforge({ userId, source, windowStartIso }) {
|
|
277
|
+
if (!userId || !UUID_RE.test(String(userId))) {
|
|
278
|
+
return {
|
|
279
|
+
ok: false,
|
|
280
|
+
error: "invalid-user-id",
|
|
281
|
+
message: `refusing to query DB with non-UUID user id '${String(userId).slice(0, 40)}'`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (!source || !SOURCE_ID_RE.test(String(source))) {
|
|
285
|
+
return {
|
|
286
|
+
ok: false,
|
|
287
|
+
error: "invalid-source-id",
|
|
288
|
+
message: `refusing to query DB with non-identifier source '${String(source).slice(0, 40)}'`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (!windowStartIso || !ISO_TIMESTAMP_RE.test(String(windowStartIso))) {
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
error: "invalid-window-start",
|
|
295
|
+
message: `refusing to query DB with non-ISO windowStartIso '${String(windowStartIso).slice(0, 40)}'`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
267
298
|
const sql =
|
|
268
299
|
`SELECT DATE(hour_start) AS day, SUM(total_tokens) AS tokens ` +
|
|
269
300
|
`FROM vibeusage_tracker_hourly ` +
|
|
@@ -361,4 +392,8 @@ module.exports = {
|
|
|
361
392
|
runSourceAudit,
|
|
362
393
|
getStrategy,
|
|
363
394
|
listRegisteredSources,
|
|
395
|
+
// Exported for targeted tests that assert the SQL-interpolation inputs are
|
|
396
|
+
// validated before reaching `insforge db query`.
|
|
397
|
+
queryDbTotalsViaInsforge,
|
|
398
|
+
resolveUserIdViaInsforge,
|
|
364
399
|
};
|
|
@@ -11,14 +11,25 @@ module.exports = {
|
|
|
11
11
|
},
|
|
12
12
|
walkSessions({ root }) {
|
|
13
13
|
if (!fs.existsSync(root)) return [];
|
|
14
|
+
// Recurse: Claude Code writes the main thread under
|
|
15
|
+
// `projects/<project>/<session>.jsonl` AND subagent threads under
|
|
16
|
+
// `projects/<project>/<sessionId>/subagents/agent-*.jsonl`. Subagents
|
|
17
|
+
// burn real Anthropic tokens, so the audit must include them. Sync's
|
|
18
|
+
// walkClaudeProjects (rollout.js) already recurses; this mirrors it.
|
|
14
19
|
const out = [];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const dir =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const stack = [root];
|
|
21
|
+
while (stack.length) {
|
|
22
|
+
const dir = stack.pop();
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
26
|
+
} catch (_err) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const p = path.join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) stack.push(p);
|
|
32
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(p);
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
return out;
|