exe-watcher 0.2.66 → 0.2.68
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 +6 -4
- package/dist/cli.js +675 -377
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2125,12 +2125,12 @@ function getPricingWarnings() {
|
|
|
2125
2125
|
}
|
|
2126
2126
|
function getPricingMetadata() {
|
|
2127
2127
|
return {
|
|
2128
|
-
basis: "
|
|
2128
|
+
basis: "recorded-rates",
|
|
2129
2129
|
currency: "USD",
|
|
2130
2130
|
refreshedAt: _pricingLastUpdated ? new Date(_pricingLastUpdated).toISOString() : null,
|
|
2131
2131
|
fallbackVerifiedOn: verified_pricing_default.verifiedOn,
|
|
2132
2132
|
sources: OFFICIAL_PRICING_SOURCES,
|
|
2133
|
-
note: "
|
|
2133
|
+
note: "Counted usage keeps its recorded API-equivalent cost. Latest available rates apply to newly observed usage; price refreshes do not revalue saved history. This is an estimate, not a reconstruction of invoices."
|
|
2134
2134
|
};
|
|
2135
2135
|
}
|
|
2136
2136
|
function getCachePath() {
|
|
@@ -2690,12 +2690,223 @@ var init_discovery_cache = __esm({
|
|
|
2690
2690
|
}
|
|
2691
2691
|
});
|
|
2692
2692
|
|
|
2693
|
+
// src/sqlite.ts
|
|
2694
|
+
import { createRequire } from "module";
|
|
2695
|
+
function loadDriver() {
|
|
2696
|
+
if (loadAttempted) return DatabaseSync !== null;
|
|
2697
|
+
loadAttempted = true;
|
|
2698
|
+
const origEmit = process.emit.bind(process);
|
|
2699
|
+
let restored = false;
|
|
2700
|
+
const restore = () => {
|
|
2701
|
+
if (restored) return;
|
|
2702
|
+
restored = true;
|
|
2703
|
+
process.emit = origEmit;
|
|
2704
|
+
};
|
|
2705
|
+
process.emit = function patchedEmit(event, ...args) {
|
|
2706
|
+
if (event === "warning") {
|
|
2707
|
+
const warning = args[0];
|
|
2708
|
+
if (warning?.name === "ExperimentalWarning" && typeof warning.message === "string" && /SQLite/i.test(warning.message)) {
|
|
2709
|
+
return false;
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return origEmit.call(this, event, ...args);
|
|
2713
|
+
};
|
|
2714
|
+
try {
|
|
2715
|
+
const mod = requireForSqlite("node:sqlite");
|
|
2716
|
+
DatabaseSync = mod.DatabaseSync;
|
|
2717
|
+
return true;
|
|
2718
|
+
} catch (err) {
|
|
2719
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2720
|
+
loadError = `Watcher saved costs and SQLite-based providers need Node 22.13+ (Node 23 requires 23.4+) with node:sqlite.
|
|
2721
|
+
Current Node: ${process.version}.
|
|
2722
|
+
Upgrade Node (https://nodejs.org) and run exe-watcher again.
|
|
2723
|
+
(underlying error: ${message})`;
|
|
2724
|
+
return false;
|
|
2725
|
+
} finally {
|
|
2726
|
+
setTimeout(restore, 0);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
function isSqliteAvailable() {
|
|
2730
|
+
return loadDriver();
|
|
2731
|
+
}
|
|
2732
|
+
function getSqliteLoadError() {
|
|
2733
|
+
return loadError ?? "SQLite driver not available";
|
|
2734
|
+
}
|
|
2735
|
+
function openDatabase(path) {
|
|
2736
|
+
if (!loadDriver() || DatabaseSync === null) {
|
|
2737
|
+
throw new Error(getSqliteLoadError());
|
|
2738
|
+
}
|
|
2739
|
+
const db = new DatabaseSync(path, { readOnly: true });
|
|
2740
|
+
return {
|
|
2741
|
+
query(sql, params = []) {
|
|
2742
|
+
const rows2 = [];
|
|
2743
|
+
let bytes = 0;
|
|
2744
|
+
for (const row of db.prepare(sql).iterate(...params)) {
|
|
2745
|
+
const size = Buffer.byteLength(JSON.stringify(row));
|
|
2746
|
+
bytes += size;
|
|
2747
|
+
if (rows2.length >= 1e5 || bytes > 32 * 1024 * 1024) throw new ResourceBudgetError("SQLite result budget");
|
|
2748
|
+
chargeRead(size);
|
|
2749
|
+
rows2.push(row);
|
|
2750
|
+
}
|
|
2751
|
+
return rows2;
|
|
2752
|
+
},
|
|
2753
|
+
close() {
|
|
2754
|
+
db.close();
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
var requireForSqlite, DatabaseSync, loadAttempted, loadError;
|
|
2759
|
+
var init_sqlite = __esm({
|
|
2760
|
+
"src/sqlite.ts"() {
|
|
2761
|
+
"use strict";
|
|
2762
|
+
init_resource_budget();
|
|
2763
|
+
requireForSqlite = createRequire(import.meta.url);
|
|
2764
|
+
DatabaseSync = null;
|
|
2765
|
+
loadAttempted = false;
|
|
2766
|
+
loadError = null;
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
|
|
2770
|
+
// src/recorded-costs.ts
|
|
2771
|
+
import { createHash as createHash2 } from "crypto";
|
|
2772
|
+
import { createRequire as createRequire2 } from "module";
|
|
2773
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync } from "fs";
|
|
2774
|
+
import { join as join9 } from "path";
|
|
2775
|
+
function database() {
|
|
2776
|
+
const directory = getCacheDir();
|
|
2777
|
+
const path = join9(directory, "recorded-costs.sqlite");
|
|
2778
|
+
if (opened?.path === path && existsSync(path)) return opened.db;
|
|
2779
|
+
opened?.db.close();
|
|
2780
|
+
opened = void 0;
|
|
2781
|
+
if (!isSqliteAvailable()) throw new Error(getSqliteLoadError());
|
|
2782
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2783
|
+
if (existsSync(path) && !lstatSync(path).isFile()) throw new Error("Watcher pricing ledger must be a regular file");
|
|
2784
|
+
const { DatabaseSync: DatabaseSync2 } = require2("node:sqlite");
|
|
2785
|
+
const db = new DatabaseSync2(path);
|
|
2786
|
+
chmodSync(path, 384);
|
|
2787
|
+
db.exec("PRAGMA busy_timeout=1000; PRAGMA cache_size=-1024; PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;");
|
|
2788
|
+
const pageSize = Number(db.prepare("PRAGMA page_size").get().page_size);
|
|
2789
|
+
db.exec(`PRAGMA max_page_count=${Math.floor(512 * 1024 * 1024 / pageSize)}`);
|
|
2790
|
+
db.exec("CREATE TABLE IF NOT EXISTS costs (id BLOB PRIMARY KEY, usd REAL NOT NULL CHECK(usd >= 0)) WITHOUT ROWID");
|
|
2791
|
+
opened = { path, db };
|
|
2792
|
+
return db;
|
|
2793
|
+
}
|
|
2794
|
+
function costRow(value) {
|
|
2795
|
+
if (!value || typeof value !== "object") return null;
|
|
2796
|
+
const record = value;
|
|
2797
|
+
const { provider, model, deduplicationKey, costUSD } = record;
|
|
2798
|
+
if (typeof provider !== "string" || typeof model !== "string" || typeof deduplicationKey !== "string" || !deduplicationKey || typeof costUSD !== "number" || !Number.isFinite(costUSD) || costUSD < 0) return null;
|
|
2799
|
+
if (costUSD === 0 && !getModelCosts(model)) return "unpriced";
|
|
2800
|
+
const usage = record.usage ?? record;
|
|
2801
|
+
const fields2 = ["inputTokens", "outputTokens", "cacheCreationInputTokens", "cacheReadInputTokens", "cacheCreation1hInputTokens", "webSearchRequests"];
|
|
2802
|
+
const tokens = fields2.map((field) => usage[field] ?? 0);
|
|
2803
|
+
if (!tokens.every((value2) => typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0)) return null;
|
|
2804
|
+
const id = createHash2("sha256").update(JSON.stringify([provider, deduplicationKey, model, record.speed ?? "standard", ...tokens])).digest();
|
|
2805
|
+
return { record, id };
|
|
2806
|
+
}
|
|
2807
|
+
function lockRecordedCosts(data) {
|
|
2808
|
+
let db;
|
|
2809
|
+
let batch = [];
|
|
2810
|
+
let hasUnpricedUsage = false;
|
|
2811
|
+
const flush = () => {
|
|
2812
|
+
if (!batch.length) return;
|
|
2813
|
+
chargeRead(0);
|
|
2814
|
+
db ??= database();
|
|
2815
|
+
const placeholders = batch.map(() => "?").join(",");
|
|
2816
|
+
const query = db.prepare(`SELECT id, usd FROM costs WHERE id IN (${placeholders})`);
|
|
2817
|
+
const read = () => new Map(query.all(...batch.map((row) => row.id)).map((row) => {
|
|
2818
|
+
const cost = Number(row.usd);
|
|
2819
|
+
if (!Number.isFinite(cost) || cost < 0) throw new Error("Watcher pricing ledger contains an invalid recorded cost");
|
|
2820
|
+
return [Buffer.from(row.id).toString("hex"), cost];
|
|
2821
|
+
}));
|
|
2822
|
+
let known = read();
|
|
2823
|
+
const missing = batch.filter((row) => !known.has(row.id.toString("hex")));
|
|
2824
|
+
if (missing.length) {
|
|
2825
|
+
db.exec("BEGIN IMMEDIATE");
|
|
2826
|
+
try {
|
|
2827
|
+
const insert = db.prepare("INSERT OR IGNORE INTO costs (id, usd) VALUES (?, ?)");
|
|
2828
|
+
for (const row of missing) insert.run(row.id, row.record.costUSD);
|
|
2829
|
+
db.exec("COMMIT");
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
db.exec("ROLLBACK");
|
|
2832
|
+
throw error;
|
|
2833
|
+
}
|
|
2834
|
+
known = read();
|
|
2835
|
+
}
|
|
2836
|
+
chargeRead(known.size * 40);
|
|
2837
|
+
for (const row of batch) row.record.costUSD = known.get(row.id.toString("hex"));
|
|
2838
|
+
batch = [];
|
|
2839
|
+
};
|
|
2840
|
+
const add2 = (value) => {
|
|
2841
|
+
const row = costRow(value);
|
|
2842
|
+
if (row === "unpriced") hasUnpricedUsage = true;
|
|
2843
|
+
else if (row) batch.push(row);
|
|
2844
|
+
if (batch.length === BATCH) flush();
|
|
2845
|
+
};
|
|
2846
|
+
for (const entry of data) {
|
|
2847
|
+
if (entry && typeof entry === "object" && Array.isArray(entry.assistantCalls)) {
|
|
2848
|
+
for (const call of entry.assistantCalls) add2(call);
|
|
2849
|
+
} else add2(entry);
|
|
2850
|
+
}
|
|
2851
|
+
flush();
|
|
2852
|
+
return hasUnpricedUsage;
|
|
2853
|
+
}
|
|
2854
|
+
var require2, BATCH, opened;
|
|
2855
|
+
var init_recorded_costs = __esm({
|
|
2856
|
+
"src/recorded-costs.ts"() {
|
|
2857
|
+
"use strict";
|
|
2858
|
+
init_cache_dir();
|
|
2859
|
+
init_models();
|
|
2860
|
+
init_resource_budget();
|
|
2861
|
+
init_sqlite();
|
|
2862
|
+
require2 = createRequire2(import.meta.url);
|
|
2863
|
+
BATCH = 128;
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
|
|
2693
2867
|
// src/parsed-file-cache.ts
|
|
2694
2868
|
import { gzip, gunzip } from "zlib";
|
|
2695
2869
|
import { promisify } from "util";
|
|
2696
|
-
import { createHash as
|
|
2870
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
2697
2871
|
import { mkdir as mkdir8, readFile as readFile7, stat as stat5, writeFile as writeFile3, rename as rename6, unlink as unlink5, readdir as readdir3 } from "fs/promises";
|
|
2698
|
-
import { join as
|
|
2872
|
+
import { join as join10 } from "path";
|
|
2873
|
+
function encodeEntry(value) {
|
|
2874
|
+
let bytes = 0;
|
|
2875
|
+
try {
|
|
2876
|
+
return JSON.stringify(value, (key2, item) => {
|
|
2877
|
+
bytes += key2 ? Buffer.byteLength(JSON.stringify(key2)) + 2 : 0;
|
|
2878
|
+
bytes += item && typeof item === "object" ? 2 : Buffer.byteLength(JSON.stringify(item) ?? "null");
|
|
2879
|
+
if (bytes > MAX_ENTRY) throw new Error("parsed entry limit");
|
|
2880
|
+
return item;
|
|
2881
|
+
});
|
|
2882
|
+
} catch {
|
|
2883
|
+
return void 0;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
function fileProgress(path, dir, cachePath, before) {
|
|
2887
|
+
let startingOffset = 0;
|
|
2888
|
+
return {
|
|
2889
|
+
async load(valid) {
|
|
2890
|
+
if (!before) return;
|
|
2891
|
+
try {
|
|
2892
|
+
const size = (await stat5(cachePath)).size;
|
|
2893
|
+
if (size > MAX_ENTRY) return;
|
|
2894
|
+
chargeRead(size);
|
|
2895
|
+
const saved = JSON.parse((await decompress(await readFile7(cachePath), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
|
|
2896
|
+
if (saved.v !== VERSION2 || saved.fingerprint !== before || !Number.isSafeInteger(saved.offset) || saved.offset <= 0 || saved.offset > Number(before.split(":")[2]) || !valid(saved.state)) return;
|
|
2897
|
+
startingOffset = saved.offset;
|
|
2898
|
+
return { offset: saved.offset, state: saved.state };
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
if (error instanceof ResourceBudgetError) throw error;
|
|
2901
|
+
}
|
|
2902
|
+
},
|
|
2903
|
+
async save(state, offset) {
|
|
2904
|
+
if (!before || offset <= startingOffset || offset > Number(before.split(":")[2]) || await fingerprint(path) !== before) return;
|
|
2905
|
+
const payload = encodeEntry({ v: VERSION2, fingerprint: before, offset, state });
|
|
2906
|
+
if (payload) await storeParsedEntry(dir, cachePath, payload);
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2699
2910
|
async function fingerprint(path) {
|
|
2700
2911
|
try {
|
|
2701
2912
|
const s = await stat5(path);
|
|
@@ -2706,12 +2917,15 @@ async function fingerprint(path) {
|
|
|
2706
2917
|
}
|
|
2707
2918
|
}
|
|
2708
2919
|
async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
2709
|
-
const dir =
|
|
2710
|
-
const key2 =
|
|
2711
|
-
const cachePath =
|
|
2920
|
+
const dir = join10(getCacheDir(), "parsed-files");
|
|
2921
|
+
const key2 = createHash3("sha256").update(`${VERSION2}:${kind}:${path}`).digest("hex");
|
|
2922
|
+
const cachePath = join10(dir, `v${VERSION2}-${key2}.json.gz`);
|
|
2923
|
+
const progressPath = join10(dir, `v${VERSION2}-progress-${key2}.json.gz`);
|
|
2924
|
+
const legacyKey = createHash3("sha256").update(`3:${kind}:${path}:${getPricingFingerprint()}`).digest("hex");
|
|
2925
|
+
const legacyPath = join10(dir, `v3-${legacyKey}.json.gz`);
|
|
2712
2926
|
const fingerprintPath = kind === "opencode" ? path.slice(0, path.lastIndexOf(":")) : path;
|
|
2713
2927
|
const before = await fingerprint(fingerprintPath);
|
|
2714
|
-
const rangeKind = `usage-range:${
|
|
2928
|
+
const rangeKind = `usage-range:3:${kind}`;
|
|
2715
2929
|
const bounds = range && before ? await readDiscoveryFact(rangeKind, path, before) : void 0;
|
|
2716
2930
|
if (range && bounds && (bounds.last < range.start.getTime() || bounds.first > range.end.getTime())) {
|
|
2717
2931
|
if (Array.isArray(bounds.warnings)) warnings?.push(...bounds.warnings);
|
|
@@ -2729,32 +2943,52 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2729
2943
|
}
|
|
2730
2944
|
await rememberDiscoveryFact(rangeKind, path, before, { first: data2.length ? first : 0, last: data2.length ? last : -1, warnings });
|
|
2731
2945
|
};
|
|
2946
|
+
let migrated;
|
|
2732
2947
|
if (before) {
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
if (
|
|
2740
|
-
|
|
2741
|
-
|
|
2948
|
+
for (const candidate of [cachePath, legacyPath]) {
|
|
2949
|
+
try {
|
|
2950
|
+
const s = await stat5(candidate);
|
|
2951
|
+
if (s.size <= MAX_ENTRY) {
|
|
2952
|
+
chargeRead(s.size);
|
|
2953
|
+
const saved = JSON.parse((await decompress(await readFile7(candidate), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
|
|
2954
|
+
if ((saved.v === VERSION2 || saved.v === 3) && saved.fingerprint === before && Array.isArray(saved.data)) {
|
|
2955
|
+
if (Array.isArray(saved.warnings)) warnings?.push(...saved.warnings.filter((w) => typeof w === "string"));
|
|
2956
|
+
await rememberRange(saved.data);
|
|
2957
|
+
if (saved.v === VERSION2 && saved.pricesLocked === true) {
|
|
2958
|
+
if (!saved.hasUnpricedUsage || saved.pricingFingerprint === getPricingFingerprint()) return saved.data;
|
|
2959
|
+
continue;
|
|
2960
|
+
}
|
|
2961
|
+
migrated = saved.data;
|
|
2962
|
+
break;
|
|
2963
|
+
}
|
|
2742
2964
|
}
|
|
2965
|
+
} catch (err) {
|
|
2966
|
+
if (err instanceof ResourceBudgetError) throw err;
|
|
2743
2967
|
}
|
|
2744
|
-
} catch (err) {
|
|
2745
|
-
if (err instanceof ResourceBudgetError) throw err;
|
|
2746
2968
|
}
|
|
2747
2969
|
}
|
|
2748
|
-
const data = await parse();
|
|
2970
|
+
const data = migrated ?? await parse(fileProgress(fingerprintPath, dir, progressPath, before));
|
|
2971
|
+
const hasUnpricedUsage = lockRecordedCosts(data);
|
|
2749
2972
|
if (!before || await fingerprint(fingerprintPath) !== before) return data;
|
|
2750
2973
|
await rememberRange(data);
|
|
2751
|
-
const payload =
|
|
2752
|
-
|
|
2974
|
+
const payload = encodeEntry({
|
|
2975
|
+
v: VERSION2,
|
|
2976
|
+
fingerprint: before,
|
|
2977
|
+
data,
|
|
2978
|
+
warnings,
|
|
2979
|
+
pricesLocked: true,
|
|
2980
|
+
hasUnpricedUsage,
|
|
2981
|
+
pricingFingerprint: getPricingFingerprint()
|
|
2982
|
+
});
|
|
2983
|
+
if (payload) await storeParsedEntry(dir, cachePath, payload, progressPath);
|
|
2984
|
+
return data;
|
|
2985
|
+
}
|
|
2986
|
+
async function storeParsedEntry(dir, cachePath, payload, obsolete) {
|
|
2753
2987
|
await mkdir8(dir, { recursive: true });
|
|
2754
2988
|
const encoded = await compress(Buffer.from(payload));
|
|
2755
|
-
const release = await acquireFileLock(
|
|
2989
|
+
const release = await acquireFileLock(join10(dir, "writes.lock"));
|
|
2756
2990
|
try {
|
|
2757
|
-
const quotaPath =
|
|
2991
|
+
const quotaPath = join10(dir, "quota");
|
|
2758
2992
|
const quota = await readFile7(quotaPath, "utf8").catch(() => "");
|
|
2759
2993
|
let total = /^\d+$/.test(quota) ? Number(quota) : NaN;
|
|
2760
2994
|
const writeQuota = async (bytes) => {
|
|
@@ -2772,12 +3006,12 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2772
3006
|
const rows2 = [];
|
|
2773
3007
|
for (const name of files) {
|
|
2774
3008
|
if (!name.endsWith(".json") && !name.endsWith(".json.gz") && !name.endsWith(".tmp")) continue;
|
|
2775
|
-
if (!name.startsWith(`v${VERSION2}-`)) {
|
|
2776
|
-
await unlink5(
|
|
3009
|
+
if (!name.startsWith(`v${VERSION2}-`) && !name.startsWith("v3-")) {
|
|
3010
|
+
await unlink5(join10(dir, name)).catch(() => {
|
|
2777
3011
|
});
|
|
2778
3012
|
continue;
|
|
2779
3013
|
}
|
|
2780
|
-
const p =
|
|
3014
|
+
const p = join10(dir, name), s = await stat5(p).catch(() => null);
|
|
2781
3015
|
if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
2782
3016
|
}
|
|
2783
3017
|
total = rows2.reduce((n, r) => n + r.size, 0);
|
|
@@ -2792,12 +3026,12 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2792
3026
|
if (!(await readFile7(cachePath).catch(() => Buffer.alloc(0))).equals(encoded)) {
|
|
2793
3027
|
const oldSize = (await stat5(cachePath).catch(() => null))?.size ?? 0;
|
|
2794
3028
|
let nextSize = total + encoded.length;
|
|
2795
|
-
if (!Number.isFinite(nextSize)) return
|
|
3029
|
+
if (!Number.isFinite(nextSize)) return;
|
|
2796
3030
|
if (nextSize > MAX_DISK) {
|
|
2797
3031
|
const rows2 = [];
|
|
2798
3032
|
for (const name of await readdir3(dir)) {
|
|
2799
3033
|
if (!name.endsWith(".json.gz")) continue;
|
|
2800
|
-
const p =
|
|
3034
|
+
const p = join10(dir, name);
|
|
2801
3035
|
if (p === cachePath) continue;
|
|
2802
3036
|
const s = await stat5(p).catch(() => null);
|
|
2803
3037
|
if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
@@ -2808,25 +3042,33 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2808
3042
|
});
|
|
2809
3043
|
nextSize -= row.size;
|
|
2810
3044
|
}
|
|
2811
|
-
if (nextSize > MAX_DISK) return
|
|
3045
|
+
if (nextSize > MAX_DISK) return;
|
|
2812
3046
|
}
|
|
2813
3047
|
const tmp = `${cachePath}.${randomUUID2()}.tmp`;
|
|
2814
3048
|
try {
|
|
2815
3049
|
await writeQuota(nextSize);
|
|
2816
3050
|
await writeFile3(tmp, encoded, { mode: 384 });
|
|
2817
3051
|
await rename6(tmp, cachePath);
|
|
3052
|
+
mutationCount++;
|
|
2818
3053
|
if (oldSize) await writeQuota(nextSize - oldSize);
|
|
3054
|
+
total = nextSize - oldSize;
|
|
2819
3055
|
} finally {
|
|
2820
3056
|
await unlink5(tmp).catch(() => {
|
|
2821
3057
|
});
|
|
2822
3058
|
}
|
|
2823
3059
|
}
|
|
3060
|
+
if (obsolete) {
|
|
3061
|
+
const size = (await stat5(obsolete).catch(() => null))?.size ?? 0;
|
|
3062
|
+
if (size) {
|
|
3063
|
+
await unlink5(obsolete);
|
|
3064
|
+
await writeQuota(Math.max(0, total - size));
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
2824
3067
|
} finally {
|
|
2825
3068
|
await release();
|
|
2826
3069
|
}
|
|
2827
|
-
return data;
|
|
2828
3070
|
}
|
|
2829
|
-
var VERSION2, compress, decompress, MAX_ENTRY, MAX_DISK;
|
|
3071
|
+
var VERSION2, compress, decompress, MAX_ENTRY, MAX_DISK, mutationCount, parsedFileProgress;
|
|
2830
3072
|
var init_parsed_file_cache = __esm({
|
|
2831
3073
|
"src/parsed-file-cache.ts"() {
|
|
2832
3074
|
"use strict";
|
|
@@ -2835,11 +3077,14 @@ var init_parsed_file_cache = __esm({
|
|
|
2835
3077
|
init_resource_budget();
|
|
2836
3078
|
init_file_lock();
|
|
2837
3079
|
init_discovery_cache();
|
|
2838
|
-
|
|
3080
|
+
init_recorded_costs();
|
|
3081
|
+
VERSION2 = 4;
|
|
2839
3082
|
compress = promisify(gzip);
|
|
2840
3083
|
decompress = promisify(gunzip);
|
|
2841
3084
|
MAX_ENTRY = 16 * 1024 * 1024;
|
|
2842
3085
|
MAX_DISK = 256 * 1024 * 1024;
|
|
3086
|
+
mutationCount = 0;
|
|
3087
|
+
parsedFileProgress = () => mutationCount;
|
|
2843
3088
|
}
|
|
2844
3089
|
});
|
|
2845
3090
|
|
|
@@ -2948,23 +3193,22 @@ function readSessionFileSync(filePath) {
|
|
|
2948
3193
|
closeSync(fd);
|
|
2949
3194
|
}
|
|
2950
3195
|
}
|
|
2951
|
-
async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord) {
|
|
3196
|
+
async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord, completedLine) {
|
|
2952
3197
|
const handle = await open5(filePath, "r").catch(() => null);
|
|
2953
3198
|
if (!handle) return;
|
|
2954
3199
|
try {
|
|
2955
3200
|
const size = (await handle.stat()).size;
|
|
2956
3201
|
if (size <= startByte) return;
|
|
2957
|
-
|
|
2958
|
-
const stream = handle.createReadStream({ encoding: "utf-8", start: startByte, end: size - 1, autoClose: false });
|
|
3202
|
+
const stream = handle.createReadStream({ start: startByte, end: size - 1, autoClose: false });
|
|
2959
3203
|
let pieces = [];
|
|
2960
3204
|
let lineBytes = 0;
|
|
2961
3205
|
let discarding = false;
|
|
2962
3206
|
const maxLineBytes = 32 * 1024 * 1024;
|
|
2963
3207
|
const append = (piece) => {
|
|
2964
3208
|
if (discarding) return;
|
|
2965
|
-
lineBytes +=
|
|
3209
|
+
lineBytes += piece.length;
|
|
2966
3210
|
if (lineBytes > maxLineBytes) {
|
|
2967
|
-
const prefix = (pieces.slice(0, 4)
|
|
3211
|
+
const prefix = Buffer.concat([...pieces.slice(0, 4), piece.subarray(0, 1024)]).toString("utf8").slice(0, 1024);
|
|
2968
3212
|
if (!skipOversizedRecord?.(prefix)) throw new ResourceBudgetError(`32 MiB JSONL record budget in ${filePath}`);
|
|
2969
3213
|
discarding = true;
|
|
2970
3214
|
pieces = [];
|
|
@@ -2972,23 +3216,31 @@ async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord) {
|
|
|
2972
3216
|
}
|
|
2973
3217
|
pieces.push(piece);
|
|
2974
3218
|
};
|
|
3219
|
+
let readThrough = startByte;
|
|
2975
3220
|
try {
|
|
2976
3221
|
for await (const chunk of stream) {
|
|
3222
|
+
chargeRead(chunk.length);
|
|
3223
|
+
const chunkStart = readThrough;
|
|
3224
|
+
readThrough += chunk.length;
|
|
2977
3225
|
let start = 0;
|
|
2978
3226
|
let newline;
|
|
2979
|
-
while ((newline = chunk.indexOf(
|
|
2980
|
-
append(chunk.
|
|
3227
|
+
while ((newline = chunk.indexOf(10, start)) >= 0) {
|
|
3228
|
+
append(chunk.subarray(start, newline));
|
|
2981
3229
|
chargeRead(0);
|
|
2982
|
-
const line = discarding ? null : pieces.
|
|
3230
|
+
const line = discarding ? null : (pieces.length === 1 ? pieces[0] : Buffer.concat(pieces)).toString("utf8").replace(/\r$/, "");
|
|
2983
3231
|
pieces = [];
|
|
2984
3232
|
lineBytes = 0;
|
|
2985
3233
|
discarding = false;
|
|
2986
3234
|
if (line !== null) yield line;
|
|
3235
|
+
completedLine?.(chunkStart + newline + 1);
|
|
2987
3236
|
start = newline + 1;
|
|
2988
3237
|
}
|
|
2989
|
-
if (start < chunk.length) append(chunk.
|
|
3238
|
+
if (start < chunk.length) append(chunk.subarray(start));
|
|
3239
|
+
}
|
|
3240
|
+
if (pieces.length) {
|
|
3241
|
+
yield (pieces.length === 1 ? pieces[0] : Buffer.concat(pieces)).toString("utf8").replace(/\r$/, "");
|
|
3242
|
+
completedLine?.(readThrough);
|
|
2990
3243
|
}
|
|
2991
|
-
if (pieces.length) yield pieces.join("").replace(/\r$/, "");
|
|
2992
3244
|
} finally {
|
|
2993
3245
|
stream.destroy();
|
|
2994
3246
|
}
|
|
@@ -3011,15 +3263,15 @@ var init_fs_utils = __esm({
|
|
|
3011
3263
|
|
|
3012
3264
|
// src/providers/claude.ts
|
|
3013
3265
|
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
3014
|
-
import { basename as basename2, join as
|
|
3266
|
+
import { basename as basename2, join as join11, resolve as resolve2 } from "path";
|
|
3015
3267
|
import { homedir as homedir4 } from "os";
|
|
3016
3268
|
function getClaudeDir() {
|
|
3017
|
-
return process.env["CLAUDE_CONFIG_DIR"] ||
|
|
3269
|
+
return process.env["CLAUDE_CONFIG_DIR"] || join11(homedir4(), ".claude");
|
|
3018
3270
|
}
|
|
3019
3271
|
function getDesktopSessionsDir() {
|
|
3020
|
-
if (process.platform === "darwin") return
|
|
3021
|
-
if (process.platform === "win32") return
|
|
3022
|
-
return
|
|
3272
|
+
if (process.platform === "darwin") return join11(homedir4(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
|
|
3273
|
+
if (process.platform === "win32") return join11(homedir4(), "AppData", "Roaming", "Claude", "local-agent-mode-sessions");
|
|
3274
|
+
return join11(homedir4(), ".config", "Claude", "local-agent-mode-sessions");
|
|
3023
3275
|
}
|
|
3024
3276
|
async function findDesktopProjectDirs(base) {
|
|
3025
3277
|
const results = [];
|
|
@@ -3028,13 +3280,13 @@ async function findDesktopProjectDirs(base) {
|
|
|
3028
3280
|
const entries = await readdir4(dir).catch(() => []);
|
|
3029
3281
|
for (const entry of entries) {
|
|
3030
3282
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
3031
|
-
const full =
|
|
3283
|
+
const full = join11(dir, entry);
|
|
3032
3284
|
const s = await stat6(full).catch(() => null);
|
|
3033
3285
|
if (!s?.isDirectory()) continue;
|
|
3034
3286
|
if (entry === "projects") {
|
|
3035
3287
|
const projectDirs = await readdir4(full).catch(() => []);
|
|
3036
3288
|
for (const pd of projectDirs) {
|
|
3037
|
-
const pdFull =
|
|
3289
|
+
const pdFull = join11(full, pd);
|
|
3038
3290
|
const pdStat = await stat6(pdFull).catch(() => null);
|
|
3039
3291
|
if (pdStat?.isDirectory()) results.push(pdFull);
|
|
3040
3292
|
}
|
|
@@ -3088,11 +3340,11 @@ var init_claude = __esm({
|
|
|
3088
3340
|
const config = await readConfig();
|
|
3089
3341
|
const roots = /* @__PURE__ */ new Set([getClaudeDir(), ...config.claudeConfigDirs ?? []]);
|
|
3090
3342
|
for (const root of roots) {
|
|
3091
|
-
const projectsDir =
|
|
3343
|
+
const projectsDir = join11(root.startsWith("~/") ? join11(homedir4(), root.slice(2)) : resolve2(root), "projects");
|
|
3092
3344
|
try {
|
|
3093
3345
|
const entries = await readdir4(projectsDir);
|
|
3094
3346
|
for (const dirName of entries) {
|
|
3095
|
-
const dirPath =
|
|
3347
|
+
const dirPath = join11(projectsDir, dirName);
|
|
3096
3348
|
const dirStat = await stat6(dirPath).catch(() => null);
|
|
3097
3349
|
if (dirStat?.isDirectory()) {
|
|
3098
3350
|
sources.push({ path: dirPath, project: dirName, provider: "claude" });
|
|
@@ -3119,10 +3371,10 @@ var init_claude = __esm({
|
|
|
3119
3371
|
|
|
3120
3372
|
// src/providers/codex.ts
|
|
3121
3373
|
import { readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
3122
|
-
import { basename as basename3, join as
|
|
3374
|
+
import { basename as basename3, join as join12 } from "path";
|
|
3123
3375
|
import { homedir as homedir5 } from "os";
|
|
3124
3376
|
function getCodexDir(override) {
|
|
3125
|
-
return override ?? process.env["CODEX_HOME"] ??
|
|
3377
|
+
return override ?? process.env["CODEX_HOME"] ?? join12(homedir5(), ".codex");
|
|
3126
3378
|
}
|
|
3127
3379
|
function sanitizeProject(cwd) {
|
|
3128
3380
|
return cwd.replace(/^\//, "").replace(/\//g, "-");
|
|
@@ -3143,12 +3395,12 @@ async function readCodexHeader(filePath) {
|
|
|
3143
3395
|
return { project: sanitizeProject(entry.payload?.cwd ?? "unknown") };
|
|
3144
3396
|
}
|
|
3145
3397
|
async function discoverSessionsInDir(codexDir) {
|
|
3146
|
-
const sessionsDir =
|
|
3398
|
+
const sessionsDir = join12(codexDir, "sessions");
|
|
3147
3399
|
const sources = [];
|
|
3148
|
-
const archivedDir =
|
|
3400
|
+
const archivedDir = join12(codexDir, "archived_sessions");
|
|
3149
3401
|
for (const file of await readdir5(archivedDir).catch(() => [])) {
|
|
3150
3402
|
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
|
|
3151
|
-
const filePath =
|
|
3403
|
+
const filePath = join12(archivedDir, file);
|
|
3152
3404
|
const s = await stat7(filePath).catch(() => null);
|
|
3153
3405
|
if (!s?.isFile()) continue;
|
|
3154
3406
|
const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
|
|
@@ -3162,19 +3414,19 @@ async function discoverSessionsInDir(codexDir) {
|
|
|
3162
3414
|
}
|
|
3163
3415
|
for (const year of years) {
|
|
3164
3416
|
if (!/^\d{4}$/.test(year)) continue;
|
|
3165
|
-
const yearDir =
|
|
3417
|
+
const yearDir = join12(sessionsDir, year);
|
|
3166
3418
|
const months = await readdir5(yearDir).catch(() => []);
|
|
3167
3419
|
for (const month of months) {
|
|
3168
3420
|
if (!/^\d{2}$/.test(month)) continue;
|
|
3169
|
-
const monthDir =
|
|
3421
|
+
const monthDir = join12(yearDir, month);
|
|
3170
3422
|
const days = await readdir5(monthDir).catch(() => []);
|
|
3171
3423
|
for (const day of days) {
|
|
3172
3424
|
if (!/^\d{2}$/.test(day)) continue;
|
|
3173
|
-
const dayDir =
|
|
3425
|
+
const dayDir = join12(monthDir, day);
|
|
3174
3426
|
const files = await readdir5(dayDir).catch(() => []);
|
|
3175
3427
|
for (const file of files) {
|
|
3176
3428
|
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
|
|
3177
|
-
const filePath =
|
|
3429
|
+
const filePath = join12(dayDir, file);
|
|
3178
3430
|
const s = await stat7(filePath).catch(() => null);
|
|
3179
3431
|
if (!s?.isFile()) continue;
|
|
3180
3432
|
const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
|
|
@@ -3393,10 +3645,10 @@ var init_codex = __esm({
|
|
|
3393
3645
|
|
|
3394
3646
|
// src/providers/copilot.ts
|
|
3395
3647
|
import { readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
3396
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
3648
|
+
import { basename as basename4, dirname as dirname3, join as join13 } from "path";
|
|
3397
3649
|
import { homedir as homedir6 } from "os";
|
|
3398
3650
|
function getCopilotSessionStateDir(override) {
|
|
3399
|
-
return override ??
|
|
3651
|
+
return override ?? join13(homedir6(), ".copilot", "session-state");
|
|
3400
3652
|
}
|
|
3401
3653
|
function parseCwd(yaml) {
|
|
3402
3654
|
const match = yaml.match(/^cwd:\s*(.+)$/m);
|
|
@@ -3471,11 +3723,11 @@ async function discoverSessionsInDir2(sessionStateDir) {
|
|
|
3471
3723
|
return sources;
|
|
3472
3724
|
}
|
|
3473
3725
|
for (const sessionId of sessionDirs) {
|
|
3474
|
-
const eventsPath =
|
|
3726
|
+
const eventsPath = join13(sessionStateDir, sessionId, "events.jsonl");
|
|
3475
3727
|
const s = await stat8(eventsPath).catch(() => null);
|
|
3476
3728
|
if (!s?.isFile()) continue;
|
|
3477
3729
|
let project = sessionId;
|
|
3478
|
-
const yaml = await readSessionFile(
|
|
3730
|
+
const yaml = await readSessionFile(join13(sessionStateDir, sessionId, "workspace.yaml"));
|
|
3479
3731
|
if (yaml !== null) {
|
|
3480
3732
|
const cwd = parseCwd(yaml);
|
|
3481
3733
|
if (cwd) project = basename4(cwd);
|
|
@@ -3589,13 +3841,13 @@ var init_bash_utils = __esm({
|
|
|
3589
3841
|
|
|
3590
3842
|
// src/providers/pi.ts
|
|
3591
3843
|
import { readdir as readdir7, stat as stat9 } from "fs/promises";
|
|
3592
|
-
import { basename as basename6, join as
|
|
3844
|
+
import { basename as basename6, join as join14 } from "path";
|
|
3593
3845
|
import { homedir as homedir7 } from "os";
|
|
3594
3846
|
function getPiSessionsDir(override) {
|
|
3595
|
-
return override ??
|
|
3847
|
+
return override ?? join14(homedir7(), ".pi", "agent", "sessions");
|
|
3596
3848
|
}
|
|
3597
3849
|
function getOmpSessionsDir(override) {
|
|
3598
|
-
return override ??
|
|
3850
|
+
return override ?? join14(homedir7(), ".omp", "agent", "sessions");
|
|
3599
3851
|
}
|
|
3600
3852
|
async function readFirstEntry(filePath) {
|
|
3601
3853
|
const line = await readSessionFirstLine(filePath);
|
|
@@ -3615,7 +3867,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
|
|
|
3615
3867
|
return sources;
|
|
3616
3868
|
}
|
|
3617
3869
|
for (const dirName of projectDirs) {
|
|
3618
|
-
const dirPath =
|
|
3870
|
+
const dirPath = join14(sessionsDir, dirName);
|
|
3619
3871
|
const dirStat = await stat9(dirPath).catch(() => null);
|
|
3620
3872
|
if (!dirStat?.isDirectory()) continue;
|
|
3621
3873
|
let files;
|
|
@@ -3626,7 +3878,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
|
|
|
3626
3878
|
}
|
|
3627
3879
|
for (const file of files) {
|
|
3628
3880
|
if (!file.endsWith(".jsonl")) continue;
|
|
3629
|
-
const filePath =
|
|
3881
|
+
const filePath = join14(dirPath, file);
|
|
3630
3882
|
const fileStat = await stat9(filePath).catch(() => null);
|
|
3631
3883
|
if (!fileStat?.isFile()) continue;
|
|
3632
3884
|
const first = await readFirstEntry(filePath);
|
|
@@ -3783,12 +4035,12 @@ var init_pi = __esm({
|
|
|
3783
4035
|
});
|
|
3784
4036
|
|
|
3785
4037
|
// src/providers/kimi.ts
|
|
3786
|
-
import { createHash as
|
|
4038
|
+
import { createHash as createHash4 } from "crypto";
|
|
3787
4039
|
import { readdir as readdir8, readFile as readFile8, stat as stat10 } from "fs/promises";
|
|
3788
|
-
import { basename as basename7, dirname as dirname4, join as
|
|
4040
|
+
import { basename as basename7, dirname as dirname4, join as join15 } from "path";
|
|
3789
4041
|
import { homedir as homedir8 } from "os";
|
|
3790
4042
|
function createKimiProvider(home) {
|
|
3791
|
-
const root = () => home ?? process.env["KIMI_CODE_HOME"] ??
|
|
4043
|
+
const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join15(homedir8(), ".kimi-code");
|
|
3792
4044
|
return {
|
|
3793
4045
|
name: "kimi",
|
|
3794
4046
|
displayName: "Kimi",
|
|
@@ -3796,7 +4048,7 @@ function createKimiProvider(home) {
|
|
|
3796
4048
|
toolDisplayName: (tool) => tool,
|
|
3797
4049
|
async discoverSessions() {
|
|
3798
4050
|
const projects = /* @__PURE__ */ new Map();
|
|
3799
|
-
const catalog =
|
|
4051
|
+
const catalog = join15(root(), "workspaces.json");
|
|
3800
4052
|
try {
|
|
3801
4053
|
const size = (await stat10(catalog)).size;
|
|
3802
4054
|
if (size <= 2 * 1024 * 1024) {
|
|
@@ -3818,16 +4070,16 @@ function createKimiProvider(home) {
|
|
|
3818
4070
|
const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
|
|
3819
4071
|
for (const entry of entries) {
|
|
3820
4072
|
if (entry.name.startsWith(".")) continue;
|
|
3821
|
-
const path =
|
|
4073
|
+
const path = join15(dir, entry.name);
|
|
3822
4074
|
if (entry.isDirectory()) await walk(path, project, depth + 1);
|
|
3823
4075
|
else if (entry.isFile() && entry.name === "wire.jsonl") sources.push({ path, project, provider: "kimi" });
|
|
3824
4076
|
}
|
|
3825
4077
|
}
|
|
3826
|
-
const sessions =
|
|
4078
|
+
const sessions = join15(root(), "sessions");
|
|
3827
4079
|
for (const entry of await readdir8(sessions, { withFileTypes: true }).catch(() => [])) {
|
|
3828
4080
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3829
4081
|
const project = projects.get(entry.name) ?? entry.name.replace(/^wd_/, "").replace(/_[a-f\d]{12}$/, "");
|
|
3830
|
-
await walk(
|
|
4082
|
+
await walk(join15(sessions, entry.name), project, 0);
|
|
3831
4083
|
}
|
|
3832
4084
|
return sources.sort((a, b) => a.path.localeCompare(b.path));
|
|
3833
4085
|
},
|
|
@@ -3866,7 +4118,7 @@ function createKimiProvider(home) {
|
|
|
3866
4118
|
const warning = `No API pricing for model "${model}"; its tokens are counted but its cost is excluded. Configure a model alias only when the underlying API model is known.`;
|
|
3867
4119
|
if (!warnings.includes(warning)) warnings.push(warning);
|
|
3868
4120
|
}
|
|
3869
|
-
const key2 = "kimi:" +
|
|
4121
|
+
const key2 = "kimi:" + createHash4("sha256").update(JSON.stringify([
|
|
3870
4122
|
data.agentId ?? agent,
|
|
3871
4123
|
data.time,
|
|
3872
4124
|
data.model,
|
|
@@ -3916,10 +4168,10 @@ var init_kimi = __esm({
|
|
|
3916
4168
|
// src/cursor-cache.ts
|
|
3917
4169
|
import { readFile as readFile9, mkdir as mkdir9, stat as stat11, rename as rename7 } from "fs/promises";
|
|
3918
4170
|
import { open as open6 } from "fs/promises";
|
|
3919
|
-
import { join as
|
|
4171
|
+
import { join as join16 } from "path";
|
|
3920
4172
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
3921
4173
|
function getCachePath2() {
|
|
3922
|
-
return
|
|
4174
|
+
return join16(getCacheDir(), CACHE_FILE);
|
|
3923
4175
|
}
|
|
3924
4176
|
async function getDbFingerprint(dbPath) {
|
|
3925
4177
|
try {
|
|
@@ -3984,100 +4236,23 @@ var init_cursor_cache = __esm({
|
|
|
3984
4236
|
}
|
|
3985
4237
|
});
|
|
3986
4238
|
|
|
3987
|
-
// src/sqlite.ts
|
|
3988
|
-
import { createRequire } from "module";
|
|
3989
|
-
function loadDriver() {
|
|
3990
|
-
if (loadAttempted) return DatabaseSync !== null;
|
|
3991
|
-
loadAttempted = true;
|
|
3992
|
-
const origEmit = process.emit.bind(process);
|
|
3993
|
-
let restored = false;
|
|
3994
|
-
const restore = () => {
|
|
3995
|
-
if (restored) return;
|
|
3996
|
-
restored = true;
|
|
3997
|
-
process.emit = origEmit;
|
|
3998
|
-
};
|
|
3999
|
-
process.emit = function patchedEmit(event, ...args) {
|
|
4000
|
-
if (event === "warning") {
|
|
4001
|
-
const warning = args[0];
|
|
4002
|
-
if (warning?.name === "ExperimentalWarning" && typeof warning.message === "string" && /SQLite/i.test(warning.message)) {
|
|
4003
|
-
return false;
|
|
4004
|
-
}
|
|
4005
|
-
}
|
|
4006
|
-
return origEmit.call(this, event, ...args);
|
|
4007
|
-
};
|
|
4008
|
-
try {
|
|
4009
|
-
const mod = requireForSqlite("node:sqlite");
|
|
4010
|
-
DatabaseSync = mod.DatabaseSync;
|
|
4011
|
-
return true;
|
|
4012
|
-
} catch (err) {
|
|
4013
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4014
|
-
loadError = `SQLite-based providers (Cursor, OpenCode) need Node 22+ with the node:sqlite module.
|
|
4015
|
-
Current Node: ${process.version}.
|
|
4016
|
-
Upgrade Node (https://nodejs.org) and run exe-watcher again.
|
|
4017
|
-
(underlying error: ${message})`;
|
|
4018
|
-
return false;
|
|
4019
|
-
} finally {
|
|
4020
|
-
setTimeout(restore, 0);
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
function isSqliteAvailable() {
|
|
4024
|
-
return loadDriver();
|
|
4025
|
-
}
|
|
4026
|
-
function getSqliteLoadError() {
|
|
4027
|
-
return loadError ?? "SQLite driver not available";
|
|
4028
|
-
}
|
|
4029
|
-
function openDatabase(path) {
|
|
4030
|
-
if (!loadDriver() || DatabaseSync === null) {
|
|
4031
|
-
throw new Error(getSqliteLoadError());
|
|
4032
|
-
}
|
|
4033
|
-
const db = new DatabaseSync(path, { readOnly: true });
|
|
4034
|
-
return {
|
|
4035
|
-
query(sql, params = []) {
|
|
4036
|
-
const rows2 = [];
|
|
4037
|
-
let bytes = 0;
|
|
4038
|
-
for (const row of db.prepare(sql).iterate(...params)) {
|
|
4039
|
-
const size = Buffer.byteLength(JSON.stringify(row));
|
|
4040
|
-
bytes += size;
|
|
4041
|
-
if (rows2.length >= 1e5 || bytes > 32 * 1024 * 1024) throw new ResourceBudgetError("SQLite result budget");
|
|
4042
|
-
chargeRead(size);
|
|
4043
|
-
rows2.push(row);
|
|
4044
|
-
}
|
|
4045
|
-
return rows2;
|
|
4046
|
-
},
|
|
4047
|
-
close() {
|
|
4048
|
-
db.close();
|
|
4049
|
-
}
|
|
4050
|
-
};
|
|
4051
|
-
}
|
|
4052
|
-
var requireForSqlite, DatabaseSync, loadAttempted, loadError;
|
|
4053
|
-
var init_sqlite = __esm({
|
|
4054
|
-
"src/sqlite.ts"() {
|
|
4055
|
-
"use strict";
|
|
4056
|
-
init_resource_budget();
|
|
4057
|
-
requireForSqlite = createRequire(import.meta.url);
|
|
4058
|
-
DatabaseSync = null;
|
|
4059
|
-
loadAttempted = false;
|
|
4060
|
-
loadError = null;
|
|
4061
|
-
}
|
|
4062
|
-
});
|
|
4063
|
-
|
|
4064
4239
|
// src/providers/cursor.ts
|
|
4065
4240
|
var cursor_exports = {};
|
|
4066
4241
|
__export(cursor_exports, {
|
|
4067
4242
|
createCursorProvider: () => createCursorProvider,
|
|
4068
4243
|
cursor: () => cursor
|
|
4069
4244
|
});
|
|
4070
|
-
import { existsSync } from "fs";
|
|
4071
|
-
import { join as
|
|
4245
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4246
|
+
import { join as join17 } from "path";
|
|
4072
4247
|
import { homedir as homedir9 } from "os";
|
|
4073
4248
|
function getCursorDbPath() {
|
|
4074
4249
|
if (process.platform === "darwin") {
|
|
4075
|
-
return
|
|
4250
|
+
return join17(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4076
4251
|
}
|
|
4077
4252
|
if (process.platform === "win32") {
|
|
4078
|
-
return
|
|
4253
|
+
return join17(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4079
4254
|
}
|
|
4080
|
-
return
|
|
4255
|
+
return join17(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4081
4256
|
}
|
|
4082
4257
|
function extractLanguages(codeBlocksJson) {
|
|
4083
4258
|
if (!codeBlocksJson) return [];
|
|
@@ -4249,7 +4424,7 @@ function createCursorProvider(dbPathOverride) {
|
|
|
4249
4424
|
async discoverSessions() {
|
|
4250
4425
|
if (!isSqliteAvailable()) return [];
|
|
4251
4426
|
const dbPath = dbPathOverride ?? getCursorDbPath();
|
|
4252
|
-
if (!
|
|
4427
|
+
if (!existsSync2(dbPath)) return [];
|
|
4253
4428
|
return [{ path: dbPath, project: "cursor", provider: "cursor" }];
|
|
4254
4429
|
},
|
|
4255
4430
|
createSessionParser(source, seenKeys) {
|
|
@@ -4320,19 +4495,19 @@ __export(opencode_exports, {
|
|
|
4320
4495
|
opencode: () => opencode
|
|
4321
4496
|
});
|
|
4322
4497
|
import { readdir as readdir9 } from "fs/promises";
|
|
4323
|
-
import { join as
|
|
4498
|
+
import { join as join18 } from "path";
|
|
4324
4499
|
import { homedir as homedir10 } from "os";
|
|
4325
4500
|
function sanitize2(dir) {
|
|
4326
4501
|
return dir.replace(/^\//, "").replace(/\//g, "-");
|
|
4327
4502
|
}
|
|
4328
4503
|
function getDataDir(dataDir) {
|
|
4329
|
-
const base = dataDir ?? process.env["XDG_DATA_HOME"] ??
|
|
4330
|
-
return
|
|
4504
|
+
const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join18(homedir10(), ".local", "share");
|
|
4505
|
+
return join18(base, "opencode");
|
|
4331
4506
|
}
|
|
4332
4507
|
async function findDbFiles(dir) {
|
|
4333
4508
|
try {
|
|
4334
4509
|
const entries = await readdir9(dir);
|
|
4335
|
-
return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) =>
|
|
4510
|
+
return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join18(dir, f));
|
|
4336
4511
|
} catch (error) {
|
|
4337
4512
|
if (error instanceof ResourceBudgetError) throw error;
|
|
4338
4513
|
return [];
|
|
@@ -4561,20 +4736,20 @@ __export(cursor_agent_exports, {
|
|
|
4561
4736
|
createCursorAgentProvider: () => createCursorAgentProvider,
|
|
4562
4737
|
cursor_agent: () => cursor_agent
|
|
4563
4738
|
});
|
|
4564
|
-
import { createHash as
|
|
4565
|
-
import { existsSync as
|
|
4739
|
+
import { createHash as createHash5 } from "crypto";
|
|
4740
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4566
4741
|
import { readdir as readdir10, stat as stat12 } from "fs/promises";
|
|
4567
|
-
import { join as
|
|
4742
|
+
import { join as join19, basename as basename8 } from "path";
|
|
4568
4743
|
import { homedir as homedir11 } from "os";
|
|
4569
4744
|
function getCursorAgentBaseDir(baseDirOverride) {
|
|
4570
4745
|
if (baseDirOverride) return baseDirOverride;
|
|
4571
|
-
return
|
|
4746
|
+
return join19(homedir11(), ".cursor");
|
|
4572
4747
|
}
|
|
4573
4748
|
function getProjectsDir(baseDir) {
|
|
4574
|
-
return
|
|
4749
|
+
return join19(baseDir, "projects");
|
|
4575
4750
|
}
|
|
4576
4751
|
function getAttributionDbPath(baseDir) {
|
|
4577
|
-
return
|
|
4752
|
+
return join19(baseDir, "ai-tracking", "ai-code-tracking.db");
|
|
4578
4753
|
}
|
|
4579
4754
|
function estimateTokens(charCount) {
|
|
4580
4755
|
if (charCount <= 0) return 0;
|
|
@@ -4625,7 +4800,7 @@ function resolveModel3(raw) {
|
|
|
4625
4800
|
function toConversationId(transcriptPath) {
|
|
4626
4801
|
const filename = basename8(transcriptPath, ".txt");
|
|
4627
4802
|
if (filename.length === 36 && UUID_LIKE.test(filename)) return filename;
|
|
4628
|
-
return
|
|
4803
|
+
return createHash5("sha1").update(transcriptPath).digest("hex").slice(0, 16);
|
|
4629
4804
|
}
|
|
4630
4805
|
function extractUserQuery(userBlock) {
|
|
4631
4806
|
const chunks = [];
|
|
@@ -4734,7 +4909,7 @@ function createParser6(source, seenKeys, dbPath, summariesByConversationId) {
|
|
|
4734
4909
|
let db = null;
|
|
4735
4910
|
try {
|
|
4736
4911
|
if (!summary) {
|
|
4737
|
-
if (
|
|
4912
|
+
if (existsSync3(dbPath)) {
|
|
4738
4913
|
try {
|
|
4739
4914
|
db = openDatabase(dbPath);
|
|
4740
4915
|
const rows2 = db.query(CONVERSATION_SUMMARY_QUERY, [conversationId]);
|
|
@@ -4827,19 +5002,19 @@ function createCursorAgentProvider(baseDirOverride) {
|
|
|
4827
5002
|
return rawTool;
|
|
4828
5003
|
},
|
|
4829
5004
|
async discoverSessions() {
|
|
4830
|
-
if (!
|
|
5005
|
+
if (!existsSync3(projectsDir)) return [];
|
|
4831
5006
|
const projectEntries = await readdir10(projectsDir, { withFileTypes: true });
|
|
4832
5007
|
const sources = [];
|
|
4833
5008
|
for (const entry of projectEntries) {
|
|
4834
5009
|
if (!entry.isDirectory()) continue;
|
|
4835
5010
|
const projectId = prettifyProjectId(entry.name);
|
|
4836
|
-
const transcriptDir =
|
|
4837
|
-
if (!
|
|
5011
|
+
const transcriptDir = join19(projectsDir, entry.name, "agent-transcripts");
|
|
5012
|
+
if (!existsSync3(transcriptDir)) continue;
|
|
4838
5013
|
const transcriptEntries = await readdir10(transcriptDir, { withFileTypes: true });
|
|
4839
5014
|
for (const transcript of transcriptEntries) {
|
|
4840
5015
|
if (!transcript.isFile()) continue;
|
|
4841
5016
|
if (!transcript.name.endsWith(".txt")) continue;
|
|
4842
|
-
const transcriptPath =
|
|
5017
|
+
const transcriptPath = join19(transcriptDir, transcript.name);
|
|
4843
5018
|
sources.push({
|
|
4844
5019
|
path: transcriptPath,
|
|
4845
5020
|
project: projectId,
|
|
@@ -5153,15 +5328,15 @@ var init_classifier = __esm({
|
|
|
5153
5328
|
|
|
5154
5329
|
// src/session-index.ts
|
|
5155
5330
|
import { stat as stat13 } from "fs/promises";
|
|
5156
|
-
import { existsSync as
|
|
5331
|
+
import { existsSync as existsSync4, unlinkSync } from "fs";
|
|
5157
5332
|
import { readFile as readFile11, mkdir as mkdir10, rename as rename8, unlink as unlink6, open as open7 } from "fs/promises";
|
|
5158
|
-
import { join as
|
|
5333
|
+
import { join as join20 } from "path";
|
|
5159
5334
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
5160
5335
|
function emptyIndex() {
|
|
5161
5336
|
return { v: SESSION_INDEX_VERSION, e: {} };
|
|
5162
5337
|
}
|
|
5163
5338
|
function indexPath() {
|
|
5164
|
-
return
|
|
5339
|
+
return join20(getCacheDir(), INDEX_FILENAME);
|
|
5165
5340
|
}
|
|
5166
5341
|
async function loadSessionIndex() {
|
|
5167
5342
|
if (currentIndex) return currentIndex;
|
|
@@ -5181,7 +5356,7 @@ async function loadSessionIndex() {
|
|
|
5181
5356
|
}
|
|
5182
5357
|
async function saveSessionIndex(index) {
|
|
5183
5358
|
const dir = getCacheDir();
|
|
5184
|
-
if (!
|
|
5359
|
+
if (!existsSync4(dir)) await mkdir10(dir, { recursive: true });
|
|
5185
5360
|
const finalPath = indexPath();
|
|
5186
5361
|
const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
5187
5362
|
const payload = JSON.stringify(index);
|
|
@@ -5263,7 +5438,7 @@ __export(parser_exports, {
|
|
|
5263
5438
|
});
|
|
5264
5439
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
5265
5440
|
import { readdir as readdir11, stat as stat14 } from "fs/promises";
|
|
5266
|
-
import { basename as basename9, join as
|
|
5441
|
+
import { basename as basename9, join as join21 } from "path";
|
|
5267
5442
|
function getParseWarnings() {
|
|
5268
5443
|
return _parseWarnings;
|
|
5269
5444
|
}
|
|
@@ -5349,71 +5524,95 @@ function parseApiCall(entry) {
|
|
|
5349
5524
|
deduplicationKey: msg.id ?? `claude:${entry.timestamp}`
|
|
5350
5525
|
};
|
|
5351
5526
|
}
|
|
5352
|
-
|
|
5527
|
+
function validTurnScan(value) {
|
|
5528
|
+
const x = value;
|
|
5529
|
+
const amount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
|
|
5530
|
+
const calls = (list) => Array.isArray(list) && list.every((c) => c && typeof c.deduplicationKey === "string" && typeof c.model === "string" && typeof c.timestamp === "string" && amount(c.costUSD) && c.usage && ["inputTokens", "outputTokens", "cacheCreationInputTokens", "cacheReadInputTokens"].every((key2) => amount(c.usage[key2])) && Array.isArray(c.tools) && Array.isArray(c.bashCommands));
|
|
5531
|
+
return !!x && Array.isArray(x.turns) && x.turns.every((t) => t && calls(t.assistantCalls) && typeof t.userMessage === "string" && typeof t.timestamp === "string" && typeof t.sessionId === "string") && calls(x.currentCalls) && typeof x.currentUserMessage === "string" && Number.isSafeInteger(x.currentUserSignals) && x.currentUserSignals >= 0 && typeof x.currentTimestamp === "string" && typeof x.currentSessionId === "string" && Number.isSafeInteger(x.lineNumber) && x.lineNumber >= 0;
|
|
5532
|
+
}
|
|
5533
|
+
async function parseTurns(filePath, progress) {
|
|
5534
|
+
const checkpoint = await progress.load(validTurnScan);
|
|
5535
|
+
const saved = checkpoint?.state;
|
|
5353
5536
|
const seenCalls = /* @__PURE__ */ new Map();
|
|
5354
|
-
const turns = [];
|
|
5355
|
-
let currentUserMessage = "";
|
|
5356
|
-
let currentUserSignals = 0;
|
|
5357
|
-
let currentCalls = [];
|
|
5358
|
-
let currentTimestamp = "";
|
|
5359
|
-
let currentSessionId = "";
|
|
5360
|
-
|
|
5361
|
-
for
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5537
|
+
const turns = saved?.turns ?? [];
|
|
5538
|
+
let currentUserMessage = saved?.currentUserMessage ?? "";
|
|
5539
|
+
let currentUserSignals = saved?.currentUserSignals ?? 0;
|
|
5540
|
+
let currentCalls = saved?.currentCalls ?? [];
|
|
5541
|
+
let currentTimestamp = saved?.currentTimestamp ?? "";
|
|
5542
|
+
let currentSessionId = saved?.currentSessionId ?? "";
|
|
5543
|
+
for (const turn of turns) for (const call of turn.assistantCalls) seenCalls.set(call.deduplicationKey, call);
|
|
5544
|
+
for (const call of currentCalls) seenCalls.set(call.deduplicationKey, call);
|
|
5545
|
+
let lineNumber = saved?.lineNumber ?? 0;
|
|
5546
|
+
let offset = checkpoint?.offset ?? 0;
|
|
5547
|
+
try {
|
|
5548
|
+
for await (const line of readSessionLines(filePath, offset, void 0, (next) => {
|
|
5549
|
+
offset = next;
|
|
5550
|
+
})) {
|
|
5551
|
+
chargeRecord();
|
|
5552
|
+
lineNumber++;
|
|
5553
|
+
const entry = parseJsonlLine(line);
|
|
5554
|
+
if (!entry) continue;
|
|
5555
|
+
if (entry.type === "user") {
|
|
5556
|
+
const text = getUserMessageText(entry);
|
|
5557
|
+
if (text.trim()) {
|
|
5558
|
+
if (currentCalls.length > 0) {
|
|
5559
|
+
turns.push({
|
|
5560
|
+
userMessage: currentUserMessage,
|
|
5561
|
+
userMessageSignals: currentUserSignals,
|
|
5562
|
+
assistantCalls: currentCalls,
|
|
5563
|
+
timestamp: currentTimestamp,
|
|
5564
|
+
sessionId: currentSessionId
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
const compact = compactUserMessage(text);
|
|
5568
|
+
currentUserMessage = compact.userMessage;
|
|
5569
|
+
currentUserSignals = compact.userMessageSignals;
|
|
5570
|
+
currentCalls = [];
|
|
5571
|
+
currentTimestamp = entry.timestamp ?? "";
|
|
5572
|
+
currentSessionId = entry.sessionId ?? "";
|
|
5376
5573
|
}
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5574
|
+
} else if (entry.type === "assistant") {
|
|
5575
|
+
const msgId = getMessageId(entry);
|
|
5576
|
+
const call = parseApiCall(entry);
|
|
5577
|
+
if (!call) continue;
|
|
5578
|
+
const previous = msgId ? seenCalls.get(msgId) : void 0;
|
|
5579
|
+
if (previous) {
|
|
5580
|
+
for (const key2 of Object.keys(call.usage)) {
|
|
5581
|
+
previous.usage[key2] = Math.max(previous.usage[key2] ?? 0, call.usage[key2] ?? 0);
|
|
5582
|
+
}
|
|
5583
|
+
previous.tools = [.../* @__PURE__ */ new Set([...previous.tools, ...call.tools])];
|
|
5584
|
+
previous.mcpTools = extractMcpTools(previous.tools);
|
|
5585
|
+
previous.bashCommands = [.../* @__PURE__ */ new Set([...previous.bashCommands, ...call.bashCommands])];
|
|
5586
|
+
previous.hasAgentSpawn ||= call.hasAgentSpawn;
|
|
5587
|
+
previous.hasPlanMode ||= call.hasPlanMode;
|
|
5588
|
+
if (call.speed === "fast") previous.speed = "fast";
|
|
5589
|
+
previous.costUSD = calculateCost(
|
|
5590
|
+
previous.model,
|
|
5591
|
+
previous.usage.inputTokens,
|
|
5592
|
+
previous.usage.outputTokens,
|
|
5593
|
+
previous.usage.cacheCreationInputTokens,
|
|
5594
|
+
previous.usage.cacheReadInputTokens,
|
|
5595
|
+
previous.usage.webSearchRequests,
|
|
5596
|
+
previous.speed,
|
|
5597
|
+
previous.usage.inputTokens + previous.usage.cacheCreationInputTokens + previous.usage.cacheReadInputTokens,
|
|
5598
|
+
previous.usage.cacheCreation1hInputTokens
|
|
5599
|
+
);
|
|
5600
|
+
continue;
|
|
5392
5601
|
}
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
previous.usage.cacheReadInputTokens,
|
|
5405
|
-
previous.usage.webSearchRequests,
|
|
5406
|
-
previous.speed,
|
|
5407
|
-
previous.usage.inputTokens + previous.usage.cacheCreationInputTokens + previous.usage.cacheReadInputTokens,
|
|
5408
|
-
previous.usage.cacheCreation1hInputTokens
|
|
5409
|
-
);
|
|
5410
|
-
continue;
|
|
5602
|
+
if (msgId) seenCalls.set(msgId, call);
|
|
5603
|
+
else call.deduplicationKey = `${filePath}:${lineNumber}`;
|
|
5604
|
+
currentCalls.push(call);
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
} catch (error) {
|
|
5608
|
+
if (error instanceof ResourceBudgetError) {
|
|
5609
|
+
try {
|
|
5610
|
+
await progress.save({ turns, currentCalls, currentUserMessage, currentUserSignals, currentTimestamp, currentSessionId, lineNumber }, offset);
|
|
5611
|
+
} catch {
|
|
5612
|
+
_parseWarnings.push("A historical transcript checkpoint could not be saved; its unfinished usage remains unknown.");
|
|
5411
5613
|
}
|
|
5412
|
-
if (msgId) seenCalls.set(msgId, call);
|
|
5413
|
-
else call.deduplicationKey = `${filePath}:${lineNumber}`;
|
|
5414
|
-
chargeRecord();
|
|
5415
|
-
currentCalls.push(call);
|
|
5416
5614
|
}
|
|
5615
|
+
throw error;
|
|
5417
5616
|
}
|
|
5418
5617
|
if (currentCalls.length > 0) {
|
|
5419
5618
|
turns.push({
|
|
@@ -5518,7 +5717,7 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
|
|
|
5518
5717
|
} catch {
|
|
5519
5718
|
}
|
|
5520
5719
|
}
|
|
5521
|
-
const rawTurns = await cachedFileParse(filePath, "claude-turns", () => parseTurns(filePath));
|
|
5720
|
+
const rawTurns = await cachedFileParse(filePath, "claude-turns", (progress) => parseTurns(filePath, progress));
|
|
5522
5721
|
const sessionId = basename9(filePath, ".jsonl");
|
|
5523
5722
|
const turns = [];
|
|
5524
5723
|
for (const turn of rawTurns) {
|
|
@@ -5553,13 +5752,13 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
|
|
|
5553
5752
|
}
|
|
5554
5753
|
async function collectJsonlFiles2(dirPath) {
|
|
5555
5754
|
const files = await readdir11(dirPath).catch(() => []);
|
|
5556
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
5755
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join21(dirPath, f));
|
|
5557
5756
|
for (const entry of files) {
|
|
5558
5757
|
if (entry.endsWith(".jsonl")) continue;
|
|
5559
|
-
const subagentsPath =
|
|
5758
|
+
const subagentsPath = join21(dirPath, entry, "subagents");
|
|
5560
5759
|
const subFiles = await readdir11(subagentsPath).catch(() => []);
|
|
5561
5760
|
for (const sf of subFiles) {
|
|
5562
|
-
if (sf.endsWith(".jsonl")) jsonlFiles.push(
|
|
5761
|
+
if (sf.endsWith(".jsonl")) jsonlFiles.push(join21(subagentsPath, sf));
|
|
5563
5762
|
}
|
|
5564
5763
|
}
|
|
5565
5764
|
return jsonlFiles;
|
|
@@ -5925,8 +6124,8 @@ var init_parser = __esm({
|
|
|
5925
6124
|
init_cache_dir();
|
|
5926
6125
|
init_file_lock();
|
|
5927
6126
|
init_resource_budget();
|
|
5928
|
-
import { existsSync as
|
|
5929
|
-
import { join as
|
|
6127
|
+
import { existsSync as existsSync8, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
6128
|
+
import { join as join31 } from "path";
|
|
5930
6129
|
import { homedir as homedir17 } from "os";
|
|
5931
6130
|
import { Command } from "commander";
|
|
5932
6131
|
|
|
@@ -6657,19 +6856,19 @@ function renderStatusBar(projects) {
|
|
|
6657
6856
|
|
|
6658
6857
|
// src/menubar-json.ts
|
|
6659
6858
|
import { readdirSync, readFileSync } from "fs";
|
|
6660
|
-
import { join as
|
|
6859
|
+
import { join as join22, dirname as dirname5 } from "path";
|
|
6661
6860
|
init_models();
|
|
6662
6861
|
import { fileURLToPath } from "url";
|
|
6663
6862
|
function getCliVersion() {
|
|
6664
6863
|
try {
|
|
6665
|
-
const pkgPath =
|
|
6864
|
+
const pkgPath = join22(dirname5(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
6666
6865
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
6667
6866
|
return pkg.version ?? "0.0.0";
|
|
6668
6867
|
} catch {
|
|
6669
6868
|
return "0.0.0";
|
|
6670
6869
|
}
|
|
6671
6870
|
}
|
|
6672
|
-
var MIN_APP_VERSION = "0.2.
|
|
6871
|
+
var MIN_APP_VERSION = "0.2.67";
|
|
6673
6872
|
var TOP_ACTIVITIES_LIMIT = 20;
|
|
6674
6873
|
var TOP_MODELS_LIMIT = 20;
|
|
6675
6874
|
var TOP_FINDINGS_LIMIT = 10;
|
|
@@ -6825,18 +7024,18 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
|
|
|
6825
7024
|
// src/daily-cache.ts
|
|
6826
7025
|
init_cache_dir();
|
|
6827
7026
|
init_models();
|
|
6828
|
-
import { createHash as
|
|
6829
|
-
import { existsSync as
|
|
7027
|
+
import { createHash as createHash6, randomBytes as randomBytes6 } from "crypto";
|
|
7028
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6830
7029
|
import { mkdir as mkdir11, open as open8, readFile as readFile12, rename as rename9, unlink as unlink7 } from "fs/promises";
|
|
6831
|
-
import { join as
|
|
7030
|
+
import { join as join23 } from "path";
|
|
6832
7031
|
var DAILY_CACHE_VERSION = 9;
|
|
6833
7032
|
var DEFAULT_DAILY_CACHE_SCOPE = "global";
|
|
6834
7033
|
var DAILY_CACHE_FILENAME = "daily-cache.json";
|
|
6835
7034
|
function getCachePath3(scopeKey) {
|
|
6836
7035
|
if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
|
|
6837
|
-
return
|
|
7036
|
+
return join23(getCacheDir(), DAILY_CACHE_FILENAME);
|
|
6838
7037
|
}
|
|
6839
|
-
return
|
|
7038
|
+
return join23(getCacheDir(), `daily-cache-${scopeKey}.json`);
|
|
6840
7039
|
}
|
|
6841
7040
|
function emptyCache(scopeKey) {
|
|
6842
7041
|
return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
|
|
@@ -6851,7 +7050,7 @@ function buildDailyCacheScopeKey(include, exclude) {
|
|
|
6851
7050
|
return DEFAULT_DAILY_CACHE_SCOPE;
|
|
6852
7051
|
}
|
|
6853
7052
|
const raw = JSON.stringify({ include: normalizedInclude, exclude: normalizedExclude });
|
|
6854
|
-
return `scope-${
|
|
7053
|
+
return `scope-${createHash6("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
6855
7054
|
}
|
|
6856
7055
|
function isValidCache(parsed) {
|
|
6857
7056
|
if (!parsed || typeof parsed !== "object") return false;
|
|
@@ -6859,6 +7058,7 @@ function isValidCache(parsed) {
|
|
|
6859
7058
|
if (c.version !== DAILY_CACHE_VERSION && c.version !== 8) return false;
|
|
6860
7059
|
if (typeof c.scopeKey !== "string" || c.scopeKey.length === 0) return false;
|
|
6861
7060
|
if (!Array.isArray(c.days)) return false;
|
|
7061
|
+
if (c.historyScanProgress !== void 0 && (!Number.isSafeInteger(c.historyScanProgress) || c.historyScanProgress < 0)) return false;
|
|
6862
7062
|
if (c.missingProviders && (!Array.isArray(c.missingProviders) || !c.missingProviders.every((p) => typeof p === "string"))) return false;
|
|
6863
7063
|
if (c.historyBuild) {
|
|
6864
7064
|
const stage = c.historyBuild;
|
|
@@ -6869,20 +7069,20 @@ function isValidCache(parsed) {
|
|
|
6869
7069
|
}
|
|
6870
7070
|
async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
|
|
6871
7071
|
const path = getCachePath3(scopeKey);
|
|
6872
|
-
if (!
|
|
7072
|
+
if (!existsSync5(path)) return emptyCache(scopeKey);
|
|
6873
7073
|
try {
|
|
6874
7074
|
const raw = await readFile12(path, "utf-8");
|
|
6875
7075
|
const parsed = JSON.parse(raw);
|
|
6876
7076
|
if (!isValidCache(parsed)) return emptyCache(scopeKey);
|
|
6877
7077
|
if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
|
|
6878
|
-
if (
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
7078
|
+
if (parsed.needsRepricing) {
|
|
7079
|
+
const repricing = parsed.historyBuild;
|
|
7080
|
+
if (parsed.days.length) {
|
|
7081
|
+
parsed.historyBuild = void 0;
|
|
7082
|
+
parsed.backfill = repricing?.previousBackfill ?? null;
|
|
7083
|
+
}
|
|
7084
|
+
delete parsed.needsRepricing;
|
|
7085
|
+
delete parsed.previousHistoryStart;
|
|
6886
7086
|
}
|
|
6887
7087
|
if (parsed.version !== DAILY_CACHE_VERSION) {
|
|
6888
7088
|
return {
|
|
@@ -6899,7 +7099,7 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
|
|
|
6899
7099
|
}
|
|
6900
7100
|
async function saveDailyCache(cache) {
|
|
6901
7101
|
const dir = getCacheDir();
|
|
6902
|
-
if (!
|
|
7102
|
+
if (!existsSync5(dir)) await mkdir11(dir, { recursive: true });
|
|
6903
7103
|
const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
|
|
6904
7104
|
const finalPath = getCachePath3(scopeKey);
|
|
6905
7105
|
const tempPath = `${finalPath}.${randomBytes6(8).toString("hex")}.tmp`;
|
|
@@ -6937,6 +7137,7 @@ function addNewDays(cache, incoming, newestDate, options = {}) {
|
|
|
6937
7137
|
lastComputedDate: nextLast,
|
|
6938
7138
|
days: merged,
|
|
6939
7139
|
revalidatedAt: cache.revalidatedAt,
|
|
7140
|
+
historyScanProgress: cache.historyScanProgress,
|
|
6940
7141
|
backfill: cache.backfill,
|
|
6941
7142
|
partialDates: cache.partialDates
|
|
6942
7143
|
};
|
|
@@ -6959,16 +7160,16 @@ init_types();
|
|
|
6959
7160
|
init_cache_dir();
|
|
6960
7161
|
init_models();
|
|
6961
7162
|
import { readFile as readFile13, stat as stat16, mkdir as mkdir12, writeFile as writeFile4, rename as rename10, unlink as unlink8 } from "fs/promises";
|
|
6962
|
-
import { createHash as
|
|
7163
|
+
import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
|
|
6963
7164
|
import { homedir as homedir12 } from "os";
|
|
6964
|
-
import { join as
|
|
7165
|
+
import { join as join25 } from "path";
|
|
6965
7166
|
|
|
6966
7167
|
// src/history-start.ts
|
|
6967
7168
|
init_discovery_cache();
|
|
6968
7169
|
init_fs_utils();
|
|
6969
7170
|
init_parser();
|
|
6970
7171
|
import { readdir as readdir12, stat as stat15 } from "fs/promises";
|
|
6971
|
-
import { join as
|
|
7172
|
+
import { join as join24 } from "path";
|
|
6972
7173
|
function parseHistoryStartDate(value) {
|
|
6973
7174
|
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
6974
7175
|
const [year, month, day] = value.split("-").map(Number);
|
|
@@ -6988,7 +7189,7 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
|
|
|
6988
7189
|
for (const source of sources) {
|
|
6989
7190
|
const sourceStat = await stat15(source.path).catch(() => null);
|
|
6990
7191
|
if (!sourceStat) continue;
|
|
6991
|
-
const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) =>
|
|
7192
|
+
const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) => join24(source.path, name)) : source.path.endsWith(".jsonl") ? [source.path] : [];
|
|
6992
7193
|
for (const path of paths) {
|
|
6993
7194
|
const fileStat = await stat15(path).catch(() => null);
|
|
6994
7195
|
if (!fileStat) continue;
|
|
@@ -7007,6 +7208,20 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
|
|
|
7007
7208
|
}
|
|
7008
7209
|
|
|
7009
7210
|
// src/claude-recovery.ts
|
|
7211
|
+
init_recorded_costs();
|
|
7212
|
+
function lockClaudeRecovery(recovery) {
|
|
7213
|
+
if (!recovery) return null;
|
|
7214
|
+
const rows2 = recovery.models.map((row) => ({
|
|
7215
|
+
...row,
|
|
7216
|
+
provider: "claude",
|
|
7217
|
+
deduplicationKey: "claude-stats-recovery",
|
|
7218
|
+
cacheCreationInputTokens: row.cacheWriteTokens,
|
|
7219
|
+
cacheReadInputTokens: row.cacheReadTokens
|
|
7220
|
+
}));
|
|
7221
|
+
lockRecordedCosts(rows2);
|
|
7222
|
+
const models = recovery.models.map((row, index) => ({ ...row, costUSD: rows2[index].costUSD }));
|
|
7223
|
+
return { ...recovery, models, costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0) };
|
|
7224
|
+
}
|
|
7010
7225
|
var fields = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
7011
7226
|
var emptyTokens = () => ({ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 });
|
|
7012
7227
|
function applyClaudeRecovery(data, recovery) {
|
|
@@ -7025,9 +7240,9 @@ function applyClaudeRecovery(data, recovery) {
|
|
|
7025
7240
|
}
|
|
7026
7241
|
}
|
|
7027
7242
|
async function readClaudeUsageSnapshot() {
|
|
7028
|
-
const root = process.env["CLAUDE_CONFIG_DIR"] ||
|
|
7029
|
-
const path =
|
|
7030
|
-
const savedPath =
|
|
7243
|
+
const root = process.env["CLAUDE_CONFIG_DIR"] || join25(homedir12(), ".claude");
|
|
7244
|
+
const path = join25(root, "stats-cache.json");
|
|
7245
|
+
const savedPath = join25(getCacheDir(), `claude-lifetime-${createHash7("sha256").update(root).digest("hex").slice(0, 16)}.json`);
|
|
7031
7246
|
const read = async (file) => {
|
|
7032
7247
|
try {
|
|
7033
7248
|
if ((await stat16(file)).size > 2 * 1024 ** 2) return null;
|
|
@@ -7111,7 +7326,7 @@ function recoverClaudeUsage(snapshot, recorded, rangeStart) {
|
|
|
7111
7326
|
...totals,
|
|
7112
7327
|
costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0),
|
|
7113
7328
|
models,
|
|
7114
|
-
note: "Includes historical Claude token counters missing from retained transcripts, reconciled per model and token type to avoid double-counting. Their dates, calls, projects, request sizes, cache TTL and speed are unavailable; recovered cost uses
|
|
7329
|
+
note: "Includes historical Claude token counters missing from retained transcripts, reconciled per model and token type to avoid double-counting. Their dates, calls, projects, request sizes, cache TTL and speed are unavailable; recovered cost uses the standard rates available when counted, short-context rates and 5-minute cache writes. Saved estimates are not revalued on pricing refresh. Time-based discounts cannot be reconstructed. Earlier usage without logs or counters remains unknown."
|
|
7115
7330
|
};
|
|
7116
7331
|
}
|
|
7117
7332
|
async function getClaudeRecoveryForRange(range, signal) {
|
|
@@ -7132,7 +7347,7 @@ async function getClaudeRecoveryForRange(range, signal) {
|
|
|
7132
7347
|
recorded.set(model, total);
|
|
7133
7348
|
}
|
|
7134
7349
|
}
|
|
7135
|
-
return recoverClaudeUsage(snapshot, recorded, range.start);
|
|
7350
|
+
return lockClaudeRecovery(recoverClaudeUsage(snapshot, recorded, range.start));
|
|
7136
7351
|
}
|
|
7137
7352
|
|
|
7138
7353
|
// src/latest-load.ts
|
|
@@ -7195,8 +7410,8 @@ init_resource_budget();
|
|
|
7195
7410
|
init_providers();
|
|
7196
7411
|
import chalk2 from "chalk";
|
|
7197
7412
|
import { readdir as readdir13, stat as stat17 } from "fs/promises";
|
|
7198
|
-
import { existsSync as
|
|
7199
|
-
import { basename as basename10, join as
|
|
7413
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
7414
|
+
import { basename as basename10, join as join26 } from "path";
|
|
7200
7415
|
import { homedir as homedir13 } from "os";
|
|
7201
7416
|
var ORANGE = "#F59E0B";
|
|
7202
7417
|
var DIM = "#666666";
|
|
@@ -7279,13 +7494,13 @@ var DEFAULT_TREND_PERIOD_MS = DEFAULT_TREND_PERIOD_DAYS * 24 * 60 * 60 * 1e3;
|
|
|
7279
7494
|
var IMPROVING_THRESHOLD = 0.5;
|
|
7280
7495
|
async function collectJsonlFiles3(dirPath) {
|
|
7281
7496
|
const files = await readdir13(dirPath).catch(() => []);
|
|
7282
|
-
const result = files.filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
7497
|
+
const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join26(dirPath, f));
|
|
7283
7498
|
for (const entry of files) {
|
|
7284
7499
|
if (entry.endsWith(".jsonl")) continue;
|
|
7285
|
-
const subPath =
|
|
7500
|
+
const subPath = join26(dirPath, entry, "subagents");
|
|
7286
7501
|
const subFiles = await readdir13(subPath).catch(() => []);
|
|
7287
7502
|
for (const sf of subFiles) {
|
|
7288
|
-
if (sf.endsWith(".jsonl")) result.push(
|
|
7503
|
+
if (sf.endsWith(".jsonl")) result.push(join26(subPath, sf));
|
|
7289
7504
|
}
|
|
7290
7505
|
}
|
|
7291
7506
|
return result;
|
|
@@ -7475,16 +7690,16 @@ function isReadTool(name) {
|
|
|
7475
7690
|
function loadMcpConfigs(projectCwds) {
|
|
7476
7691
|
const servers = /* @__PURE__ */ new Map();
|
|
7477
7692
|
const configPaths = [
|
|
7478
|
-
|
|
7479
|
-
|
|
7693
|
+
join26(homedir13(), ".claude", "settings.json"),
|
|
7694
|
+
join26(homedir13(), ".claude", "settings.local.json")
|
|
7480
7695
|
];
|
|
7481
7696
|
for (const cwd of projectCwds) {
|
|
7482
|
-
configPaths.push(
|
|
7483
|
-
configPaths.push(
|
|
7484
|
-
configPaths.push(
|
|
7697
|
+
configPaths.push(join26(cwd, ".mcp.json"));
|
|
7698
|
+
configPaths.push(join26(cwd, ".claude", "settings.json"));
|
|
7699
|
+
configPaths.push(join26(cwd, ".claude", "settings.local.json"));
|
|
7485
7700
|
}
|
|
7486
7701
|
for (const p of configPaths) {
|
|
7487
|
-
if (!
|
|
7702
|
+
if (!existsSync6(p)) continue;
|
|
7488
7703
|
const config = readJsonFile(p);
|
|
7489
7704
|
if (!config) continue;
|
|
7490
7705
|
let mtime = 0;
|
|
@@ -7634,13 +7849,13 @@ function expandImports(filePath, seen, depth) {
|
|
|
7634
7849
|
if (content === null) return { totalLines: 0, importedFiles: 0 };
|
|
7635
7850
|
let totalLines = content.split("\n").length;
|
|
7636
7851
|
let importedFiles = 0;
|
|
7637
|
-
const dir =
|
|
7852
|
+
const dir = join26(filePath, "..");
|
|
7638
7853
|
IMPORT_PATTERN.lastIndex = 0;
|
|
7639
7854
|
for (const match of content.matchAll(IMPORT_PATTERN)) {
|
|
7640
7855
|
const rawPath = match[1];
|
|
7641
7856
|
if (!rawPath) continue;
|
|
7642
|
-
const resolved = rawPath.startsWith("/") ? rawPath :
|
|
7643
|
-
if (!
|
|
7857
|
+
const resolved = rawPath.startsWith("/") ? rawPath : join26(dir, rawPath);
|
|
7858
|
+
if (!existsSync6(resolved)) continue;
|
|
7644
7859
|
const nested = expandImports(resolved, seen, depth + 1);
|
|
7645
7860
|
totalLines += nested.totalLines;
|
|
7646
7861
|
importedFiles += 1 + nested.importedFiles;
|
|
@@ -7651,8 +7866,8 @@ function detectBloatedClaudeMd(projectCwds) {
|
|
|
7651
7866
|
const bloated = [];
|
|
7652
7867
|
for (const cwd of projectCwds) {
|
|
7653
7868
|
for (const name of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
7654
|
-
const fullPath =
|
|
7655
|
-
if (!
|
|
7869
|
+
const fullPath = join26(cwd, name);
|
|
7870
|
+
if (!existsSync6(fullPath)) continue;
|
|
7656
7871
|
const { totalLines, importedFiles } = expandImports(fullPath, /* @__PURE__ */ new Set(), 0);
|
|
7657
7872
|
if (totalLines > CLAUDEMD_HEALTHY_LINES) {
|
|
7658
7873
|
bloated.push({ path: `${shortHomePath(cwd)}/${name}`, expandedLines: totalLines, imports: importedFiles });
|
|
@@ -7780,7 +7995,7 @@ function detectCacheBloat(apiCalls, projects, dateRange) {
|
|
|
7780
7995
|
};
|
|
7781
7996
|
}
|
|
7782
7997
|
async function listMarkdownFiles(dir) {
|
|
7783
|
-
if (!
|
|
7998
|
+
if (!existsSync6(dir)) return [];
|
|
7784
7999
|
try {
|
|
7785
8000
|
const entries = await readdir13(dir);
|
|
7786
8001
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(/\.md$/, ""));
|
|
@@ -7789,12 +8004,12 @@ async function listMarkdownFiles(dir) {
|
|
|
7789
8004
|
}
|
|
7790
8005
|
}
|
|
7791
8006
|
async function listSkillDirs(dir) {
|
|
7792
|
-
if (!
|
|
8007
|
+
if (!existsSync6(dir)) return [];
|
|
7793
8008
|
try {
|
|
7794
8009
|
const entries = await readdir13(dir);
|
|
7795
8010
|
const names = [];
|
|
7796
8011
|
for (const entry of entries) {
|
|
7797
|
-
if (
|
|
8012
|
+
if (existsSync6(join26(dir, entry, "SKILL.md"))) names.push(entry);
|
|
7798
8013
|
}
|
|
7799
8014
|
return names;
|
|
7800
8015
|
} catch {
|
|
@@ -7802,7 +8017,7 @@ async function listSkillDirs(dir) {
|
|
|
7802
8017
|
}
|
|
7803
8018
|
}
|
|
7804
8019
|
async function detectGhostAgents(calls) {
|
|
7805
|
-
const defined = await listMarkdownFiles(
|
|
8020
|
+
const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "agents"));
|
|
7806
8021
|
if (defined.length === 0) return null;
|
|
7807
8022
|
const invoked = /* @__PURE__ */ new Set();
|
|
7808
8023
|
for (const call of calls) {
|
|
@@ -7827,7 +8042,7 @@ async function detectGhostAgents(calls) {
|
|
|
7827
8042
|
};
|
|
7828
8043
|
}
|
|
7829
8044
|
async function detectGhostSkills(calls) {
|
|
7830
|
-
const defined = await listSkillDirs(
|
|
8045
|
+
const defined = await listSkillDirs(join26(homedir13(), ".claude", "skills"));
|
|
7831
8046
|
if (defined.length === 0) return null;
|
|
7832
8047
|
const invoked = /* @__PURE__ */ new Set();
|
|
7833
8048
|
for (const call of calls) {
|
|
@@ -7852,7 +8067,7 @@ async function detectGhostSkills(calls) {
|
|
|
7852
8067
|
};
|
|
7853
8068
|
}
|
|
7854
8069
|
async function detectGhostCommands(userMessages) {
|
|
7855
|
-
const defined = await listMarkdownFiles(
|
|
8070
|
+
const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "commands"));
|
|
7856
8071
|
if (defined.length === 0) return null;
|
|
7857
8072
|
const invoked = /* @__PURE__ */ new Set();
|
|
7858
8073
|
for (const msg of userMessages) {
|
|
@@ -7880,8 +8095,8 @@ async function detectGhostCommands(userMessages) {
|
|
|
7880
8095
|
}
|
|
7881
8096
|
function readShellProfileLimit() {
|
|
7882
8097
|
for (const profile of SHELL_PROFILES) {
|
|
7883
|
-
const path =
|
|
7884
|
-
if (!
|
|
8098
|
+
const path = join26(homedir13(), profile);
|
|
8099
|
+
if (!existsSync6(path)) continue;
|
|
7885
8100
|
const content = readSessionFileSync(path);
|
|
7886
8101
|
if (content === null) continue;
|
|
7887
8102
|
const match = content.match(/^\s*export\s+BASH_MAX_OUTPUT_LENGTH\s*=\s*['"]?(\d+)['"]?/m);
|
|
@@ -8104,7 +8319,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
8104
8319
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8105
8320
|
import { stat as stat18, writeFile as writeFile5 } from "fs/promises";
|
|
8106
8321
|
import { homedir as homedir14 } from "os";
|
|
8107
|
-
import { join as
|
|
8322
|
+
import { join as join27, resolve as resolve3 } from "path";
|
|
8108
8323
|
function codeBlock(content, language) {
|
|
8109
8324
|
const longest = (content.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
|
|
8110
8325
|
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
@@ -8198,7 +8413,7 @@ async function copyOptimizeMarkdown(markdown) {
|
|
|
8198
8413
|
throw new Error("Could not copy. Press e to save a Markdown file instead.");
|
|
8199
8414
|
}
|
|
8200
8415
|
async function saveOptimizeMarkdown(markdown, directory) {
|
|
8201
|
-
const downloads =
|
|
8416
|
+
const downloads = join27(homedir14(), "Downloads");
|
|
8202
8417
|
const folder = directory ?? ((await stat18(downloads).catch(() => null))?.isDirectory() ? downloads : process.cwd());
|
|
8203
8418
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8204
8419
|
const path = resolve3(folder, `exe-watcher-optimizer-${stamp}-${randomUUID4().slice(0, 8)}.md`);
|
|
@@ -8209,8 +8424,8 @@ async function saveOptimizeMarkdown(markdown, directory) {
|
|
|
8209
8424
|
// src/context-budget.ts
|
|
8210
8425
|
init_fs_utils();
|
|
8211
8426
|
import { readdir as readdir14 } from "fs/promises";
|
|
8212
|
-
import { existsSync as
|
|
8213
|
-
import { join as
|
|
8427
|
+
import { existsSync as existsSync7 } from "fs";
|
|
8428
|
+
import { join as join28 } from "path";
|
|
8214
8429
|
import { homedir as homedir15 } from "os";
|
|
8215
8430
|
var CHARS_PER_TOKEN2 = 4;
|
|
8216
8431
|
var SYSTEM_BASE_TOKENS = 10400;
|
|
@@ -8220,7 +8435,7 @@ function estimateTokens2(text) {
|
|
|
8220
8435
|
return Math.ceil(text.length / CHARS_PER_TOKEN2);
|
|
8221
8436
|
}
|
|
8222
8437
|
async function readConfigFile(path) {
|
|
8223
|
-
if (!
|
|
8438
|
+
if (!existsSync7(path)) return null;
|
|
8224
8439
|
const raw = await readSessionFile(path);
|
|
8225
8440
|
if (raw === null) return null;
|
|
8226
8441
|
try {
|
|
@@ -8232,13 +8447,13 @@ async function readConfigFile(path) {
|
|
|
8232
8447
|
async function countMcpTools(projectPath) {
|
|
8233
8448
|
const home = homedir15();
|
|
8234
8449
|
const configPaths = [
|
|
8235
|
-
|
|
8236
|
-
|
|
8450
|
+
join28(home, ".claude", "settings.json"),
|
|
8451
|
+
join28(home, ".claude", "settings.local.json")
|
|
8237
8452
|
];
|
|
8238
8453
|
if (projectPath) {
|
|
8239
|
-
configPaths.push(
|
|
8240
|
-
configPaths.push(
|
|
8241
|
-
configPaths.push(
|
|
8454
|
+
configPaths.push(join28(projectPath, ".mcp.json"));
|
|
8455
|
+
configPaths.push(join28(projectPath, ".claude", "settings.json"));
|
|
8456
|
+
configPaths.push(join28(projectPath, ".claude", "settings.local.json"));
|
|
8242
8457
|
}
|
|
8243
8458
|
const servers = /* @__PURE__ */ new Set();
|
|
8244
8459
|
let toolCount = 0;
|
|
@@ -8255,16 +8470,16 @@ async function countMcpTools(projectPath) {
|
|
|
8255
8470
|
return toolCount;
|
|
8256
8471
|
}
|
|
8257
8472
|
async function countSkills(projectPath) {
|
|
8258
|
-
const dirs = [
|
|
8259
|
-
if (projectPath) dirs.push(
|
|
8473
|
+
const dirs = [join28(homedir15(), ".claude", "skills")];
|
|
8474
|
+
if (projectPath) dirs.push(join28(projectPath, ".claude", "skills"));
|
|
8260
8475
|
let count = 0;
|
|
8261
8476
|
for (const dir of dirs) {
|
|
8262
|
-
if (!
|
|
8477
|
+
if (!existsSync7(dir)) continue;
|
|
8263
8478
|
try {
|
|
8264
8479
|
const entries = await readdir14(dir);
|
|
8265
8480
|
for (const entry of entries) {
|
|
8266
|
-
const skillFile =
|
|
8267
|
-
if (
|
|
8481
|
+
const skillFile = join28(dir, entry, "SKILL.md");
|
|
8482
|
+
if (existsSync7(skillFile)) count++;
|
|
8268
8483
|
}
|
|
8269
8484
|
} catch {
|
|
8270
8485
|
continue;
|
|
@@ -8276,15 +8491,15 @@ async function scanMemoryFiles(projectPath) {
|
|
|
8276
8491
|
const home = homedir15();
|
|
8277
8492
|
const files = [];
|
|
8278
8493
|
const paths = [
|
|
8279
|
-
{ path:
|
|
8494
|
+
{ path: join28(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
|
|
8280
8495
|
];
|
|
8281
8496
|
if (projectPath) {
|
|
8282
|
-
paths.push({ path:
|
|
8283
|
-
paths.push({ path:
|
|
8284
|
-
paths.push({ path:
|
|
8497
|
+
paths.push({ path: join28(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
|
|
8498
|
+
paths.push({ path: join28(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
|
|
8499
|
+
paths.push({ path: join28(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
|
|
8285
8500
|
}
|
|
8286
8501
|
for (const { path, name } of paths) {
|
|
8287
|
-
if (!
|
|
8502
|
+
if (!existsSync7(path)) continue;
|
|
8288
8503
|
const content = await readSessionFile(path);
|
|
8289
8504
|
if (content === null) continue;
|
|
8290
8505
|
files.push({ name, tokens: estimateTokens2(content) });
|
|
@@ -8318,7 +8533,7 @@ async function discoverProjectCwd(sessionDir) {
|
|
|
8318
8533
|
if (files.length === 0) return null;
|
|
8319
8534
|
const firstFile = files[0];
|
|
8320
8535
|
if (!firstFile) return null;
|
|
8321
|
-
const content = await readSessionFile(
|
|
8536
|
+
const content = await readSessionFile(join28(sessionDir, firstFile));
|
|
8322
8537
|
if (content === null) return null;
|
|
8323
8538
|
for (const line of content.split("\n")) {
|
|
8324
8539
|
if (!line.trim()) continue;
|
|
@@ -9012,20 +9227,20 @@ function owlFrame(tick) {
|
|
|
9012
9227
|
}
|
|
9013
9228
|
|
|
9014
9229
|
// src/dashboard.tsx
|
|
9015
|
-
import { join as
|
|
9230
|
+
import { join as join30 } from "path";
|
|
9016
9231
|
|
|
9017
9232
|
// src/time-index.ts
|
|
9018
9233
|
init_cache_dir();
|
|
9019
9234
|
init_file_lock();
|
|
9020
9235
|
init_resource_budget();
|
|
9021
|
-
import { createHash as
|
|
9236
|
+
import { createHash as createHash9, randomUUID as randomUUID5 } from "crypto";
|
|
9022
9237
|
import { mkdir as mkdir13, open as open9, readFile as readFile14, rename as rename11, stat as stat19, unlink as unlink9, readdir as readdir15 } from "fs/promises";
|
|
9023
|
-
import { basename as basename11, dirname as dirname6, join as
|
|
9238
|
+
import { basename as basename11, dirname as dirname6, join as join29 } from "path";
|
|
9024
9239
|
import { gzip as gzip2, gunzip as gunzip2 } from "zlib";
|
|
9025
9240
|
import { promisify as promisify2 } from "util";
|
|
9026
9241
|
|
|
9027
9242
|
// src/time-tracking.ts
|
|
9028
|
-
import { createHash as
|
|
9243
|
+
import { createHash as createHash8 } from "crypto";
|
|
9029
9244
|
var TIME_HISTORY_START = (/* @__PURE__ */ new Date("2025-07-01T00:00:00")).getTime();
|
|
9030
9245
|
var TIME_PROVIDERS = ["claude", "codex", "cursor", "cursor-agent", "copilot", "opencode", "kimi", "pi", "omp"];
|
|
9031
9246
|
function timestamp(value) {
|
|
@@ -9238,7 +9453,7 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9238
9453
|
runnableFiles: pending.length - waitingFiles,
|
|
9239
9454
|
waitingFiles,
|
|
9240
9455
|
pendingBytes: pending.reduce((n, f) => n + Math.max(0, f.size - f.offset), 0),
|
|
9241
|
-
token:
|
|
9456
|
+
token: createHash8("sha256").update(files.map((f) => `${f.identity}:${f.size}:${f.mtime}:${f.offset}:${f.dbOffset ?? 0}:${!!f.waitingForAppend}:${!!f.unavailable}`).join("|")).digest("hex")
|
|
9242
9457
|
};
|
|
9243
9458
|
return {
|
|
9244
9459
|
indexedAt: index.updatedAt,
|
|
@@ -9371,7 +9586,7 @@ var MAX_DISK2 = 16 * 1024 ** 2;
|
|
|
9371
9586
|
var MAX_FILES = 2e4;
|
|
9372
9587
|
var BLOCK = 2 * 1024 ** 2;
|
|
9373
9588
|
var adapterVersion = (provider) => provider === "opencode" ? 2 : 1;
|
|
9374
|
-
var indexPath2 = () =>
|
|
9589
|
+
var indexPath2 = () => join29(getCacheDir(), "time-index-v1.json.gz");
|
|
9375
9590
|
var empty = () => ({ version: 1, files: /* @__PURE__ */ Object.create(null), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), warnings: [] });
|
|
9376
9591
|
async function readTimeIndex() {
|
|
9377
9592
|
try {
|
|
@@ -9434,11 +9649,11 @@ async function expandSources(sources, warnings) {
|
|
|
9434
9649
|
continue;
|
|
9435
9650
|
}
|
|
9436
9651
|
for (const entry of await readdir15(source.path, { withFileTypes: true }).catch(() => [])) {
|
|
9437
|
-
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
9652
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join29(source.path, entry.name) });
|
|
9438
9653
|
else if (entry.isDirectory()) {
|
|
9439
|
-
const folder =
|
|
9654
|
+
const folder = join29(source.path, entry.name, "subagents");
|
|
9440
9655
|
for (const agent of await readdir15(folder, { withFileTypes: true }).catch(() => [])) {
|
|
9441
|
-
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
9656
|
+
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join29(folder, agent.name) });
|
|
9442
9657
|
}
|
|
9443
9658
|
}
|
|
9444
9659
|
}
|
|
@@ -9452,7 +9667,7 @@ async function anchor(path, offset) {
|
|
|
9452
9667
|
await handle.read(head, 0, head.length, 0);
|
|
9453
9668
|
await handle.read(tail, 0, tail.length, Math.max(0, offset - tail.length));
|
|
9454
9669
|
chargeRead(head.length + tail.length);
|
|
9455
|
-
return
|
|
9670
|
+
return createHash9("sha256").update(head).update(tail).digest("hex");
|
|
9456
9671
|
} finally {
|
|
9457
9672
|
await handle.close();
|
|
9458
9673
|
}
|
|
@@ -9535,7 +9750,7 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9535
9750
|
let current;
|
|
9536
9751
|
let release;
|
|
9537
9752
|
try {
|
|
9538
|
-
release = await acquireFileLock(
|
|
9753
|
+
release = await acquireFileLock(join29(getCacheDir(), "time-index.lock"), 100);
|
|
9539
9754
|
} catch {
|
|
9540
9755
|
current = await readTimeIndex();
|
|
9541
9756
|
current.warnings.push("Another timing update is running; showing the saved timing snapshot.");
|
|
@@ -9555,17 +9770,17 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9555
9770
|
let fileCount = Object.keys(current.files).length;
|
|
9556
9771
|
for (const source of expanded) {
|
|
9557
9772
|
chargeRead(0);
|
|
9558
|
-
const
|
|
9559
|
-
const s = await stat19(
|
|
9773
|
+
const database2 = source.provider === "opencode" ? source.path.slice(0, source.path.lastIndexOf(":")) : source.path;
|
|
9774
|
+
const s = await stat19(database2).catch(() => null);
|
|
9560
9775
|
if (!s) continue;
|
|
9561
|
-
const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(
|
|
9776
|
+
const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(database2 + "-wal").catch(() => null) : null;
|
|
9562
9777
|
candidates.push({ source, s, wal });
|
|
9563
9778
|
}
|
|
9564
9779
|
candidates.sort((a, b) => b.s.mtimeMs - a.s.mtimeMs || a.source.path.localeCompare(b.source.path));
|
|
9565
9780
|
if (candidates.length > MAX_FILES) current.warnings.push("Some timing sources exceed the bounded file limit; their time remains unknown.");
|
|
9566
9781
|
const deadline = Date.now() + (options.history ? 15e3 : 750);
|
|
9567
9782
|
for (const { source, s, wal } of candidates) {
|
|
9568
|
-
const key2 =
|
|
9783
|
+
const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
|
|
9569
9784
|
if (!current.files[key2] && fileCount < MAX_FILES) {
|
|
9570
9785
|
current.files[key2] = fresh(source, `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`, s.size, s.mtimeMs, s.ctimeMs);
|
|
9571
9786
|
changed = true;
|
|
@@ -9577,7 +9792,7 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9577
9792
|
}
|
|
9578
9793
|
for (const { source, s, wal } of candidates) {
|
|
9579
9794
|
chargeRead(0);
|
|
9580
|
-
const key2 =
|
|
9795
|
+
const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
|
|
9581
9796
|
const identity = `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`;
|
|
9582
9797
|
let row = current.files[key2];
|
|
9583
9798
|
const sameAdapter = (row?.adapterVersion ?? 1) === adapterVersion(source.provider);
|
|
@@ -10482,11 +10697,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
|
|
|
10482
10697
|
useEffect2(() => {
|
|
10483
10698
|
let cancelled = false;
|
|
10484
10699
|
async function loadBudgets() {
|
|
10485
|
-
const claudeDir =
|
|
10700
|
+
const claudeDir = join30(homedir16(), ".claude", "projects");
|
|
10486
10701
|
const budgets = /* @__PURE__ */ new Map();
|
|
10487
10702
|
for (const project of projects.slice(0, 8)) {
|
|
10488
10703
|
if (cancelled) return;
|
|
10489
|
-
const cwd = await discoverProjectCwd(
|
|
10704
|
+
const cwd = await discoverProjectCwd(join30(claudeDir, project.project));
|
|
10490
10705
|
if (!cwd) continue;
|
|
10491
10706
|
budgets.set(project.project, await estimateContextBudget(cwd));
|
|
10492
10707
|
}
|
|
@@ -10809,6 +11024,10 @@ function shouldSkipBlockedChunk(cursor2, date2, maxAttempts = MAX_BLOCKED_CHUNK_
|
|
|
10809
11024
|
// src/period-snapshots.ts
|
|
10810
11025
|
init_day_aggregator();
|
|
10811
11026
|
init_models();
|
|
11027
|
+
import { createHash as createHash10 } from "crypto";
|
|
11028
|
+
function hasRecordedHistory(cache, today) {
|
|
11029
|
+
return cache.days.some((day) => day.date < today && (day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0));
|
|
11030
|
+
}
|
|
10812
11031
|
function assertHistoryComplete(cache, start, today) {
|
|
10813
11032
|
if (start >= today) return;
|
|
10814
11033
|
const required = Math.round((Date.parse(today) - Date.parse(start)) / 864e5);
|
|
@@ -10830,7 +11049,27 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10830
11049
|
for (const window of windows) {
|
|
10831
11050
|
const required = Math.max(0, Math.round((Date.parse(today) - Date.parse(window.start)) / 864e5));
|
|
10832
11051
|
const covered = [...dates].filter((d) => d >= window.start && !partial.has(d)).length;
|
|
10833
|
-
|
|
11052
|
+
const incomplete = window.key !== "today" && (cache.needsRepricing || cache.historyBuild || cache.missingProviders?.length || covered !== required);
|
|
11053
|
+
const committed = closed.filter((day) => day.date >= window.start).sort((a, b) => a.date.localeCompare(b.date));
|
|
11054
|
+
const recorded = (day) => day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0;
|
|
11055
|
+
const hasData = committed.some(recorded) || window.key !== "all" && live2.some(recorded);
|
|
11056
|
+
const savedHistory = incomplete && (window.key !== "all" || hasData) ? {
|
|
11057
|
+
through: committed.at(-1)?.date ?? today,
|
|
11058
|
+
hasData,
|
|
11059
|
+
progressToken: createHash10("sha256").update(JSON.stringify([
|
|
11060
|
+
window.start,
|
|
11061
|
+
cache.lastComputedDate,
|
|
11062
|
+
cache.days.length,
|
|
11063
|
+
cache.historyBuild?.completedProviders,
|
|
11064
|
+
cache.historyBuild?.chunked,
|
|
11065
|
+
cache.missingProviders,
|
|
11066
|
+
cache.partialDates,
|
|
11067
|
+
cache.backfill,
|
|
11068
|
+
cache.historyScanProgress
|
|
11069
|
+
])).digest("hex"),
|
|
11070
|
+
message: window.key === "all" ? `Saved history through ${committed.at(-1).date}${live2.length ? ", plus today" : ""}. Some history is still missing; this is not the complete All total.` : `${hasData ? "Showing known usage" : "Recovering local usage"} for ${window.label}. ${covered} of ${required} historical days loaded${live2.some(recorded) ? ", plus today" : ""}. Missing days are unknown; this total is incomplete.`
|
|
11071
|
+
} : void 0;
|
|
11072
|
+
if (incomplete && !savedHistory) continue;
|
|
10834
11073
|
const selected = periodDays(window.start);
|
|
10835
11074
|
const scoped = provider === "all" ? selected : filterDaysToProvider(selected, provider);
|
|
10836
11075
|
const data = buildPeriodDataFromDays(scoped, window.label);
|
|
@@ -10852,6 +11091,7 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10852
11091
|
const providers = provider === "all" ? Object.entries(totals).map(([name, cost]) => ({ name: names.get(name) ?? name, cost })) : [{ name: names.get(provider) ?? provider, cost: data.cost }];
|
|
10853
11092
|
const built = buildMenubarPayload(data, providers, null);
|
|
10854
11093
|
result[window.key] = {
|
|
11094
|
+
...savedHistory ? { savedHistory } : {},
|
|
10855
11095
|
current: built.current,
|
|
10856
11096
|
projectSpend: buildProjectSpendFromDays(selected, live2, periodDays(week.start), periodDays(month.start)),
|
|
10857
11097
|
diagnostics: { daysCount: selected.length, parseTimeMs, warnings: notes },
|
|
@@ -10862,6 +11102,23 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10862
11102
|
return result;
|
|
10863
11103
|
}
|
|
10864
11104
|
|
|
11105
|
+
// src/saved-history.ts
|
|
11106
|
+
init_day_aggregator();
|
|
11107
|
+
function buildSavedHistory(cache, today, provider, window = { start: "0000-01-01", label: "All Time" }) {
|
|
11108
|
+
if (!hasRecordedHistory(cache, today)) throw new Error("No saved history yet. Watcher is recovering local usage.");
|
|
11109
|
+
const closed = cache.days.filter((day) => day.date >= window.start && day.date < today).sort((a, b) => a.date.localeCompare(b.date));
|
|
11110
|
+
if (!closed.some((day) => day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0)) throw new Error("No saved usage in this period yet. Watcher is recovering local usage.");
|
|
11111
|
+
const days = provider === "all" ? closed : filterDaysToProvider(closed, provider);
|
|
11112
|
+
const current = buildPeriodDataFromDays(days, window.label);
|
|
11113
|
+
const totals = /* @__PURE__ */ new Map();
|
|
11114
|
+
for (const day of days) for (const [name, data] of Object.entries(day.providers)) totals.set(name, (totals.get(name) ?? 0) + data.cost);
|
|
11115
|
+
const payload = buildMenubarPayload(current, [...totals].map(([name, cost]) => ({ name, cost })), null);
|
|
11116
|
+
return { ...payload, savedHistory: {
|
|
11117
|
+
through: closed.at(-1).date,
|
|
11118
|
+
message: `Saved ${window.label} history through ${closed.at(-1).date}. Newer usage has not loaded yet.`
|
|
11119
|
+
} };
|
|
11120
|
+
}
|
|
11121
|
+
|
|
10865
11122
|
// src/history-build.ts
|
|
10866
11123
|
init_day_aggregator();
|
|
10867
11124
|
init_report_summary();
|
|
@@ -10960,8 +11217,9 @@ async function completeHistoryBuild(cache, start, end, providers, scan, save2, o
|
|
|
10960
11217
|
}
|
|
10961
11218
|
|
|
10962
11219
|
// src/cli.ts
|
|
11220
|
+
init_parsed_file_cache();
|
|
10963
11221
|
import { createInterface } from "readline";
|
|
10964
|
-
import { createRequire as
|
|
11222
|
+
import { createRequire as createRequire3 } from "module";
|
|
10965
11223
|
|
|
10966
11224
|
// src/menubar-lifecycle.ts
|
|
10967
11225
|
var live = { parent: () => process.ppid, checkAlive: (pid) => {
|
|
@@ -10995,8 +11253,8 @@ function buildTimeSnapshots(index, ranges, provider = "all", include, exclude) {
|
|
|
10995
11253
|
}
|
|
10996
11254
|
|
|
10997
11255
|
// src/cli.ts
|
|
10998
|
-
var
|
|
10999
|
-
var { version } =
|
|
11256
|
+
var require3 = createRequire3(import.meta.url);
|
|
11257
|
+
var { version } = require3("../package.json");
|
|
11000
11258
|
var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
|
|
11001
11259
|
var configuredHistoryStart = null;
|
|
11002
11260
|
function toDateString(date2) {
|
|
@@ -11111,14 +11369,14 @@ async function runJsonReport(period, provider, project, exclude) {
|
|
|
11111
11369
|
console.log(JSON.stringify({ ...report, pricing: getPricingMetadata() }, null, 2));
|
|
11112
11370
|
}
|
|
11113
11371
|
var program = new Command().name("exe-watcher").description("See where your AI coding tokens go - by task, tool, model, and project").version(version).option("--verbose", "print warnings to stderr on read failures and skipped files");
|
|
11114
|
-
program.hook("preAction", async (thisCommand) => {
|
|
11372
|
+
program.hook("preAction", async (thisCommand, command) => {
|
|
11115
11373
|
const config = await readConfig();
|
|
11116
11374
|
setModelAliases(config.modelAliases ?? {});
|
|
11117
11375
|
configuredHistoryStart = parseHistoryStartDate(config.historyStartDate);
|
|
11118
11376
|
if (thisCommand.opts().verbose) {
|
|
11119
11377
|
process.env["EXE_WATCHER_VERBOSE"] = "1";
|
|
11120
11378
|
}
|
|
11121
|
-
await loadCurrency();
|
|
11379
|
+
if (!command.opts().cachedOnly) await loadCurrency();
|
|
11122
11380
|
});
|
|
11123
11381
|
function buildJsonReport(projects, period, periodKey) {
|
|
11124
11382
|
const sessions = projects.flatMap((p) => p.sessions);
|
|
@@ -11308,7 +11566,19 @@ function buildPeriodData(label, projects) {
|
|
|
11308
11566
|
models: Object.entries(modelTotals).sort(([, a], [, b]) => b.cost - a.cost).map(([name, d]) => ({ name, ...d }))
|
|
11309
11567
|
};
|
|
11310
11568
|
}
|
|
11311
|
-
program.command("status").description("Compact status output (today + week + month)").option("--format <format>", "Output format: terminal, menubar-json, json", "terminal").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor, cursor-agent, copilot, opencode, kimi, pi, omp", "all").option("--project <name>", "Show only projects matching name (repeatable)", collect, []).option("--exclude <name>", "Exclude projects matching name (repeatable)", collect, []).option("--period <period>", "Primary period for menubar-json: today, week, 30days, month, all", "today").option("--no-optimize", "Skip optimize findings (menubar-json only, faster)").action(async (opts) => {
|
|
11569
|
+
program.command("status").description("Compact status output (today + week + month)").option("--format <format>", "Output format: terminal, menubar-json, json", "terminal").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor, cursor-agent, copilot, opencode, kimi, pi, omp", "all").option("--project <name>", "Show only projects matching name (repeatable)", collect, []).option("--exclude <name>", "Exclude projects matching name (repeatable)", collect, []).option("--period <period>", "Primary period for menubar-json: today, week, 30days, month, all", "today").option("--no-optimize", "Skip optimize findings (menubar-json only, faster)").option("--cached-only", "Read saved period totals without scanning or networking (menubar-json only)").action(async (opts) => {
|
|
11570
|
+
if (opts.cachedOnly) {
|
|
11571
|
+
if (opts.format !== "menubar-json" || !["week", "30days", "month", "all"].includes(opts.period) || opts.optimize !== false) throw new Error("--cached-only requires menubar-json, a historical period and --no-optimize");
|
|
11572
|
+
const cache = await loadDailyCache(buildDailyCacheScopeKey(opts.project, opts.exclude));
|
|
11573
|
+
const window = getDateRange2(opts.period);
|
|
11574
|
+
console.log(JSON.stringify(buildSavedHistory(
|
|
11575
|
+
cache,
|
|
11576
|
+
toDateString(/* @__PURE__ */ new Date()),
|
|
11577
|
+
opts.provider,
|
|
11578
|
+
{ start: opts.period === "all" ? "0000-01-01" : toDateString(window.range.start), label: window.label }
|
|
11579
|
+
)));
|
|
11580
|
+
return;
|
|
11581
|
+
}
|
|
11312
11582
|
await loadPricing();
|
|
11313
11583
|
const pf = opts.provider;
|
|
11314
11584
|
const fp = (p) => filterProjectsByName(p, opts.project, opts.exclude);
|
|
@@ -11327,20 +11597,31 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11327
11597
|
const isAllProviders = pf === "all";
|
|
11328
11598
|
const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
|
|
11329
11599
|
const backfillWarnings = [];
|
|
11330
|
-
|
|
11600
|
+
let savedParserProgress = parsedFileProgress();
|
|
11601
|
+
const saveHistoryCache = async (c) => {
|
|
11602
|
+
const progress = parsedFileProgress();
|
|
11603
|
+
const advanced = progress > savedParserProgress;
|
|
11604
|
+
c.historyScanProgress = (c.historyScanProgress ?? 0) + progress - savedParserProgress;
|
|
11605
|
+
savedParserProgress = progress;
|
|
11606
|
+
if (c.backfill) {
|
|
11607
|
+
c.backfill.attempts = advanced ? 0 : Math.min(c.backfill.attempts, MAX_BLOCKED_CHUNK_ATTEMPTS);
|
|
11608
|
+
}
|
|
11609
|
+
await saveDailyCache(c);
|
|
11610
|
+
};
|
|
11611
|
+
const runChunkedBackfill = async (cache2, start, end, order, repair = false) => {
|
|
11331
11612
|
let c = cache2;
|
|
11332
11613
|
const chunks = planBackfillChunks(start, end);
|
|
11333
11614
|
if (order === "newest-first") chunks.reverse();
|
|
11334
11615
|
for (const chunk of chunks) {
|
|
11335
11616
|
const chunkDate = toDateString(chunk.start);
|
|
11336
11617
|
const coveredThrough = toDateString(chunk.end);
|
|
11337
|
-
if (shouldSkipBlockedChunk(c.backfill, chunkDate)) {
|
|
11618
|
+
if (!repair && shouldSkipBlockedChunk(c.backfill, chunkDate)) {
|
|
11338
11619
|
const blanks = fillMissingDays(chunk.start, chunk.end, []);
|
|
11339
11620
|
c = addNewDays(c, blanks, yesterdayStr, { coveredThrough });
|
|
11340
11621
|
c.partialDates = [.../* @__PURE__ */ new Set([...c.partialDates ?? [], ...blanks.map((d) => d.date)])];
|
|
11341
11622
|
c.backfill = null;
|
|
11342
11623
|
c.revalidatedAt = Date.now();
|
|
11343
|
-
await
|
|
11624
|
+
await saveHistoryCache(c);
|
|
11344
11625
|
const warn2 = `history for ${chunkDate} exceeded the scan budget ${MAX_BLOCKED_CHUNK_ATTEMPTS} times; recorded as incomplete so newer days can load`;
|
|
11345
11626
|
backfillWarnings.push(warn2);
|
|
11346
11627
|
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
@@ -11352,14 +11633,15 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11352
11633
|
const chunkProjects = filterProjectsByName(await parseAllSessions(chunkRange, "all", true), opts.project, opts.exclude);
|
|
11353
11634
|
const chunkDays = fillMissingDays(chunk.start, chunk.end, aggregateProjectsIntoDays(chunkProjects));
|
|
11354
11635
|
c = addNewDays(c, chunkDays, yesterdayStr, { coveredThrough });
|
|
11636
|
+
c.partialDates = (c.partialDates ?? []).filter((date2) => date2 < chunkDate || date2 > coveredThrough);
|
|
11355
11637
|
c.backfill = null;
|
|
11356
11638
|
c.revalidatedAt = Date.now();
|
|
11357
|
-
await
|
|
11639
|
+
await saveHistoryCache(c);
|
|
11358
11640
|
} catch (err) {
|
|
11359
11641
|
if (!(err instanceof ResourceBudgetError)) throw err;
|
|
11360
11642
|
c.backfill = nextBackfillCursor(c.backfill, chunkDate);
|
|
11361
11643
|
c.revalidatedAt = Date.now();
|
|
11362
|
-
await
|
|
11644
|
+
await saveHistoryCache(c);
|
|
11363
11645
|
const warn2 = `history backfill paused at ${chunkDate} (attempt ${c.backfill.attempts}): ${err.message}`;
|
|
11364
11646
|
backfillWarnings.push(warn2);
|
|
11365
11647
|
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
@@ -11380,7 +11662,7 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11380
11662
|
opts.project,
|
|
11381
11663
|
opts.exclude
|
|
11382
11664
|
)),
|
|
11383
|
-
|
|
11665
|
+
saveHistoryCache,
|
|
11384
11666
|
{ mergeExisting }
|
|
11385
11667
|
);
|
|
11386
11668
|
const pausedHistory = (c) => {
|
|
@@ -11388,8 +11670,9 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11388
11670
|
backfillWarnings.push(`History rebuild paused at ${c.historyBuild.chunked?.nextDate ?? c.historyBuild.start}; completed scans are saved and will resume on the next historical refresh.`);
|
|
11389
11671
|
return true;
|
|
11390
11672
|
};
|
|
11391
|
-
const cache = await withDailyCacheLock(async () => {
|
|
11673
|
+
const cache = await withScanBudget(() => withScanSlice(() => withDailyCacheLock(async () => {
|
|
11392
11674
|
let c = await loadDailyCache(dailyCacheScope);
|
|
11675
|
+
const repairDates = [...new Set(c.partialDates ?? [])].sort();
|
|
11393
11676
|
if (opts.period === "today" && opts.optimize === false) return c;
|
|
11394
11677
|
if (c.missingProviders?.length && c.days.length && !c.needsRepricing) {
|
|
11395
11678
|
const from = parseHistoryStartDate(c.days[0].date);
|
|
@@ -11405,19 +11688,13 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11405
11688
|
c = await buildHistory2(c, from, through);
|
|
11406
11689
|
if (pausedHistory(c)) return c;
|
|
11407
11690
|
}
|
|
11408
|
-
if (
|
|
11691
|
+
if (opts.period === "all" && c.days.length === 0) {
|
|
11409
11692
|
const start = parseHistoryStartDate(c.previousHistoryStart) ?? historyStart;
|
|
11410
11693
|
if (start <= yesterdayEnd) {
|
|
11411
11694
|
c = await buildHistory2(c, start, yesterdayEnd);
|
|
11412
11695
|
if (pausedHistory(c)) return c;
|
|
11413
11696
|
}
|
|
11414
11697
|
}
|
|
11415
|
-
const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
|
|
11416
|
-
if (hadYesterday && (!c.revalidatedAt || Date.now() - c.revalidatedAt >= 5 * 6e4)) {
|
|
11417
|
-
const freshDays = c.days.filter((d) => d.date < yesterdayStr);
|
|
11418
|
-
const latestFresh = freshDays.length > 0 ? freshDays[freshDays.length - 1]?.date ?? null : null;
|
|
11419
|
-
c = { ...c, days: freshDays, lastComputedDate: latestFresh };
|
|
11420
|
-
}
|
|
11421
11698
|
const oldestCachedDate = c.days.length > 0 ? c.days[0]?.date ?? null : null;
|
|
11422
11699
|
const effectiveGapStart = computeProgressiveBackfillStart({
|
|
11423
11700
|
lastComputedDate: c.lastComputedDate,
|
|
@@ -11441,9 +11718,22 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11441
11718
|
c = await runChunkedBackfill(c, clampedNeeded, backEnd, "newest-first");
|
|
11442
11719
|
}
|
|
11443
11720
|
}
|
|
11721
|
+
if (!c.backfill || repairDates.includes(c.backfill.blockedDate)) {
|
|
11722
|
+
const selectedStart = toDateString(periodInfo.range.start);
|
|
11723
|
+
for (const date2 of repairDates) {
|
|
11724
|
+
if (date2 < selectedStart || date2 > yesterdayStr || !c.partialDates?.includes(date2)) continue;
|
|
11725
|
+
const start = parseHistoryStartDate(date2);
|
|
11726
|
+
const end = new Date(start);
|
|
11727
|
+
end.setHours(23, 59, 59, 999);
|
|
11728
|
+
c = await runChunkedBackfill(c, start, end, "oldest-first", true);
|
|
11729
|
+
if (c.backfill) break;
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11444
11732
|
return c;
|
|
11445
|
-
});
|
|
11446
|
-
|
|
11733
|
+
}), { maxMs: 1e4, maxBytes: 8 * 1024 ** 3, maxRecords: 2e6 }), 2e6, void 0, 8 * 1024 ** 3);
|
|
11734
|
+
if (opts.optimize !== false || opts.period === "all" && !hasRecordedHistory(cache, toDateString(todayStart))) {
|
|
11735
|
+
assertHistoryComplete(cache, toDateString(periodInfo.range.start), toDateString(todayStart));
|
|
11736
|
+
}
|
|
11447
11737
|
let currentData;
|
|
11448
11738
|
const scanRange = periodInfo.range;
|
|
11449
11739
|
const todayRange = { start: todayStart, end: periodInfo.range.end };
|
|
@@ -11517,7 +11807,7 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11517
11807
|
let historicalRecovery = null;
|
|
11518
11808
|
if ((opts.period === "all" || opts.optimize === false) && (isAllProviders || pf === "claude") && !opts.project.length && !opts.exclude.length && !cache.partialDates?.length && !cache.backfill) {
|
|
11519
11809
|
const snapshot = await readClaudeUsageSnapshot();
|
|
11520
|
-
historicalRecovery = recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart);
|
|
11810
|
+
historicalRecovery = lockClaudeRecovery(recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart));
|
|
11521
11811
|
if (historicalRecovery && opts.period === "all") {
|
|
11522
11812
|
applyClaudeRecovery(currentData, historicalRecovery);
|
|
11523
11813
|
const claudeProvider = providers.find((p) => p.name.toLowerCase() === "claude");
|
|
@@ -11585,13 +11875,13 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11585
11875
|
`);
|
|
11586
11876
|
}
|
|
11587
11877
|
}
|
|
11588
|
-
const exeOsDir =
|
|
11589
|
-
const exeOsDetected =
|
|
11878
|
+
const exeOsDir = join31(homedir17(), ".exe-os");
|
|
11879
|
+
const exeOsDetected = existsSync8(exeOsDir);
|
|
11590
11880
|
let agentStats = null;
|
|
11591
11881
|
let statsFileAge = null;
|
|
11592
11882
|
try {
|
|
11593
|
-
const statsPath =
|
|
11594
|
-
if (
|
|
11883
|
+
const statsPath = join31(exeOsDir, "agent-stats.json");
|
|
11884
|
+
if (existsSync8(statsPath)) {
|
|
11595
11885
|
statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
|
|
11596
11886
|
const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
|
|
11597
11887
|
if (parsed && Array.isArray(parsed.agents) && typeof parsed.generated === "string") {
|
|
@@ -11654,7 +11944,15 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11654
11944
|
if (periodSnapshots) for (const [key2, snapshot] of Object.entries(periodSnapshots)) {
|
|
11655
11945
|
snapshot.time = summarizeTime(timeIndex, getDateRange2(key2, now).range, pf, opts.project, opts.exclude);
|
|
11656
11946
|
}
|
|
11657
|
-
const json = JSON.stringify({
|
|
11947
|
+
const json = JSON.stringify({
|
|
11948
|
+
...payload,
|
|
11949
|
+
time,
|
|
11950
|
+
periodSnapshots,
|
|
11951
|
+
...periodSnapshots?.[opts.period]?.savedHistory ? { savedHistory: periodSnapshots[opts.period].savedHistory } : {},
|
|
11952
|
+
costBasis: "api-equivalent",
|
|
11953
|
+
pricing: getPricingMetadata(),
|
|
11954
|
+
historicalRecovery: opts.period === "all" ? historicalRecovery : null
|
|
11955
|
+
});
|
|
11658
11956
|
console.log(json);
|
|
11659
11957
|
return;
|
|
11660
11958
|
}
|
|
@@ -11984,12 +12282,12 @@ program.command("compare").description("Compare two AI models side-by-side").opt
|
|
|
11984
12282
|
await renderCompare(range, opts.provider);
|
|
11985
12283
|
});
|
|
11986
12284
|
program.command("agent-status").description("Diagnose exe-os agent data pipeline").action(() => {
|
|
11987
|
-
const exeOsDir =
|
|
11988
|
-
const statsPath =
|
|
11989
|
-
const pidPath =
|
|
11990
|
-
const dirExists =
|
|
11991
|
-
const statsExists = dirExists &&
|
|
11992
|
-
const pidExists = dirExists &&
|
|
12285
|
+
const exeOsDir = join31(homedir17(), ".exe-os");
|
|
12286
|
+
const statsPath = join31(exeOsDir, "agent-stats.json");
|
|
12287
|
+
const pidPath = join31(exeOsDir, "exed.pid");
|
|
12288
|
+
const dirExists = existsSync8(exeOsDir);
|
|
12289
|
+
const statsExists = dirExists && existsSync8(statsPath);
|
|
12290
|
+
const pidExists = dirExists && existsSync8(pidPath);
|
|
11993
12291
|
const lines = [];
|
|
11994
12292
|
lines.push(`exe-os directory: ${dirExists ? "\u2713 found" : "\u2717 not found"} (${exeOsDir})`);
|
|
11995
12293
|
lines.push(`daemon pid file: ${pidExists ? "\u2713 found" : "\u2717 not found"}`);
|
|
@@ -12021,7 +12319,7 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
|
|
|
12021
12319
|
var releaseScan;
|
|
12022
12320
|
var stopParentWatch = watchMenubarParent(Number(process.env["EXE_WATCHER_PARENT_PID"]));
|
|
12023
12321
|
program.hook("preAction", async (_root, command) => {
|
|
12024
|
-
if (command.name() === "status") releaseScan = await acquireFileLock(
|
|
12322
|
+
if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join31(getCacheDir(), "scan.lock"));
|
|
12025
12323
|
});
|
|
12026
12324
|
program.hook("postAction", async () => {
|
|
12027
12325
|
await releaseScan?.();
|