@ipv9/tokentracker-cli 0.39.45 → 0.39.47
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/dashboard/dist/assets/{Card-2-TQg7P7.js → Card-BMWFZ5k5.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-yb9ss9uE.js → DashboardPage-HjWNRKq7.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BOc6XtOK.js → FadeIn-BgLVBi0I.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-C2tIb68P.js → IpCheckPage-CsS9rEbM.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BsuLQ9co.js → LimitsPage-BifpRDJ6.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-C8R9KLef.js → LocalOnlyNotice-D7tdrheS.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-BpfseoI7.js → PopoverPopup-DjMB9uLV.js} +1 -1
- package/dashboard/dist/assets/{Select-Ctk8zze5.js → Select-DeCgT4YH.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-BKZlFyFs.js → SelectItemText-DwZRVpPE.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-CQXM8qGU.js → SettingsPage-B1l6jasL.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-4EMm0eBX.js → SkillsPage-DP5bdeUE.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-C2sdX5g6.js → WidgetsPage-BVOQlozx.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-CLiuEcQZ.js → WrappedPage-ixZYG73C.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-BDkp93DX.js → arrow-up-right-D74FAyJC.js} +1 -1
- package/dashboard/dist/assets/{download-DZ6SoCSn.js → download-sKFNy5AQ.js} +1 -1
- package/dashboard/dist/assets/{format-CaW9kvsA.js → format-Bl1W1y2T.js} +1 -1
- package/dashboard/dist/assets/{limitDisplay-DNU_w4O7.js → limitDisplay-C2rw2OOb.js} +1 -1
- package/dashboard/dist/assets/{main-DgyymGht.js → main-NjvOmlYA.js} +3 -3
- package/dashboard/dist/assets/{mock-data-D6C7Fba3.js → mock-data-DJ2XGAUP.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-B7cHBa7Y.js → use-limits-display-prefs-C7BBDoNV.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-BKAzGuxw.js → use-native-settings-BYIme-b6.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-BVr6Ajuu.js → useCurrency-CJzAqnSI.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +62 -14
package/src/lib/rollout.js
CHANGED
|
@@ -3237,21 +3237,48 @@ function isUncPath(p) {
|
|
|
3237
3237
|
return typeof p === "string" && (p.startsWith("\\\\") || p.startsWith("//"));
|
|
3238
3238
|
}
|
|
3239
3239
|
|
|
3240
|
+
function sqliteDbChangeFingerprint(dbPath) {
|
|
3241
|
+
const parts = [];
|
|
3242
|
+
for (const suffix of ["", "-wal", "-shm", "-journal"]) {
|
|
3243
|
+
const filePath = dbPath + suffix;
|
|
3244
|
+
const label = suffix || "main";
|
|
3245
|
+
try {
|
|
3246
|
+
const stat = fssync.statSync(filePath, { bigint: true });
|
|
3247
|
+
parts.push(`${label}:${stat.size}:${stat.mtimeNs}`);
|
|
3248
|
+
} catch (error) {
|
|
3249
|
+
if (error && error.code === "ENOENT") {
|
|
3250
|
+
parts.push(`${label}:missing`);
|
|
3251
|
+
continue;
|
|
3252
|
+
}
|
|
3253
|
+
// A partial fingerprint must never authorize the no-change fast path.
|
|
3254
|
+
// Retry the read instead of treating repeated stat failures as stable.
|
|
3255
|
+
return null;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
return parts.join("|");
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3240
3261
|
function snapshotSqliteDb(dbPath) {
|
|
3241
3262
|
const tmpRoot = fssync.mkdtempSync(
|
|
3242
3263
|
path.join(require("node:os").tmpdir(), "tokentracker-hermes-snap-"),
|
|
3243
3264
|
);
|
|
3244
3265
|
const target = path.join(tmpRoot, path.basename(dbPath));
|
|
3245
3266
|
fssync.copyFileSync(dbPath, target);
|
|
3246
|
-
//
|
|
3267
|
+
// Copy SQLite sidecars as one logical snapshot. A missing sidecar is normal,
|
|
3268
|
+
// but any other copy failure makes the snapshot incomplete and must prevent
|
|
3269
|
+
// callers from advancing a change fingerprint based on stale main-DB data.
|
|
3270
|
+
let sidecarsComplete = true;
|
|
3247
3271
|
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
3248
3272
|
const src = dbPath + suffix;
|
|
3249
3273
|
try {
|
|
3250
|
-
|
|
3251
|
-
} catch (
|
|
3274
|
+
fssync.copyFileSync(src, target + suffix);
|
|
3275
|
+
} catch (error) {
|
|
3276
|
+
if (!error || error.code !== "ENOENT") sidecarsComplete = false;
|
|
3277
|
+
}
|
|
3252
3278
|
}
|
|
3253
3279
|
return {
|
|
3254
3280
|
path: target,
|
|
3281
|
+
sidecarsComplete,
|
|
3255
3282
|
cleanup() {
|
|
3256
3283
|
try { fssync.rmSync(tmpRoot, { recursive: true, force: true }); } catch (_e) { }
|
|
3257
3284
|
},
|
|
@@ -6116,8 +6143,10 @@ async function parseGooseIncremental({
|
|
|
6116
6143
|
onProgress,
|
|
6117
6144
|
env,
|
|
6118
6145
|
sqliteOptions,
|
|
6146
|
+
now = () => new Date(),
|
|
6119
6147
|
} = {}) {
|
|
6120
6148
|
await ensureDir(path.dirname(queuePath));
|
|
6149
|
+
const observedAt = now().toISOString();
|
|
6121
6150
|
const resolvedDb = dbPath || resolveGooseDbPath(env || process.env);
|
|
6122
6151
|
const gooseState =
|
|
6123
6152
|
cursors.goose && typeof cursors.goose === "object" ? cursors.goose : {};
|
|
@@ -6126,21 +6155,25 @@ async function parseGooseIncremental({
|
|
|
6126
6155
|
? { ...gooseState.sessionTotals }
|
|
6127
6156
|
: {};
|
|
6128
6157
|
|
|
6129
|
-
const
|
|
6158
|
+
const cursorDbFingerprint =
|
|
6159
|
+
typeof gooseState.lastDbFingerprint === "string" ? gooseState.lastDbFingerprint : null;
|
|
6130
6160
|
let currentMtime = 0;
|
|
6131
6161
|
try {
|
|
6132
6162
|
currentMtime = fssync.statSync(resolvedDb).mtimeMs;
|
|
6133
6163
|
} catch (e) {
|
|
6134
6164
|
if (e && e.code === "ENOENT") {
|
|
6135
|
-
cursors.goose = { ...gooseState, sessionTotals, updatedAt:
|
|
6165
|
+
cursors.goose = { ...gooseState, sessionTotals, updatedAt: observedAt };
|
|
6136
6166
|
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6137
6167
|
}
|
|
6138
6168
|
throw e;
|
|
6139
6169
|
}
|
|
6140
|
-
|
|
6141
|
-
//
|
|
6142
|
-
|
|
6143
|
-
|
|
6170
|
+
const currentDbFingerprint = sqliteDbChangeFingerprint(resolvedDb);
|
|
6171
|
+
// Goose commonly commits active-session writes only to SQLite's WAL. Skip
|
|
6172
|
+
// the table scan only when the main DB and every sidecar are unchanged.
|
|
6173
|
+
// Legacy cursors without a fingerprint intentionally perform one read to
|
|
6174
|
+
// establish the sidecar-aware baseline.
|
|
6175
|
+
if (cursorDbFingerprint && currentDbFingerprint === cursorDbFingerprint) {
|
|
6176
|
+
cursors.goose = { ...gooseState, sessionTotals, updatedAt: observedAt };
|
|
6144
6177
|
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6145
6178
|
}
|
|
6146
6179
|
|
|
@@ -6149,13 +6182,22 @@ async function parseGooseIncremental({
|
|
|
6149
6182
|
const snap = snapshotSqliteDb(resolvedDb);
|
|
6150
6183
|
let rows = [];
|
|
6151
6184
|
try {
|
|
6185
|
+
if (!snap.sidecarsComplete) {
|
|
6186
|
+
cursors.goose = {
|
|
6187
|
+
...gooseState,
|
|
6188
|
+
sessionTotals,
|
|
6189
|
+
lastDbFingerprint: null,
|
|
6190
|
+
updatedAt: observedAt,
|
|
6191
|
+
};
|
|
6192
|
+
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6193
|
+
}
|
|
6152
6194
|
rows = readGooseSessionsFromSqlite(snap.path, sqliteOptions);
|
|
6153
6195
|
} finally {
|
|
6154
6196
|
snap.cleanup();
|
|
6155
6197
|
}
|
|
6156
6198
|
|
|
6157
6199
|
if (rows.length === 0) {
|
|
6158
|
-
cursors.goose = { ...gooseState, sessionTotals, updatedAt:
|
|
6200
|
+
cursors.goose = { ...gooseState, sessionTotals, updatedAt: observedAt };
|
|
6159
6201
|
return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
6160
6202
|
}
|
|
6161
6203
|
|
|
@@ -6188,6 +6230,7 @@ async function parseGooseIncremental({
|
|
|
6188
6230
|
);
|
|
6189
6231
|
if (totalNow === 0 && inputNow === 0 && outputNow === 0) continue;
|
|
6190
6232
|
|
|
6233
|
+
const hadPreviousSnapshot = Object.prototype.hasOwnProperty.call(sessionTotals, row.id);
|
|
6191
6234
|
const prev = sessionTotals[row.id] || { input: 0, output: 0, total: 0 };
|
|
6192
6235
|
// Goose can wipe a session and re-create with the same id during
|
|
6193
6236
|
// database migration. Treat shrinking cumulative as a reset and emit
|
|
@@ -6213,7 +6256,12 @@ async function parseGooseIncremental({
|
|
|
6213
6256
|
const accountedDelta = dInput + dOutput;
|
|
6214
6257
|
const reasoningDelta = Math.max(0, dTotal - accountedDelta);
|
|
6215
6258
|
|
|
6216
|
-
|
|
6259
|
+
// The first observation has only created_at as a trustworthy timestamp.
|
|
6260
|
+
// Later cumulative deltas belong to this sync's observation bucket so a
|
|
6261
|
+
// long-lived session does not keep growing the day on which it started.
|
|
6262
|
+
const tsIso = hadPreviousSnapshot
|
|
6263
|
+
? observedAt
|
|
6264
|
+
: parseGooseCreatedAt(row.created_at) || observedAt;
|
|
6217
6265
|
const bucketStart = toUtcHalfHourStart(tsIso);
|
|
6218
6266
|
if (!bucketStart) continue;
|
|
6219
6267
|
|
|
@@ -6257,14 +6305,14 @@ async function parseGooseIncremental({
|
|
|
6257
6305
|
}
|
|
6258
6306
|
|
|
6259
6307
|
const bucketsQueued = await enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets });
|
|
6260
|
-
|
|
6261
|
-
hourlyState.updatedAt = updatedAt;
|
|
6308
|
+
hourlyState.updatedAt = observedAt;
|
|
6262
6309
|
cursors.hourly = hourlyState;
|
|
6263
6310
|
cursors.goose = {
|
|
6264
6311
|
...gooseState,
|
|
6265
6312
|
sessionTotals,
|
|
6266
6313
|
lastDbMtimeMs: currentMtime,
|
|
6267
|
-
|
|
6314
|
+
lastDbFingerprint: currentDbFingerprint,
|
|
6315
|
+
updatedAt: observedAt,
|
|
6268
6316
|
};
|
|
6269
6317
|
|
|
6270
6318
|
return { recordsProcessed, eventsAggregated, bucketsQueued };
|