tokentracker-cli 0.72.1 → 0.74.0
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.de.md +5 -4
- package/README.ja.md +5 -4
- package/README.ko.md +5 -4
- package/README.md +5 -4
- package/README.zh-CN.md +5 -4
- package/dashboard/dist/assets/{ActivityHeatmap-CtDL5OPO.js → ActivityHeatmap-DkoSqxrd.js} +1 -1
- package/dashboard/dist/assets/{Card-C5lKqKNT.js → Card-DleS-r54.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-XnEef7Ra.js → CommunityStatsModal-DV41-F9D.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-CkZvn-sV.js → ConfirmModal-Dgp07Uka.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-TmZBLaB9.js → DashboardPage-EKy2BzUx.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-BhDh3VIP.js → DevicePage-eJInQ2UX.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-CddI1gcT.js → DialogClose-DEfesqFq.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-D6txiEc4.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-Cvpx9Lfv.js → DialogTitle-v1JrTvr4.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BojLJcFD.js → FadeIn-hqIBbKaJ.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BrSt1RIj.js → HeaderGithubStar-C4zauuP2.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DxuF-1tF.js → IpCheckPage-CiRcccks.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DmCjJVwh.js → LandingPage-CbOHycnd.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardAvatar-B8SGUidW.js → LeaderboardAvatar-CQL1iOTS.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-D7CQCdDr.js → LeaderboardPage-BuEp247d.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-Dv7CQVqB.js → LeaderboardProfileModal-DT8hrPkI.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-BtGLbQnT.js → LeaderboardProfilePage-B-M0AXMb.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-DRf6h5_2.js → LimitsPage-Df2es3Xs.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BnlODmxA.js → LocalOnlyNotice-DJIfKTKo.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-CYXdV0X0.js → LoginPage-DtaaIy_8.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-x5vYN3uF.js → PopoverPopup-CqWH6aB9.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-D8ifLVwK.js → ResetPasswordPage-DCH5GU1c.js} +1 -1
- package/dashboard/dist/assets/{Select-CaaIuo5M.js → Select-BwyliVKJ.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-CdJ-E-d1.js → SelectItemText-B5_DKKEv.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Dc7v5PEC.js → SettingsPage-BoAFdOUa.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-CRbEMatQ.js → SkillsPage-BVdre-kF.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-xgy8mLMV.js → WidgetsPage-CtjV405P.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BdpfOWJu.js → WrappedPage-DGmPdtHt.js} +1 -1
- package/dashboard/dist/assets/{agent-logos-CrxAAMko.js → agent-logos-DqevdWvx.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-D23bZC75.js → arrow-up-right-DA5Pybq1.js} +1 -1
- package/dashboard/dist/assets/check-BWzAT1oX.js +1 -0
- package/dashboard/dist/assets/{copy-hiVk9JEl.js → copy-CsQRTd89.js} +1 -1
- package/dashboard/dist/assets/{download-CqLiBDG-.js → download-TEHkAKT0.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Ck5lRlFd.js → format-tokens-CtFO_jkz.js} +1 -1
- package/dashboard/dist/assets/{info-CxDHBJXB.js → info-Dxn3fiaX.js} +1 -1
- package/dashboard/dist/assets/main-Dxi4sSXf.js +1128 -0
- package/dashboard/dist/assets/{use-community-stats-CeaLHlCk.js → use-community-stats-BLNxr-bQ.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CvDWYfjp.js → use-limits-display-prefs-CC7gb303.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-HuxDwmn6.js → use-native-settings-SYtjLlxZ.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-KIV2M5tf.js → use-ordered-list-_O8onJV7.js} +1 -1
- package/dashboard/dist/assets/{use-quality-per-dollar-pref-BbsMhRJt.js → use-quality-per-dollar-pref-tuspH_6n.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-COBOepfU.js → use-usage-limits-whCVzQbf.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DbBq0Fal.js → useCurrency-BiDjYxfm.js} +1 -1
- package/dashboard/dist/assets/{useScrollLock-C4eBSnib.js → useScrollLock-DK-ndHcw.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/status.js +47 -4
- package/src/commands/sync.js +72 -18
- package/src/lib/local-api.js +24 -2
- package/src/lib/pricing/curated-overrides.json +7 -2
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +385 -0
- package/src/lib/sqlite-reader.js +24 -6
- package/src/lib/usage-limits.js +200 -29
- package/src/lib/zcode-limits.js +375 -40
- package/dashboard/dist/assets/DialogDescription-DeEet5bz.js +0 -1
- package/dashboard/dist/assets/check-fkZxgcVQ.js +0 -1
- package/dashboard/dist/assets/main-B0LYKUIY.js +0 -1127
package/src/lib/rollout.js
CHANGED
|
@@ -9448,6 +9448,387 @@ async function parseCopilotIncremental({ otelPaths, cursors, queuePath, onProgre
|
|
|
9448
9448
|
return { recordsProcessed, eventsAggregated, bucketsQueued };
|
|
9449
9449
|
}
|
|
9450
9450
|
|
|
9451
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9452
|
+
// GitHub Copilot App — passive reader for ~/.copilot/data.db
|
|
9453
|
+
//
|
|
9454
|
+
// The Copilot App persists one row per local App session in `sessions`, with
|
|
9455
|
+
// cumulative `total_*` token summary columns. We intentionally do NOT read
|
|
9456
|
+
// session-state/<id>/events.jsonl: forked App sessions inherit parent events
|
|
9457
|
+
// there before the child has produced any tokens, so parsing event history would
|
|
9458
|
+
// double-count parent usage. The only accounting source here is positive deltas
|
|
9459
|
+
// from the `sessions.total_*` counters. Those deltas feed the same aggregate
|
|
9460
|
+
// `source="copilot"` pipeline as existing OTEL data (dashboard totals, trends,
|
|
9461
|
+
// model breakdown, cost), but the cursor is separate (`cursors.copilotApp`) so
|
|
9462
|
+
// Copilot CLI / Chat extension OTEL support keeps its existing behavior.
|
|
9463
|
+
// Project attribution and per-conversation detail are intentionally absent:
|
|
9464
|
+
// data.db's summary columns do not expose privacy-safe project/message metadata.
|
|
9465
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9466
|
+
|
|
9467
|
+
const COPILOT_APP_CURSOR_VERSION = 1;
|
|
9468
|
+
const COPILOT_APP_DEFAULT_MODEL = "github-copilot";
|
|
9469
|
+
|
|
9470
|
+
function normalizeCopilotAppDbPath(dbPath, env = process.env) {
|
|
9471
|
+
if (typeof dbPath !== "string" || !dbPath.trim()) return null;
|
|
9472
|
+
const expanded = expandHomePath(dbPath.trim(), env);
|
|
9473
|
+
return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(expanded);
|
|
9474
|
+
}
|
|
9475
|
+
|
|
9476
|
+
function resolveCopilotAppDbPaths(env = process.env) {
|
|
9477
|
+
const home = env.HOME || require("node:os").homedir();
|
|
9478
|
+
const paths = new Set();
|
|
9479
|
+
const addDbPath = (dbPath) => {
|
|
9480
|
+
const normalized = normalizeCopilotAppDbPath(dbPath, env);
|
|
9481
|
+
if (normalized) paths.add(normalized);
|
|
9482
|
+
};
|
|
9483
|
+
const addHome = (homePath) => {
|
|
9484
|
+
const normalizedHome = normalizeCopilotAppDbPath(homePath, env);
|
|
9485
|
+
if (normalizedHome) addDbPath(path.join(normalizedHome, "data.db"));
|
|
9486
|
+
};
|
|
9487
|
+
|
|
9488
|
+
if (typeof env.TOKENTRACKER_COPILOT_APP_DB === "string" && env.TOKENTRACKER_COPILOT_APP_DB.trim()) {
|
|
9489
|
+
addDbPath(env.TOKENTRACKER_COPILOT_APP_DB);
|
|
9490
|
+
}
|
|
9491
|
+
if (typeof env.COPILOT_HOME === "string" && env.COPILOT_HOME.trim()) {
|
|
9492
|
+
addHome(env.COPILOT_HOME);
|
|
9493
|
+
}
|
|
9494
|
+
|
|
9495
|
+
const defaultDb = path.join(home, ".copilot", "data.db");
|
|
9496
|
+
if (process.platform === "win32") {
|
|
9497
|
+
const wslHome = wsl.shouldProbeWsl(env) ? wsl.discoverWslHome(".copilot", { env }) : null;
|
|
9498
|
+
const wslDb = wslHome ? path.join(wslHome, "data.db") : null;
|
|
9499
|
+
const resolved = resolveInstallPaths({ nativeValue: defaultDb, wslValue: wslDb }, env);
|
|
9500
|
+
if (resolved.native) addDbPath(resolved.native);
|
|
9501
|
+
if (resolved.wsl) addDbPath(resolved.wsl);
|
|
9502
|
+
if (paths.size === 0 && wsl.shouldProbeNative(env)) addDbPath(defaultDb);
|
|
9503
|
+
} else {
|
|
9504
|
+
addDbPath(defaultDb);
|
|
9505
|
+
}
|
|
9506
|
+
|
|
9507
|
+
return Array.from(paths).sort();
|
|
9508
|
+
}
|
|
9509
|
+
|
|
9510
|
+
function resolveCopilotAppDbPath(env = process.env) {
|
|
9511
|
+
const paths = resolveCopilotAppDbPaths(env);
|
|
9512
|
+
for (const candidate of paths) {
|
|
9513
|
+
try {
|
|
9514
|
+
if (fssync.existsSync(candidate)) return candidate;
|
|
9515
|
+
} catch (_e) {}
|
|
9516
|
+
}
|
|
9517
|
+
return paths[0] || null;
|
|
9518
|
+
}
|
|
9519
|
+
|
|
9520
|
+
function shouldCountCopilotAppSession(row) {
|
|
9521
|
+
const sessionType = typeof row?.session_type === "string" ? row.session_type.trim().toLowerCase() : "";
|
|
9522
|
+
const providerId = typeof row?.provider_id === "string" ? row.provider_id.trim().toLowerCase() : "";
|
|
9523
|
+
// OTEL remains the owner for recognizable Copilot CLI / VS Code extension
|
|
9524
|
+
// sessions. The App DB path is separately cursored, but skipping known
|
|
9525
|
+
// OTEL-backed surfaces avoids duplicate source="copilot" buckets if GitHub
|
|
9526
|
+
// mirrors those sessions into data.db in a future release.
|
|
9527
|
+
const markers = [sessionType, providerId];
|
|
9528
|
+
return !markers.some((value) =>
|
|
9529
|
+
value === "cli" ||
|
|
9530
|
+
value === "copilot-cli" ||
|
|
9531
|
+
value.includes("vscode") ||
|
|
9532
|
+
value.includes("extension") ||
|
|
9533
|
+
value.includes("otel")
|
|
9534
|
+
);
|
|
9535
|
+
}
|
|
9536
|
+
|
|
9537
|
+
function parseCopilotAppTimestamp(value) {
|
|
9538
|
+
if (value === null || value === undefined || value === "") return null;
|
|
9539
|
+
if (typeof value === "number") {
|
|
9540
|
+
const ms = value > 10_000_000_000 ? value : value * 1000;
|
|
9541
|
+
const d = new Date(ms);
|
|
9542
|
+
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
9543
|
+
}
|
|
9544
|
+
if (typeof value !== "string") return null;
|
|
9545
|
+
const trimmed = value.trim();
|
|
9546
|
+
if (!trimmed) return null;
|
|
9547
|
+
if (/^\d+$/.test(trimmed)) {
|
|
9548
|
+
return parseCopilotAppTimestamp(Number(trimmed));
|
|
9549
|
+
}
|
|
9550
|
+
const parsed = new Date(trimmed);
|
|
9551
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
|
|
9552
|
+
}
|
|
9553
|
+
|
|
9554
|
+
function normalizeCopilotAppModel(model) {
|
|
9555
|
+
if (typeof model !== "string") return "";
|
|
9556
|
+
const normalized = normalizeModelInput(model);
|
|
9557
|
+
if (!normalized) return "";
|
|
9558
|
+
if (/^claude-(sonnet|opus|haiku)-\d+\.\d+/.test(normalized)) {
|
|
9559
|
+
return normalized.replace(/^(claude-(?:sonnet|opus|haiku)-\d+)\.(\d+)/, "$1-$2");
|
|
9560
|
+
}
|
|
9561
|
+
return normalized;
|
|
9562
|
+
}
|
|
9563
|
+
|
|
9564
|
+
function readCopilotAppSessionsFromSqlite(dbPath, sqliteOptions = {}) {
|
|
9565
|
+
const pragmaRows = readSqliteJsonRows(dbPath, "PRAGMA table_info(sessions)", {
|
|
9566
|
+
label: "GitHub Copilot App",
|
|
9567
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
9568
|
+
timeout: 10_000,
|
|
9569
|
+
readOnly: true,
|
|
9570
|
+
throwOnReadFailure: true,
|
|
9571
|
+
...sqliteOptions,
|
|
9572
|
+
});
|
|
9573
|
+
const columns = new Set(pragmaRows.map((row) => row?.name).filter(Boolean));
|
|
9574
|
+
if (!columns.has("id")) return [];
|
|
9575
|
+
const optional = (col) => (columns.has(col) ? col : `NULL AS ${col}`);
|
|
9576
|
+
const orderTerms = ["updated_at", "created_at"].filter((col) => columns.has(col));
|
|
9577
|
+
const orderBy = orderTerms.length > 0
|
|
9578
|
+
? `ORDER BY COALESCE(${orderTerms.join(", ")}, ''), id`
|
|
9579
|
+
: "ORDER BY id";
|
|
9580
|
+
// Select only the columns the parser consumes. Content-ish columns like
|
|
9581
|
+
// `title` must stay out of the query: token counts only, never user content.
|
|
9582
|
+
const sql = `
|
|
9583
|
+
SELECT
|
|
9584
|
+
id,
|
|
9585
|
+
${optional("session_type")},
|
|
9586
|
+
${optional("model")},
|
|
9587
|
+
${optional("provider_id")},
|
|
9588
|
+
${optional("created_at")},
|
|
9589
|
+
${optional("updated_at")},
|
|
9590
|
+
${optional("total_input_tokens")},
|
|
9591
|
+
${optional("total_output_tokens")},
|
|
9592
|
+
${optional("total_cached_tokens")},
|
|
9593
|
+
${optional("total_reasoning_tokens")}
|
|
9594
|
+
FROM sessions
|
|
9595
|
+
${orderBy}
|
|
9596
|
+
`.trim();
|
|
9597
|
+
return readSqliteJsonRows(dbPath, sql, {
|
|
9598
|
+
label: "GitHub Copilot App",
|
|
9599
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
9600
|
+
timeout: 60_000,
|
|
9601
|
+
readOnly: true,
|
|
9602
|
+
throwOnReadFailure: true,
|
|
9603
|
+
...sqliteOptions,
|
|
9604
|
+
});
|
|
9605
|
+
}
|
|
9606
|
+
|
|
9607
|
+
function sqliteSidecarFingerprint(dbPath) {
|
|
9608
|
+
const out = {};
|
|
9609
|
+
for (const suffix of ["", "-wal", "-shm", "-journal"]) {
|
|
9610
|
+
const filePath = `${dbPath}${suffix}`;
|
|
9611
|
+
try {
|
|
9612
|
+
const stat = fssync.statSync(filePath);
|
|
9613
|
+
out[suffix || "db"] = {
|
|
9614
|
+
size: stat.size,
|
|
9615
|
+
mtimeMs: stat.mtimeMs,
|
|
9616
|
+
ino: stat.ino,
|
|
9617
|
+
};
|
|
9618
|
+
} catch (e) {
|
|
9619
|
+
if (!e || e.code !== "ENOENT") throw e;
|
|
9620
|
+
}
|
|
9621
|
+
}
|
|
9622
|
+
return out;
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
function sameSqliteFingerprint(a, b) {
|
|
9626
|
+
return JSON.stringify(a || {}) === JSON.stringify(b || {});
|
|
9627
|
+
}
|
|
9628
|
+
|
|
9629
|
+
function copilotAppBaselineTotal(value) {
|
|
9630
|
+
if (!value || typeof value !== "object") return 0;
|
|
9631
|
+
return (
|
|
9632
|
+
Number(value.input || 0) +
|
|
9633
|
+
Number(value.output || 0) +
|
|
9634
|
+
Number(value.cached || 0) +
|
|
9635
|
+
Number(value.reasoning || 0)
|
|
9636
|
+
);
|
|
9637
|
+
}
|
|
9638
|
+
|
|
9639
|
+
function capCopilotAppSessionTotals(sessionTotals, maxEntries = 10_000) {
|
|
9640
|
+
const entries = Object.entries(sessionTotals);
|
|
9641
|
+
if (entries.length <= maxEntries) return sessionTotals;
|
|
9642
|
+
|
|
9643
|
+
const nonzero = [];
|
|
9644
|
+
const zero = [];
|
|
9645
|
+
for (const entry of entries) {
|
|
9646
|
+
if (copilotAppBaselineTotal(entry[1]) > 0) nonzero.push(entry);
|
|
9647
|
+
else zero.push(entry);
|
|
9648
|
+
}
|
|
9649
|
+
|
|
9650
|
+
zero.sort((a, b) => {
|
|
9651
|
+
const ta = Date.parse(a[1]?.updatedAt || "") || 0;
|
|
9652
|
+
const tb = Date.parse(b[1]?.updatedAt || "") || 0;
|
|
9653
|
+
return tb - ta;
|
|
9654
|
+
});
|
|
9655
|
+
const zeroBudget = Math.max(0, maxEntries - nonzero.length);
|
|
9656
|
+
const capped = Object.fromEntries([...nonzero, ...zero.slice(0, zeroBudget)]);
|
|
9657
|
+
for (const key of Object.keys(sessionTotals)) delete sessionTotals[key];
|
|
9658
|
+
Object.assign(sessionTotals, capped);
|
|
9659
|
+
return sessionTotals;
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9662
|
+
function normalizeCopilotAppTokenDelta(curr, prev) {
|
|
9663
|
+
const inputDelta = Math.max(0, Number(curr?.input || 0) - Number(prev?.input || 0));
|
|
9664
|
+
const cachedDelta = Math.max(0, Number(curr?.cached || 0) - Number(prev?.cached || 0));
|
|
9665
|
+
const cachedInputTokens = Math.min(cachedDelta, inputDelta);
|
|
9666
|
+
const inputTokens = Math.max(0, inputDelta - cachedInputTokens);
|
|
9667
|
+
const outputTokens = Math.max(0, Number(curr?.output || 0) - Number(prev?.output || 0));
|
|
9668
|
+
const reasoningTokens = Math.max(0, Number(curr?.reasoning || 0) - Number(prev?.reasoning || 0));
|
|
9669
|
+
const totalTokens = inputTokens + cachedInputTokens + outputTokens + reasoningTokens;
|
|
9670
|
+
return {
|
|
9671
|
+
input_tokens: inputTokens,
|
|
9672
|
+
cached_input_tokens: cachedInputTokens,
|
|
9673
|
+
cache_creation_input_tokens: 0,
|
|
9674
|
+
output_tokens: outputTokens,
|
|
9675
|
+
reasoning_output_tokens: reasoningTokens,
|
|
9676
|
+
total_tokens: totalTokens,
|
|
9677
|
+
conversation_count: copilotAppBaselineTotal(prev) === 0 && totalTokens > 0 ? 1 : 0,
|
|
9678
|
+
};
|
|
9679
|
+
}
|
|
9680
|
+
|
|
9681
|
+
async function parseCopilotAppDbIncremental({
|
|
9682
|
+
dbPath,
|
|
9683
|
+
dbPaths,
|
|
9684
|
+
cursors,
|
|
9685
|
+
queuePath,
|
|
9686
|
+
onProgress,
|
|
9687
|
+
env,
|
|
9688
|
+
sqliteOptions,
|
|
9689
|
+
} = {}) {
|
|
9690
|
+
await ensureDir(path.dirname(queuePath));
|
|
9691
|
+
const paths = Array.isArray(dbPaths) && dbPaths.length > 0
|
|
9692
|
+
? dbPaths
|
|
9693
|
+
: dbPath
|
|
9694
|
+
? [dbPath]
|
|
9695
|
+
: resolveCopilotAppDbPaths(env || process.env);
|
|
9696
|
+
const uniquePaths = [...new Set(paths.map((p) => normalizeCopilotAppDbPath(p, env || process.env)).filter(Boolean))];
|
|
9697
|
+
const appState =
|
|
9698
|
+
cursors.copilotApp && typeof cursors.copilotApp === "object" ? cursors.copilotApp : {};
|
|
9699
|
+
const dbStates =
|
|
9700
|
+
appState.dbs && typeof appState.dbs === "object" ? { ...appState.dbs } : {};
|
|
9701
|
+
const hourlyState = normalizeHourlyState(cursors?.hourly);
|
|
9702
|
+
const touchedBuckets = new Set();
|
|
9703
|
+
const cb = typeof onProgress === "function" ? onProgress : null;
|
|
9704
|
+
let recordsProcessed = 0;
|
|
9705
|
+
let eventsAggregated = 0;
|
|
9706
|
+
let totalRows = 0;
|
|
9707
|
+
let dbErrors = 0;
|
|
9708
|
+
const updatedAt = new Date().toISOString();
|
|
9709
|
+
|
|
9710
|
+
for (const resolvedDb of uniquePaths) {
|
|
9711
|
+
const dbState = dbStates[resolvedDb] && typeof dbStates[resolvedDb] === "object"
|
|
9712
|
+
? dbStates[resolvedDb]
|
|
9713
|
+
: {};
|
|
9714
|
+
const sessionTotals =
|
|
9715
|
+
dbState.sessionTotals && typeof dbState.sessionTotals === "object"
|
|
9716
|
+
? { ...dbState.sessionTotals }
|
|
9717
|
+
: {};
|
|
9718
|
+
let currentFingerprint = null;
|
|
9719
|
+
try {
|
|
9720
|
+
fssync.statSync(resolvedDb);
|
|
9721
|
+
currentFingerprint = sqliteSidecarFingerprint(resolvedDb);
|
|
9722
|
+
} catch (e) {
|
|
9723
|
+
if (e && e.code === "ENOENT") continue;
|
|
9724
|
+
dbErrors++;
|
|
9725
|
+
dbStates[resolvedDb] = {
|
|
9726
|
+
...dbState,
|
|
9727
|
+
sessionTotals,
|
|
9728
|
+
lastError: e && e.message ? e.message : String(e),
|
|
9729
|
+
lastErrorAt: updatedAt,
|
|
9730
|
+
};
|
|
9731
|
+
continue;
|
|
9732
|
+
}
|
|
9733
|
+
|
|
9734
|
+
if (dbState.lastDbFingerprint && sameSqliteFingerprint(currentFingerprint, dbState.lastDbFingerprint)) {
|
|
9735
|
+
dbStates[resolvedDb] = { ...dbState, sessionTotals, updatedAt };
|
|
9736
|
+
continue;
|
|
9737
|
+
}
|
|
9738
|
+
|
|
9739
|
+
let snap = null;
|
|
9740
|
+
let rows = [];
|
|
9741
|
+
try {
|
|
9742
|
+
snap = snapshotSqliteDb(resolvedDb);
|
|
9743
|
+
rows = readCopilotAppSessionsFromSqlite(snap.path, sqliteOptions);
|
|
9744
|
+
} catch (err) {
|
|
9745
|
+
dbErrors++;
|
|
9746
|
+
dbStates[resolvedDb] = {
|
|
9747
|
+
...dbState,
|
|
9748
|
+
sessionTotals,
|
|
9749
|
+
lastError: err && err.message ? err.message : String(err),
|
|
9750
|
+
lastErrorAt: updatedAt,
|
|
9751
|
+
};
|
|
9752
|
+
continue;
|
|
9753
|
+
} finally {
|
|
9754
|
+
if (snap) snap.cleanup();
|
|
9755
|
+
}
|
|
9756
|
+
totalRows += rows.length;
|
|
9757
|
+
|
|
9758
|
+
for (const row of rows) {
|
|
9759
|
+
recordsProcessed++;
|
|
9760
|
+
if (!row || typeof row.id !== "string" || !row.id.trim()) continue;
|
|
9761
|
+
if (!shouldCountCopilotAppSession(row)) continue;
|
|
9762
|
+
|
|
9763
|
+
const sessionId = row.id.trim();
|
|
9764
|
+
const curr = {
|
|
9765
|
+
input: toNonNegativeInt(row.total_input_tokens),
|
|
9766
|
+
output: toNonNegativeInt(row.total_output_tokens),
|
|
9767
|
+
cached: toNonNegativeInt(row.total_cached_tokens),
|
|
9768
|
+
reasoning: toNonNegativeInt(row.total_reasoning_tokens),
|
|
9769
|
+
};
|
|
9770
|
+
const prev = sessionTotals[sessionId] || { input: 0, output: 0, cached: 0, reasoning: 0 };
|
|
9771
|
+
const bucketDelta = normalizeCopilotAppTokenDelta(curr, prev);
|
|
9772
|
+
const totalDelta = bucketDelta.total_tokens;
|
|
9773
|
+
const rowUpdatedAt =
|
|
9774
|
+
parseCopilotAppTimestamp(row.updated_at) ||
|
|
9775
|
+
parseCopilotAppTimestamp(row.created_at) ||
|
|
9776
|
+
updatedAt;
|
|
9777
|
+
const model = normalizeCopilotAppModel(row.model) || COPILOT_APP_DEFAULT_MODEL;
|
|
9778
|
+
|
|
9779
|
+
if (totalDelta <= 0) {
|
|
9780
|
+
sessionTotals[sessionId] = { ...curr, model, updatedAt: rowUpdatedAt };
|
|
9781
|
+
continue;
|
|
9782
|
+
}
|
|
9783
|
+
|
|
9784
|
+
const bucketStart = toUtcHalfHourStart(rowUpdatedAt);
|
|
9785
|
+
if (!bucketStart) {
|
|
9786
|
+
sessionTotals[sessionId] = { ...curr, model, updatedAt: rowUpdatedAt };
|
|
9787
|
+
continue;
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9790
|
+
const bucket = getHourlyBucket(hourlyState, "copilot", model, bucketStart);
|
|
9791
|
+
addTotals(bucket.totals, bucketDelta);
|
|
9792
|
+
touchedBuckets.add(bucketKey("copilot", model, bucketStart));
|
|
9793
|
+
sessionTotals[sessionId] = { ...curr, model, updatedAt: rowUpdatedAt };
|
|
9794
|
+
eventsAggregated++;
|
|
9795
|
+
|
|
9796
|
+
if (cb) {
|
|
9797
|
+
cb({
|
|
9798
|
+
index: recordsProcessed,
|
|
9799
|
+
total: Math.max(totalRows, recordsProcessed),
|
|
9800
|
+
recordsProcessed,
|
|
9801
|
+
eventsAggregated,
|
|
9802
|
+
bucketsQueued: touchedBuckets.size,
|
|
9803
|
+
});
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
|
|
9807
|
+
capCopilotAppSessionTotals(sessionTotals);
|
|
9808
|
+
|
|
9809
|
+
dbStates[resolvedDb] = {
|
|
9810
|
+
...dbState,
|
|
9811
|
+
sessionTotals,
|
|
9812
|
+
lastDbFingerprint: currentFingerprint,
|
|
9813
|
+
lastError: null,
|
|
9814
|
+
lastErrorAt: null,
|
|
9815
|
+
updatedAt,
|
|
9816
|
+
};
|
|
9817
|
+
}
|
|
9818
|
+
|
|
9819
|
+
const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
|
|
9820
|
+
hourlyState.updatedAt = updatedAt;
|
|
9821
|
+
cursors.hourly = hourlyState;
|
|
9822
|
+
cursors.copilotApp = {
|
|
9823
|
+
...appState,
|
|
9824
|
+
version: COPILOT_APP_CURSOR_VERSION,
|
|
9825
|
+
dbs: dbStates,
|
|
9826
|
+
updatedAt,
|
|
9827
|
+
};
|
|
9828
|
+
|
|
9829
|
+
return { recordsProcessed, eventsAggregated, bucketsQueued, dbErrors };
|
|
9830
|
+
}
|
|
9831
|
+
|
|
9451
9832
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
9452
9833
|
// Grok Build (xAI) — passive reader for ~/.grok/sessions/**/updates.jsonl + signals.json
|
|
9453
9834
|
// Triggered either by full scan in sync or by the SessionEnd hook writing a signal.
|
|
@@ -10363,6 +10744,9 @@ module.exports = {
|
|
|
10363
10744
|
probeWslDistros,
|
|
10364
10745
|
discoverWslHermesHome,
|
|
10365
10746
|
resolveCopilotOtelPaths,
|
|
10747
|
+
resolveCopilotAppDbPath,
|
|
10748
|
+
resolveCopilotAppDbPaths,
|
|
10749
|
+
readCopilotAppSessionsFromSqlite,
|
|
10366
10750
|
parseRolloutIncremental,
|
|
10367
10751
|
parseClaudeIncremental,
|
|
10368
10752
|
parseGeminiIncremental,
|
|
@@ -10376,6 +10760,7 @@ module.exports = {
|
|
|
10376
10760
|
zedInstallOwnsCursor,
|
|
10377
10761
|
hermesInstallOwnsCursor,
|
|
10378
10762
|
parseCopilotIncremental,
|
|
10763
|
+
parseCopilotAppDbIncremental,
|
|
10379
10764
|
resolveKimiWireFiles,
|
|
10380
10765
|
resolveKimiDefaultModel,
|
|
10381
10766
|
parseKimiIncremental,
|
package/src/lib/sqlite-reader.js
CHANGED
|
@@ -30,8 +30,9 @@ function warnSqliteUnavailable({ dbPath, label, cliError, nodeSqliteError, env,
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function readSqliteRowsWithCli(dbPath, sql, { execFileSync, timeout, maxBuffer }) {
|
|
34
|
-
const
|
|
33
|
+
function readSqliteRowsWithCli(dbPath, sql, { execFileSync, timeout, maxBuffer, readOnly }) {
|
|
34
|
+
const args = readOnly ? ["-readonly", "-json", dbPath, sql] : ["-json", dbPath, sql];
|
|
35
|
+
const raw = execFileSync("sqlite3", args, {
|
|
35
36
|
encoding: "utf8",
|
|
36
37
|
maxBuffer,
|
|
37
38
|
timeout,
|
|
@@ -92,7 +93,7 @@ function readSqliteJsonRows(dbPath, sql, options = {}) {
|
|
|
92
93
|
|
|
93
94
|
let cliError = null;
|
|
94
95
|
try {
|
|
95
|
-
return readSqliteRowsWithCli(dbPath, sql, { execFileSync, timeout, maxBuffer });
|
|
96
|
+
return readSqliteRowsWithCli(dbPath, sql, { execFileSync, timeout, maxBuffer, readOnly: Boolean(options.readOnly) });
|
|
96
97
|
} catch (err) {
|
|
97
98
|
cliError = err;
|
|
98
99
|
}
|
|
@@ -114,11 +115,20 @@ function readSqliteJsonRows(dbPath, sql, options = {}) {
|
|
|
114
115
|
stderr: options.stderr,
|
|
115
116
|
});
|
|
116
117
|
}
|
|
118
|
+
if (options.throwOnReadFailure) {
|
|
119
|
+
const err = new Error(
|
|
120
|
+
`Cannot read ${label} SQLite database at ${dbPath}: ${formatError(cliError)}; ${formatError(nodeSqliteError)}`,
|
|
121
|
+
);
|
|
122
|
+
err.cliError = cliError;
|
|
123
|
+
err.nodeSqliteError = nodeSqliteError;
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
117
126
|
return [];
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
async function readSqliteRowsWithCliAsync(dbPath, sql, { execFile, timeout, maxBuffer }) {
|
|
121
|
-
const
|
|
129
|
+
async function readSqliteRowsWithCliAsync(dbPath, sql, { execFile, timeout, maxBuffer, readOnly }) {
|
|
130
|
+
const args = readOnly ? ["-readonly", "-json", dbPath, sql] : ["-json", dbPath, sql];
|
|
131
|
+
const { stdout } = await execFile("sqlite3", args, {
|
|
122
132
|
encoding: "utf8",
|
|
123
133
|
maxBuffer,
|
|
124
134
|
timeout,
|
|
@@ -149,7 +159,7 @@ async function readSqliteJsonRowsAsync(dbPath, sql, options = {}) {
|
|
|
149
159
|
|
|
150
160
|
let cliError = null;
|
|
151
161
|
try {
|
|
152
|
-
return await readSqliteRowsWithCliAsync(dbPath, sql, { execFile, timeout, maxBuffer });
|
|
162
|
+
return await readSqliteRowsWithCliAsync(dbPath, sql, { execFile, timeout, maxBuffer, readOnly: Boolean(options.readOnly) });
|
|
153
163
|
} catch (err) {
|
|
154
164
|
cliError = err;
|
|
155
165
|
}
|
|
@@ -171,6 +181,14 @@ async function readSqliteJsonRowsAsync(dbPath, sql, options = {}) {
|
|
|
171
181
|
stderr: options.stderr,
|
|
172
182
|
});
|
|
173
183
|
}
|
|
184
|
+
if (options.throwOnReadFailure) {
|
|
185
|
+
const err = new Error(
|
|
186
|
+
`Cannot read ${label} SQLite database at ${dbPath}: ${formatError(cliError)}; ${formatError(nodeSqliteError)}`,
|
|
187
|
+
);
|
|
188
|
+
err.cliError = cliError;
|
|
189
|
+
err.nodeSqliteError = nodeSqliteError;
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
174
192
|
return [];
|
|
175
193
|
}
|
|
176
194
|
|