exe-watcher 0.2.66 → 0.2.67
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 +483 -295
- 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,186 @@ 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";
|
|
2699
2873
|
async function fingerprint(path) {
|
|
2700
2874
|
try {
|
|
2701
2875
|
const s = await stat5(path);
|
|
@@ -2706,12 +2880,14 @@ async function fingerprint(path) {
|
|
|
2706
2880
|
}
|
|
2707
2881
|
}
|
|
2708
2882
|
async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
2709
|
-
const dir =
|
|
2710
|
-
const key2 =
|
|
2711
|
-
const cachePath =
|
|
2883
|
+
const dir = join10(getCacheDir(), "parsed-files");
|
|
2884
|
+
const key2 = createHash3("sha256").update(`${VERSION2}:${kind}:${path}`).digest("hex");
|
|
2885
|
+
const cachePath = join10(dir, `v${VERSION2}-${key2}.json.gz`);
|
|
2886
|
+
const legacyKey = createHash3("sha256").update(`3:${kind}:${path}:${getPricingFingerprint()}`).digest("hex");
|
|
2887
|
+
const legacyPath = join10(dir, `v3-${legacyKey}.json.gz`);
|
|
2712
2888
|
const fingerprintPath = kind === "opencode" ? path.slice(0, path.lastIndexOf(":")) : path;
|
|
2713
2889
|
const before = await fingerprint(fingerprintPath);
|
|
2714
|
-
const rangeKind = `usage-range:${
|
|
2890
|
+
const rangeKind = `usage-range:3:${kind}`;
|
|
2715
2891
|
const bounds = range && before ? await readDiscoveryFact(rangeKind, path, before) : void 0;
|
|
2716
2892
|
if (range && bounds && (bounds.last < range.start.getTime() || bounds.first > range.end.getTime())) {
|
|
2717
2893
|
if (Array.isArray(bounds.warnings)) warnings?.push(...bounds.warnings);
|
|
@@ -2729,32 +2905,49 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2729
2905
|
}
|
|
2730
2906
|
await rememberDiscoveryFact(rangeKind, path, before, { first: data2.length ? first : 0, last: data2.length ? last : -1, warnings });
|
|
2731
2907
|
};
|
|
2908
|
+
let migrated;
|
|
2732
2909
|
if (before) {
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
if (
|
|
2740
|
-
|
|
2741
|
-
|
|
2910
|
+
for (const candidate of [cachePath, legacyPath]) {
|
|
2911
|
+
try {
|
|
2912
|
+
const s = await stat5(candidate);
|
|
2913
|
+
if (s.size <= MAX_ENTRY) {
|
|
2914
|
+
chargeRead(s.size);
|
|
2915
|
+
const saved = JSON.parse((await decompress(await readFile7(candidate), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
|
|
2916
|
+
if ((saved.v === VERSION2 || saved.v === 3) && saved.fingerprint === before && Array.isArray(saved.data)) {
|
|
2917
|
+
if (Array.isArray(saved.warnings)) warnings?.push(...saved.warnings.filter((w) => typeof w === "string"));
|
|
2918
|
+
await rememberRange(saved.data);
|
|
2919
|
+
if (saved.v === VERSION2 && saved.pricesLocked === true) {
|
|
2920
|
+
if (!saved.hasUnpricedUsage || saved.pricingFingerprint === getPricingFingerprint()) return saved.data;
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
migrated = saved.data;
|
|
2924
|
+
break;
|
|
2925
|
+
}
|
|
2742
2926
|
}
|
|
2927
|
+
} catch (err) {
|
|
2928
|
+
if (err instanceof ResourceBudgetError) throw err;
|
|
2743
2929
|
}
|
|
2744
|
-
} catch (err) {
|
|
2745
|
-
if (err instanceof ResourceBudgetError) throw err;
|
|
2746
2930
|
}
|
|
2747
2931
|
}
|
|
2748
|
-
const data = await parse();
|
|
2932
|
+
const data = migrated ?? await parse();
|
|
2933
|
+
const hasUnpricedUsage = lockRecordedCosts(data);
|
|
2749
2934
|
if (!before || await fingerprint(fingerprintPath) !== before) return data;
|
|
2750
2935
|
await rememberRange(data);
|
|
2751
|
-
const payload = JSON.stringify({
|
|
2936
|
+
const payload = JSON.stringify({
|
|
2937
|
+
v: VERSION2,
|
|
2938
|
+
fingerprint: before,
|
|
2939
|
+
data,
|
|
2940
|
+
warnings,
|
|
2941
|
+
pricesLocked: true,
|
|
2942
|
+
hasUnpricedUsage,
|
|
2943
|
+
pricingFingerprint: getPricingFingerprint()
|
|
2944
|
+
});
|
|
2752
2945
|
if (Buffer.byteLength(payload) > MAX_ENTRY) return data;
|
|
2753
2946
|
await mkdir8(dir, { recursive: true });
|
|
2754
2947
|
const encoded = await compress(Buffer.from(payload));
|
|
2755
|
-
const release = await acquireFileLock(
|
|
2948
|
+
const release = await acquireFileLock(join10(dir, "writes.lock"));
|
|
2756
2949
|
try {
|
|
2757
|
-
const quotaPath =
|
|
2950
|
+
const quotaPath = join10(dir, "quota");
|
|
2758
2951
|
const quota = await readFile7(quotaPath, "utf8").catch(() => "");
|
|
2759
2952
|
let total = /^\d+$/.test(quota) ? Number(quota) : NaN;
|
|
2760
2953
|
const writeQuota = async (bytes) => {
|
|
@@ -2772,12 +2965,12 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2772
2965
|
const rows2 = [];
|
|
2773
2966
|
for (const name of files) {
|
|
2774
2967
|
if (!name.endsWith(".json") && !name.endsWith(".json.gz") && !name.endsWith(".tmp")) continue;
|
|
2775
|
-
if (!name.startsWith(`v${VERSION2}-`)) {
|
|
2776
|
-
await unlink5(
|
|
2968
|
+
if (!name.startsWith(`v${VERSION2}-`) && !name.startsWith("v3-")) {
|
|
2969
|
+
await unlink5(join10(dir, name)).catch(() => {
|
|
2777
2970
|
});
|
|
2778
2971
|
continue;
|
|
2779
2972
|
}
|
|
2780
|
-
const p =
|
|
2973
|
+
const p = join10(dir, name), s = await stat5(p).catch(() => null);
|
|
2781
2974
|
if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
2782
2975
|
}
|
|
2783
2976
|
total = rows2.reduce((n, r) => n + r.size, 0);
|
|
@@ -2797,7 +2990,7 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
|
|
|
2797
2990
|
const rows2 = [];
|
|
2798
2991
|
for (const name of await readdir3(dir)) {
|
|
2799
2992
|
if (!name.endsWith(".json.gz")) continue;
|
|
2800
|
-
const p =
|
|
2993
|
+
const p = join10(dir, name);
|
|
2801
2994
|
if (p === cachePath) continue;
|
|
2802
2995
|
const s = await stat5(p).catch(() => null);
|
|
2803
2996
|
if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
@@ -2835,7 +3028,8 @@ var init_parsed_file_cache = __esm({
|
|
|
2835
3028
|
init_resource_budget();
|
|
2836
3029
|
init_file_lock();
|
|
2837
3030
|
init_discovery_cache();
|
|
2838
|
-
|
|
3031
|
+
init_recorded_costs();
|
|
3032
|
+
VERSION2 = 4;
|
|
2839
3033
|
compress = promisify(gzip);
|
|
2840
3034
|
decompress = promisify(gunzip);
|
|
2841
3035
|
MAX_ENTRY = 16 * 1024 * 1024;
|
|
@@ -3011,15 +3205,15 @@ var init_fs_utils = __esm({
|
|
|
3011
3205
|
|
|
3012
3206
|
// src/providers/claude.ts
|
|
3013
3207
|
import { readdir as readdir4, stat as stat6 } from "fs/promises";
|
|
3014
|
-
import { basename as basename2, join as
|
|
3208
|
+
import { basename as basename2, join as join11, resolve as resolve2 } from "path";
|
|
3015
3209
|
import { homedir as homedir4 } from "os";
|
|
3016
3210
|
function getClaudeDir() {
|
|
3017
|
-
return process.env["CLAUDE_CONFIG_DIR"] ||
|
|
3211
|
+
return process.env["CLAUDE_CONFIG_DIR"] || join11(homedir4(), ".claude");
|
|
3018
3212
|
}
|
|
3019
3213
|
function getDesktopSessionsDir() {
|
|
3020
|
-
if (process.platform === "darwin") return
|
|
3021
|
-
if (process.platform === "win32") return
|
|
3022
|
-
return
|
|
3214
|
+
if (process.platform === "darwin") return join11(homedir4(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
|
|
3215
|
+
if (process.platform === "win32") return join11(homedir4(), "AppData", "Roaming", "Claude", "local-agent-mode-sessions");
|
|
3216
|
+
return join11(homedir4(), ".config", "Claude", "local-agent-mode-sessions");
|
|
3023
3217
|
}
|
|
3024
3218
|
async function findDesktopProjectDirs(base) {
|
|
3025
3219
|
const results = [];
|
|
@@ -3028,13 +3222,13 @@ async function findDesktopProjectDirs(base) {
|
|
|
3028
3222
|
const entries = await readdir4(dir).catch(() => []);
|
|
3029
3223
|
for (const entry of entries) {
|
|
3030
3224
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
3031
|
-
const full =
|
|
3225
|
+
const full = join11(dir, entry);
|
|
3032
3226
|
const s = await stat6(full).catch(() => null);
|
|
3033
3227
|
if (!s?.isDirectory()) continue;
|
|
3034
3228
|
if (entry === "projects") {
|
|
3035
3229
|
const projectDirs = await readdir4(full).catch(() => []);
|
|
3036
3230
|
for (const pd of projectDirs) {
|
|
3037
|
-
const pdFull =
|
|
3231
|
+
const pdFull = join11(full, pd);
|
|
3038
3232
|
const pdStat = await stat6(pdFull).catch(() => null);
|
|
3039
3233
|
if (pdStat?.isDirectory()) results.push(pdFull);
|
|
3040
3234
|
}
|
|
@@ -3088,11 +3282,11 @@ var init_claude = __esm({
|
|
|
3088
3282
|
const config = await readConfig();
|
|
3089
3283
|
const roots = /* @__PURE__ */ new Set([getClaudeDir(), ...config.claudeConfigDirs ?? []]);
|
|
3090
3284
|
for (const root of roots) {
|
|
3091
|
-
const projectsDir =
|
|
3285
|
+
const projectsDir = join11(root.startsWith("~/") ? join11(homedir4(), root.slice(2)) : resolve2(root), "projects");
|
|
3092
3286
|
try {
|
|
3093
3287
|
const entries = await readdir4(projectsDir);
|
|
3094
3288
|
for (const dirName of entries) {
|
|
3095
|
-
const dirPath =
|
|
3289
|
+
const dirPath = join11(projectsDir, dirName);
|
|
3096
3290
|
const dirStat = await stat6(dirPath).catch(() => null);
|
|
3097
3291
|
if (dirStat?.isDirectory()) {
|
|
3098
3292
|
sources.push({ path: dirPath, project: dirName, provider: "claude" });
|
|
@@ -3119,10 +3313,10 @@ var init_claude = __esm({
|
|
|
3119
3313
|
|
|
3120
3314
|
// src/providers/codex.ts
|
|
3121
3315
|
import { readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
3122
|
-
import { basename as basename3, join as
|
|
3316
|
+
import { basename as basename3, join as join12 } from "path";
|
|
3123
3317
|
import { homedir as homedir5 } from "os";
|
|
3124
3318
|
function getCodexDir(override) {
|
|
3125
|
-
return override ?? process.env["CODEX_HOME"] ??
|
|
3319
|
+
return override ?? process.env["CODEX_HOME"] ?? join12(homedir5(), ".codex");
|
|
3126
3320
|
}
|
|
3127
3321
|
function sanitizeProject(cwd) {
|
|
3128
3322
|
return cwd.replace(/^\//, "").replace(/\//g, "-");
|
|
@@ -3143,12 +3337,12 @@ async function readCodexHeader(filePath) {
|
|
|
3143
3337
|
return { project: sanitizeProject(entry.payload?.cwd ?? "unknown") };
|
|
3144
3338
|
}
|
|
3145
3339
|
async function discoverSessionsInDir(codexDir) {
|
|
3146
|
-
const sessionsDir =
|
|
3340
|
+
const sessionsDir = join12(codexDir, "sessions");
|
|
3147
3341
|
const sources = [];
|
|
3148
|
-
const archivedDir =
|
|
3342
|
+
const archivedDir = join12(codexDir, "archived_sessions");
|
|
3149
3343
|
for (const file of await readdir5(archivedDir).catch(() => [])) {
|
|
3150
3344
|
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
|
|
3151
|
-
const filePath =
|
|
3345
|
+
const filePath = join12(archivedDir, file);
|
|
3152
3346
|
const s = await stat7(filePath).catch(() => null);
|
|
3153
3347
|
if (!s?.isFile()) continue;
|
|
3154
3348
|
const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
|
|
@@ -3162,19 +3356,19 @@ async function discoverSessionsInDir(codexDir) {
|
|
|
3162
3356
|
}
|
|
3163
3357
|
for (const year of years) {
|
|
3164
3358
|
if (!/^\d{4}$/.test(year)) continue;
|
|
3165
|
-
const yearDir =
|
|
3359
|
+
const yearDir = join12(sessionsDir, year);
|
|
3166
3360
|
const months = await readdir5(yearDir).catch(() => []);
|
|
3167
3361
|
for (const month of months) {
|
|
3168
3362
|
if (!/^\d{2}$/.test(month)) continue;
|
|
3169
|
-
const monthDir =
|
|
3363
|
+
const monthDir = join12(yearDir, month);
|
|
3170
3364
|
const days = await readdir5(monthDir).catch(() => []);
|
|
3171
3365
|
for (const day of days) {
|
|
3172
3366
|
if (!/^\d{2}$/.test(day)) continue;
|
|
3173
|
-
const dayDir =
|
|
3367
|
+
const dayDir = join12(monthDir, day);
|
|
3174
3368
|
const files = await readdir5(dayDir).catch(() => []);
|
|
3175
3369
|
for (const file of files) {
|
|
3176
3370
|
if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
|
|
3177
|
-
const filePath =
|
|
3371
|
+
const filePath = join12(dayDir, file);
|
|
3178
3372
|
const s = await stat7(filePath).catch(() => null);
|
|
3179
3373
|
if (!s?.isFile()) continue;
|
|
3180
3374
|
const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
|
|
@@ -3393,10 +3587,10 @@ var init_codex = __esm({
|
|
|
3393
3587
|
|
|
3394
3588
|
// src/providers/copilot.ts
|
|
3395
3589
|
import { readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
3396
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
3590
|
+
import { basename as basename4, dirname as dirname3, join as join13 } from "path";
|
|
3397
3591
|
import { homedir as homedir6 } from "os";
|
|
3398
3592
|
function getCopilotSessionStateDir(override) {
|
|
3399
|
-
return override ??
|
|
3593
|
+
return override ?? join13(homedir6(), ".copilot", "session-state");
|
|
3400
3594
|
}
|
|
3401
3595
|
function parseCwd(yaml) {
|
|
3402
3596
|
const match = yaml.match(/^cwd:\s*(.+)$/m);
|
|
@@ -3471,11 +3665,11 @@ async function discoverSessionsInDir2(sessionStateDir) {
|
|
|
3471
3665
|
return sources;
|
|
3472
3666
|
}
|
|
3473
3667
|
for (const sessionId of sessionDirs) {
|
|
3474
|
-
const eventsPath =
|
|
3668
|
+
const eventsPath = join13(sessionStateDir, sessionId, "events.jsonl");
|
|
3475
3669
|
const s = await stat8(eventsPath).catch(() => null);
|
|
3476
3670
|
if (!s?.isFile()) continue;
|
|
3477
3671
|
let project = sessionId;
|
|
3478
|
-
const yaml = await readSessionFile(
|
|
3672
|
+
const yaml = await readSessionFile(join13(sessionStateDir, sessionId, "workspace.yaml"));
|
|
3479
3673
|
if (yaml !== null) {
|
|
3480
3674
|
const cwd = parseCwd(yaml);
|
|
3481
3675
|
if (cwd) project = basename4(cwd);
|
|
@@ -3589,13 +3783,13 @@ var init_bash_utils = __esm({
|
|
|
3589
3783
|
|
|
3590
3784
|
// src/providers/pi.ts
|
|
3591
3785
|
import { readdir as readdir7, stat as stat9 } from "fs/promises";
|
|
3592
|
-
import { basename as basename6, join as
|
|
3786
|
+
import { basename as basename6, join as join14 } from "path";
|
|
3593
3787
|
import { homedir as homedir7 } from "os";
|
|
3594
3788
|
function getPiSessionsDir(override) {
|
|
3595
|
-
return override ??
|
|
3789
|
+
return override ?? join14(homedir7(), ".pi", "agent", "sessions");
|
|
3596
3790
|
}
|
|
3597
3791
|
function getOmpSessionsDir(override) {
|
|
3598
|
-
return override ??
|
|
3792
|
+
return override ?? join14(homedir7(), ".omp", "agent", "sessions");
|
|
3599
3793
|
}
|
|
3600
3794
|
async function readFirstEntry(filePath) {
|
|
3601
3795
|
const line = await readSessionFirstLine(filePath);
|
|
@@ -3615,7 +3809,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
|
|
|
3615
3809
|
return sources;
|
|
3616
3810
|
}
|
|
3617
3811
|
for (const dirName of projectDirs) {
|
|
3618
|
-
const dirPath =
|
|
3812
|
+
const dirPath = join14(sessionsDir, dirName);
|
|
3619
3813
|
const dirStat = await stat9(dirPath).catch(() => null);
|
|
3620
3814
|
if (!dirStat?.isDirectory()) continue;
|
|
3621
3815
|
let files;
|
|
@@ -3626,7 +3820,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
|
|
|
3626
3820
|
}
|
|
3627
3821
|
for (const file of files) {
|
|
3628
3822
|
if (!file.endsWith(".jsonl")) continue;
|
|
3629
|
-
const filePath =
|
|
3823
|
+
const filePath = join14(dirPath, file);
|
|
3630
3824
|
const fileStat = await stat9(filePath).catch(() => null);
|
|
3631
3825
|
if (!fileStat?.isFile()) continue;
|
|
3632
3826
|
const first = await readFirstEntry(filePath);
|
|
@@ -3783,12 +3977,12 @@ var init_pi = __esm({
|
|
|
3783
3977
|
});
|
|
3784
3978
|
|
|
3785
3979
|
// src/providers/kimi.ts
|
|
3786
|
-
import { createHash as
|
|
3980
|
+
import { createHash as createHash4 } from "crypto";
|
|
3787
3981
|
import { readdir as readdir8, readFile as readFile8, stat as stat10 } from "fs/promises";
|
|
3788
|
-
import { basename as basename7, dirname as dirname4, join as
|
|
3982
|
+
import { basename as basename7, dirname as dirname4, join as join15 } from "path";
|
|
3789
3983
|
import { homedir as homedir8 } from "os";
|
|
3790
3984
|
function createKimiProvider(home) {
|
|
3791
|
-
const root = () => home ?? process.env["KIMI_CODE_HOME"] ??
|
|
3985
|
+
const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join15(homedir8(), ".kimi-code");
|
|
3792
3986
|
return {
|
|
3793
3987
|
name: "kimi",
|
|
3794
3988
|
displayName: "Kimi",
|
|
@@ -3796,7 +3990,7 @@ function createKimiProvider(home) {
|
|
|
3796
3990
|
toolDisplayName: (tool) => tool,
|
|
3797
3991
|
async discoverSessions() {
|
|
3798
3992
|
const projects = /* @__PURE__ */ new Map();
|
|
3799
|
-
const catalog =
|
|
3993
|
+
const catalog = join15(root(), "workspaces.json");
|
|
3800
3994
|
try {
|
|
3801
3995
|
const size = (await stat10(catalog)).size;
|
|
3802
3996
|
if (size <= 2 * 1024 * 1024) {
|
|
@@ -3818,16 +4012,16 @@ function createKimiProvider(home) {
|
|
|
3818
4012
|
const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
|
|
3819
4013
|
for (const entry of entries) {
|
|
3820
4014
|
if (entry.name.startsWith(".")) continue;
|
|
3821
|
-
const path =
|
|
4015
|
+
const path = join15(dir, entry.name);
|
|
3822
4016
|
if (entry.isDirectory()) await walk(path, project, depth + 1);
|
|
3823
4017
|
else if (entry.isFile() && entry.name === "wire.jsonl") sources.push({ path, project, provider: "kimi" });
|
|
3824
4018
|
}
|
|
3825
4019
|
}
|
|
3826
|
-
const sessions =
|
|
4020
|
+
const sessions = join15(root(), "sessions");
|
|
3827
4021
|
for (const entry of await readdir8(sessions, { withFileTypes: true }).catch(() => [])) {
|
|
3828
4022
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3829
4023
|
const project = projects.get(entry.name) ?? entry.name.replace(/^wd_/, "").replace(/_[a-f\d]{12}$/, "");
|
|
3830
|
-
await walk(
|
|
4024
|
+
await walk(join15(sessions, entry.name), project, 0);
|
|
3831
4025
|
}
|
|
3832
4026
|
return sources.sort((a, b) => a.path.localeCompare(b.path));
|
|
3833
4027
|
},
|
|
@@ -3866,7 +4060,7 @@ function createKimiProvider(home) {
|
|
|
3866
4060
|
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
4061
|
if (!warnings.includes(warning)) warnings.push(warning);
|
|
3868
4062
|
}
|
|
3869
|
-
const key2 = "kimi:" +
|
|
4063
|
+
const key2 = "kimi:" + createHash4("sha256").update(JSON.stringify([
|
|
3870
4064
|
data.agentId ?? agent,
|
|
3871
4065
|
data.time,
|
|
3872
4066
|
data.model,
|
|
@@ -3916,10 +4110,10 @@ var init_kimi = __esm({
|
|
|
3916
4110
|
// src/cursor-cache.ts
|
|
3917
4111
|
import { readFile as readFile9, mkdir as mkdir9, stat as stat11, rename as rename7 } from "fs/promises";
|
|
3918
4112
|
import { open as open6 } from "fs/promises";
|
|
3919
|
-
import { join as
|
|
4113
|
+
import { join as join16 } from "path";
|
|
3920
4114
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
3921
4115
|
function getCachePath2() {
|
|
3922
|
-
return
|
|
4116
|
+
return join16(getCacheDir(), CACHE_FILE);
|
|
3923
4117
|
}
|
|
3924
4118
|
async function getDbFingerprint(dbPath) {
|
|
3925
4119
|
try {
|
|
@@ -3984,100 +4178,23 @@ var init_cursor_cache = __esm({
|
|
|
3984
4178
|
}
|
|
3985
4179
|
});
|
|
3986
4180
|
|
|
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
4181
|
// src/providers/cursor.ts
|
|
4065
4182
|
var cursor_exports = {};
|
|
4066
4183
|
__export(cursor_exports, {
|
|
4067
4184
|
createCursorProvider: () => createCursorProvider,
|
|
4068
4185
|
cursor: () => cursor
|
|
4069
4186
|
});
|
|
4070
|
-
import { existsSync } from "fs";
|
|
4071
|
-
import { join as
|
|
4187
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4188
|
+
import { join as join17 } from "path";
|
|
4072
4189
|
import { homedir as homedir9 } from "os";
|
|
4073
4190
|
function getCursorDbPath() {
|
|
4074
4191
|
if (process.platform === "darwin") {
|
|
4075
|
-
return
|
|
4192
|
+
return join17(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4076
4193
|
}
|
|
4077
4194
|
if (process.platform === "win32") {
|
|
4078
|
-
return
|
|
4195
|
+
return join17(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4079
4196
|
}
|
|
4080
|
-
return
|
|
4197
|
+
return join17(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4081
4198
|
}
|
|
4082
4199
|
function extractLanguages(codeBlocksJson) {
|
|
4083
4200
|
if (!codeBlocksJson) return [];
|
|
@@ -4249,7 +4366,7 @@ function createCursorProvider(dbPathOverride) {
|
|
|
4249
4366
|
async discoverSessions() {
|
|
4250
4367
|
if (!isSqliteAvailable()) return [];
|
|
4251
4368
|
const dbPath = dbPathOverride ?? getCursorDbPath();
|
|
4252
|
-
if (!
|
|
4369
|
+
if (!existsSync2(dbPath)) return [];
|
|
4253
4370
|
return [{ path: dbPath, project: "cursor", provider: "cursor" }];
|
|
4254
4371
|
},
|
|
4255
4372
|
createSessionParser(source, seenKeys) {
|
|
@@ -4320,19 +4437,19 @@ __export(opencode_exports, {
|
|
|
4320
4437
|
opencode: () => opencode
|
|
4321
4438
|
});
|
|
4322
4439
|
import { readdir as readdir9 } from "fs/promises";
|
|
4323
|
-
import { join as
|
|
4440
|
+
import { join as join18 } from "path";
|
|
4324
4441
|
import { homedir as homedir10 } from "os";
|
|
4325
4442
|
function sanitize2(dir) {
|
|
4326
4443
|
return dir.replace(/^\//, "").replace(/\//g, "-");
|
|
4327
4444
|
}
|
|
4328
4445
|
function getDataDir(dataDir) {
|
|
4329
|
-
const base = dataDir ?? process.env["XDG_DATA_HOME"] ??
|
|
4330
|
-
return
|
|
4446
|
+
const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join18(homedir10(), ".local", "share");
|
|
4447
|
+
return join18(base, "opencode");
|
|
4331
4448
|
}
|
|
4332
4449
|
async function findDbFiles(dir) {
|
|
4333
4450
|
try {
|
|
4334
4451
|
const entries = await readdir9(dir);
|
|
4335
|
-
return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) =>
|
|
4452
|
+
return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join18(dir, f));
|
|
4336
4453
|
} catch (error) {
|
|
4337
4454
|
if (error instanceof ResourceBudgetError) throw error;
|
|
4338
4455
|
return [];
|
|
@@ -4561,20 +4678,20 @@ __export(cursor_agent_exports, {
|
|
|
4561
4678
|
createCursorAgentProvider: () => createCursorAgentProvider,
|
|
4562
4679
|
cursor_agent: () => cursor_agent
|
|
4563
4680
|
});
|
|
4564
|
-
import { createHash as
|
|
4565
|
-
import { existsSync as
|
|
4681
|
+
import { createHash as createHash5 } from "crypto";
|
|
4682
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4566
4683
|
import { readdir as readdir10, stat as stat12 } from "fs/promises";
|
|
4567
|
-
import { join as
|
|
4684
|
+
import { join as join19, basename as basename8 } from "path";
|
|
4568
4685
|
import { homedir as homedir11 } from "os";
|
|
4569
4686
|
function getCursorAgentBaseDir(baseDirOverride) {
|
|
4570
4687
|
if (baseDirOverride) return baseDirOverride;
|
|
4571
|
-
return
|
|
4688
|
+
return join19(homedir11(), ".cursor");
|
|
4572
4689
|
}
|
|
4573
4690
|
function getProjectsDir(baseDir) {
|
|
4574
|
-
return
|
|
4691
|
+
return join19(baseDir, "projects");
|
|
4575
4692
|
}
|
|
4576
4693
|
function getAttributionDbPath(baseDir) {
|
|
4577
|
-
return
|
|
4694
|
+
return join19(baseDir, "ai-tracking", "ai-code-tracking.db");
|
|
4578
4695
|
}
|
|
4579
4696
|
function estimateTokens(charCount) {
|
|
4580
4697
|
if (charCount <= 0) return 0;
|
|
@@ -4625,7 +4742,7 @@ function resolveModel3(raw) {
|
|
|
4625
4742
|
function toConversationId(transcriptPath) {
|
|
4626
4743
|
const filename = basename8(transcriptPath, ".txt");
|
|
4627
4744
|
if (filename.length === 36 && UUID_LIKE.test(filename)) return filename;
|
|
4628
|
-
return
|
|
4745
|
+
return createHash5("sha1").update(transcriptPath).digest("hex").slice(0, 16);
|
|
4629
4746
|
}
|
|
4630
4747
|
function extractUserQuery(userBlock) {
|
|
4631
4748
|
const chunks = [];
|
|
@@ -4734,7 +4851,7 @@ function createParser6(source, seenKeys, dbPath, summariesByConversationId) {
|
|
|
4734
4851
|
let db = null;
|
|
4735
4852
|
try {
|
|
4736
4853
|
if (!summary) {
|
|
4737
|
-
if (
|
|
4854
|
+
if (existsSync3(dbPath)) {
|
|
4738
4855
|
try {
|
|
4739
4856
|
db = openDatabase(dbPath);
|
|
4740
4857
|
const rows2 = db.query(CONVERSATION_SUMMARY_QUERY, [conversationId]);
|
|
@@ -4827,19 +4944,19 @@ function createCursorAgentProvider(baseDirOverride) {
|
|
|
4827
4944
|
return rawTool;
|
|
4828
4945
|
},
|
|
4829
4946
|
async discoverSessions() {
|
|
4830
|
-
if (!
|
|
4947
|
+
if (!existsSync3(projectsDir)) return [];
|
|
4831
4948
|
const projectEntries = await readdir10(projectsDir, { withFileTypes: true });
|
|
4832
4949
|
const sources = [];
|
|
4833
4950
|
for (const entry of projectEntries) {
|
|
4834
4951
|
if (!entry.isDirectory()) continue;
|
|
4835
4952
|
const projectId = prettifyProjectId(entry.name);
|
|
4836
|
-
const transcriptDir =
|
|
4837
|
-
if (!
|
|
4953
|
+
const transcriptDir = join19(projectsDir, entry.name, "agent-transcripts");
|
|
4954
|
+
if (!existsSync3(transcriptDir)) continue;
|
|
4838
4955
|
const transcriptEntries = await readdir10(transcriptDir, { withFileTypes: true });
|
|
4839
4956
|
for (const transcript of transcriptEntries) {
|
|
4840
4957
|
if (!transcript.isFile()) continue;
|
|
4841
4958
|
if (!transcript.name.endsWith(".txt")) continue;
|
|
4842
|
-
const transcriptPath =
|
|
4959
|
+
const transcriptPath = join19(transcriptDir, transcript.name);
|
|
4843
4960
|
sources.push({
|
|
4844
4961
|
path: transcriptPath,
|
|
4845
4962
|
project: projectId,
|
|
@@ -5153,15 +5270,15 @@ var init_classifier = __esm({
|
|
|
5153
5270
|
|
|
5154
5271
|
// src/session-index.ts
|
|
5155
5272
|
import { stat as stat13 } from "fs/promises";
|
|
5156
|
-
import { existsSync as
|
|
5273
|
+
import { existsSync as existsSync4, unlinkSync } from "fs";
|
|
5157
5274
|
import { readFile as readFile11, mkdir as mkdir10, rename as rename8, unlink as unlink6, open as open7 } from "fs/promises";
|
|
5158
|
-
import { join as
|
|
5275
|
+
import { join as join20 } from "path";
|
|
5159
5276
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
5160
5277
|
function emptyIndex() {
|
|
5161
5278
|
return { v: SESSION_INDEX_VERSION, e: {} };
|
|
5162
5279
|
}
|
|
5163
5280
|
function indexPath() {
|
|
5164
|
-
return
|
|
5281
|
+
return join20(getCacheDir(), INDEX_FILENAME);
|
|
5165
5282
|
}
|
|
5166
5283
|
async function loadSessionIndex() {
|
|
5167
5284
|
if (currentIndex) return currentIndex;
|
|
@@ -5181,7 +5298,7 @@ async function loadSessionIndex() {
|
|
|
5181
5298
|
}
|
|
5182
5299
|
async function saveSessionIndex(index) {
|
|
5183
5300
|
const dir = getCacheDir();
|
|
5184
|
-
if (!
|
|
5301
|
+
if (!existsSync4(dir)) await mkdir10(dir, { recursive: true });
|
|
5185
5302
|
const finalPath = indexPath();
|
|
5186
5303
|
const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
5187
5304
|
const payload = JSON.stringify(index);
|
|
@@ -5263,7 +5380,7 @@ __export(parser_exports, {
|
|
|
5263
5380
|
});
|
|
5264
5381
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
5265
5382
|
import { readdir as readdir11, stat as stat14 } from "fs/promises";
|
|
5266
|
-
import { basename as basename9, join as
|
|
5383
|
+
import { basename as basename9, join as join21 } from "path";
|
|
5267
5384
|
function getParseWarnings() {
|
|
5268
5385
|
return _parseWarnings;
|
|
5269
5386
|
}
|
|
@@ -5553,13 +5670,13 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
|
|
|
5553
5670
|
}
|
|
5554
5671
|
async function collectJsonlFiles2(dirPath) {
|
|
5555
5672
|
const files = await readdir11(dirPath).catch(() => []);
|
|
5556
|
-
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
5673
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join21(dirPath, f));
|
|
5557
5674
|
for (const entry of files) {
|
|
5558
5675
|
if (entry.endsWith(".jsonl")) continue;
|
|
5559
|
-
const subagentsPath =
|
|
5676
|
+
const subagentsPath = join21(dirPath, entry, "subagents");
|
|
5560
5677
|
const subFiles = await readdir11(subagentsPath).catch(() => []);
|
|
5561
5678
|
for (const sf of subFiles) {
|
|
5562
|
-
if (sf.endsWith(".jsonl")) jsonlFiles.push(
|
|
5679
|
+
if (sf.endsWith(".jsonl")) jsonlFiles.push(join21(subagentsPath, sf));
|
|
5563
5680
|
}
|
|
5564
5681
|
}
|
|
5565
5682
|
return jsonlFiles;
|
|
@@ -5925,8 +6042,8 @@ var init_parser = __esm({
|
|
|
5925
6042
|
init_cache_dir();
|
|
5926
6043
|
init_file_lock();
|
|
5927
6044
|
init_resource_budget();
|
|
5928
|
-
import { existsSync as
|
|
5929
|
-
import { join as
|
|
6045
|
+
import { existsSync as existsSync8, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
6046
|
+
import { join as join31 } from "path";
|
|
5930
6047
|
import { homedir as homedir17 } from "os";
|
|
5931
6048
|
import { Command } from "commander";
|
|
5932
6049
|
|
|
@@ -6657,19 +6774,19 @@ function renderStatusBar(projects) {
|
|
|
6657
6774
|
|
|
6658
6775
|
// src/menubar-json.ts
|
|
6659
6776
|
import { readdirSync, readFileSync } from "fs";
|
|
6660
|
-
import { join as
|
|
6777
|
+
import { join as join22, dirname as dirname5 } from "path";
|
|
6661
6778
|
init_models();
|
|
6662
6779
|
import { fileURLToPath } from "url";
|
|
6663
6780
|
function getCliVersion() {
|
|
6664
6781
|
try {
|
|
6665
|
-
const pkgPath =
|
|
6782
|
+
const pkgPath = join22(dirname5(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
6666
6783
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
6667
6784
|
return pkg.version ?? "0.0.0";
|
|
6668
6785
|
} catch {
|
|
6669
6786
|
return "0.0.0";
|
|
6670
6787
|
}
|
|
6671
6788
|
}
|
|
6672
|
-
var MIN_APP_VERSION = "0.2.
|
|
6789
|
+
var MIN_APP_VERSION = "0.2.67";
|
|
6673
6790
|
var TOP_ACTIVITIES_LIMIT = 20;
|
|
6674
6791
|
var TOP_MODELS_LIMIT = 20;
|
|
6675
6792
|
var TOP_FINDINGS_LIMIT = 10;
|
|
@@ -6825,18 +6942,18 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
|
|
|
6825
6942
|
// src/daily-cache.ts
|
|
6826
6943
|
init_cache_dir();
|
|
6827
6944
|
init_models();
|
|
6828
|
-
import { createHash as
|
|
6829
|
-
import { existsSync as
|
|
6945
|
+
import { createHash as createHash6, randomBytes as randomBytes6 } from "crypto";
|
|
6946
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6830
6947
|
import { mkdir as mkdir11, open as open8, readFile as readFile12, rename as rename9, unlink as unlink7 } from "fs/promises";
|
|
6831
|
-
import { join as
|
|
6948
|
+
import { join as join23 } from "path";
|
|
6832
6949
|
var DAILY_CACHE_VERSION = 9;
|
|
6833
6950
|
var DEFAULT_DAILY_CACHE_SCOPE = "global";
|
|
6834
6951
|
var DAILY_CACHE_FILENAME = "daily-cache.json";
|
|
6835
6952
|
function getCachePath3(scopeKey) {
|
|
6836
6953
|
if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
|
|
6837
|
-
return
|
|
6954
|
+
return join23(getCacheDir(), DAILY_CACHE_FILENAME);
|
|
6838
6955
|
}
|
|
6839
|
-
return
|
|
6956
|
+
return join23(getCacheDir(), `daily-cache-${scopeKey}.json`);
|
|
6840
6957
|
}
|
|
6841
6958
|
function emptyCache(scopeKey) {
|
|
6842
6959
|
return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
|
|
@@ -6851,7 +6968,7 @@ function buildDailyCacheScopeKey(include, exclude) {
|
|
|
6851
6968
|
return DEFAULT_DAILY_CACHE_SCOPE;
|
|
6852
6969
|
}
|
|
6853
6970
|
const raw = JSON.stringify({ include: normalizedInclude, exclude: normalizedExclude });
|
|
6854
|
-
return `scope-${
|
|
6971
|
+
return `scope-${createHash6("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
6855
6972
|
}
|
|
6856
6973
|
function isValidCache(parsed) {
|
|
6857
6974
|
if (!parsed || typeof parsed !== "object") return false;
|
|
@@ -6869,20 +6986,20 @@ function isValidCache(parsed) {
|
|
|
6869
6986
|
}
|
|
6870
6987
|
async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
|
|
6871
6988
|
const path = getCachePath3(scopeKey);
|
|
6872
|
-
if (!
|
|
6989
|
+
if (!existsSync5(path)) return emptyCache(scopeKey);
|
|
6873
6990
|
try {
|
|
6874
6991
|
const raw = await readFile12(path, "utf-8");
|
|
6875
6992
|
const parsed = JSON.parse(raw);
|
|
6876
6993
|
if (!isValidCache(parsed)) return emptyCache(scopeKey);
|
|
6877
6994
|
if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
|
|
6878
|
-
if (
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6995
|
+
if (parsed.needsRepricing) {
|
|
6996
|
+
const repricing = parsed.historyBuild;
|
|
6997
|
+
if (parsed.days.length) {
|
|
6998
|
+
parsed.historyBuild = void 0;
|
|
6999
|
+
parsed.backfill = repricing?.previousBackfill ?? null;
|
|
7000
|
+
}
|
|
7001
|
+
delete parsed.needsRepricing;
|
|
7002
|
+
delete parsed.previousHistoryStart;
|
|
6886
7003
|
}
|
|
6887
7004
|
if (parsed.version !== DAILY_CACHE_VERSION) {
|
|
6888
7005
|
return {
|
|
@@ -6899,7 +7016,7 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
|
|
|
6899
7016
|
}
|
|
6900
7017
|
async function saveDailyCache(cache) {
|
|
6901
7018
|
const dir = getCacheDir();
|
|
6902
|
-
if (!
|
|
7019
|
+
if (!existsSync5(dir)) await mkdir11(dir, { recursive: true });
|
|
6903
7020
|
const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
|
|
6904
7021
|
const finalPath = getCachePath3(scopeKey);
|
|
6905
7022
|
const tempPath = `${finalPath}.${randomBytes6(8).toString("hex")}.tmp`;
|
|
@@ -6959,16 +7076,16 @@ init_types();
|
|
|
6959
7076
|
init_cache_dir();
|
|
6960
7077
|
init_models();
|
|
6961
7078
|
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
|
|
7079
|
+
import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
|
|
6963
7080
|
import { homedir as homedir12 } from "os";
|
|
6964
|
-
import { join as
|
|
7081
|
+
import { join as join25 } from "path";
|
|
6965
7082
|
|
|
6966
7083
|
// src/history-start.ts
|
|
6967
7084
|
init_discovery_cache();
|
|
6968
7085
|
init_fs_utils();
|
|
6969
7086
|
init_parser();
|
|
6970
7087
|
import { readdir as readdir12, stat as stat15 } from "fs/promises";
|
|
6971
|
-
import { join as
|
|
7088
|
+
import { join as join24 } from "path";
|
|
6972
7089
|
function parseHistoryStartDate(value) {
|
|
6973
7090
|
if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
6974
7091
|
const [year, month, day] = value.split("-").map(Number);
|
|
@@ -6988,7 +7105,7 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
|
|
|
6988
7105
|
for (const source of sources) {
|
|
6989
7106
|
const sourceStat = await stat15(source.path).catch(() => null);
|
|
6990
7107
|
if (!sourceStat) continue;
|
|
6991
|
-
const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) =>
|
|
7108
|
+
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
7109
|
for (const path of paths) {
|
|
6993
7110
|
const fileStat = await stat15(path).catch(() => null);
|
|
6994
7111
|
if (!fileStat) continue;
|
|
@@ -7007,6 +7124,20 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
|
|
|
7007
7124
|
}
|
|
7008
7125
|
|
|
7009
7126
|
// src/claude-recovery.ts
|
|
7127
|
+
init_recorded_costs();
|
|
7128
|
+
function lockClaudeRecovery(recovery) {
|
|
7129
|
+
if (!recovery) return null;
|
|
7130
|
+
const rows2 = recovery.models.map((row) => ({
|
|
7131
|
+
...row,
|
|
7132
|
+
provider: "claude",
|
|
7133
|
+
deduplicationKey: "claude-stats-recovery",
|
|
7134
|
+
cacheCreationInputTokens: row.cacheWriteTokens,
|
|
7135
|
+
cacheReadInputTokens: row.cacheReadTokens
|
|
7136
|
+
}));
|
|
7137
|
+
lockRecordedCosts(rows2);
|
|
7138
|
+
const models = recovery.models.map((row, index) => ({ ...row, costUSD: rows2[index].costUSD }));
|
|
7139
|
+
return { ...recovery, models, costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0) };
|
|
7140
|
+
}
|
|
7010
7141
|
var fields = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
7011
7142
|
var emptyTokens = () => ({ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 });
|
|
7012
7143
|
function applyClaudeRecovery(data, recovery) {
|
|
@@ -7025,9 +7156,9 @@ function applyClaudeRecovery(data, recovery) {
|
|
|
7025
7156
|
}
|
|
7026
7157
|
}
|
|
7027
7158
|
async function readClaudeUsageSnapshot() {
|
|
7028
|
-
const root = process.env["CLAUDE_CONFIG_DIR"] ||
|
|
7029
|
-
const path =
|
|
7030
|
-
const savedPath =
|
|
7159
|
+
const root = process.env["CLAUDE_CONFIG_DIR"] || join25(homedir12(), ".claude");
|
|
7160
|
+
const path = join25(root, "stats-cache.json");
|
|
7161
|
+
const savedPath = join25(getCacheDir(), `claude-lifetime-${createHash7("sha256").update(root).digest("hex").slice(0, 16)}.json`);
|
|
7031
7162
|
const read = async (file) => {
|
|
7032
7163
|
try {
|
|
7033
7164
|
if ((await stat16(file)).size > 2 * 1024 ** 2) return null;
|
|
@@ -7111,7 +7242,7 @@ function recoverClaudeUsage(snapshot, recorded, rangeStart) {
|
|
|
7111
7242
|
...totals,
|
|
7112
7243
|
costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0),
|
|
7113
7244
|
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
|
|
7245
|
+
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
7246
|
};
|
|
7116
7247
|
}
|
|
7117
7248
|
async function getClaudeRecoveryForRange(range, signal) {
|
|
@@ -7132,7 +7263,7 @@ async function getClaudeRecoveryForRange(range, signal) {
|
|
|
7132
7263
|
recorded.set(model, total);
|
|
7133
7264
|
}
|
|
7134
7265
|
}
|
|
7135
|
-
return recoverClaudeUsage(snapshot, recorded, range.start);
|
|
7266
|
+
return lockClaudeRecovery(recoverClaudeUsage(snapshot, recorded, range.start));
|
|
7136
7267
|
}
|
|
7137
7268
|
|
|
7138
7269
|
// src/latest-load.ts
|
|
@@ -7195,8 +7326,8 @@ init_resource_budget();
|
|
|
7195
7326
|
init_providers();
|
|
7196
7327
|
import chalk2 from "chalk";
|
|
7197
7328
|
import { readdir as readdir13, stat as stat17 } from "fs/promises";
|
|
7198
|
-
import { existsSync as
|
|
7199
|
-
import { basename as basename10, join as
|
|
7329
|
+
import { existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
7330
|
+
import { basename as basename10, join as join26 } from "path";
|
|
7200
7331
|
import { homedir as homedir13 } from "os";
|
|
7201
7332
|
var ORANGE = "#F59E0B";
|
|
7202
7333
|
var DIM = "#666666";
|
|
@@ -7279,13 +7410,13 @@ var DEFAULT_TREND_PERIOD_MS = DEFAULT_TREND_PERIOD_DAYS * 24 * 60 * 60 * 1e3;
|
|
|
7279
7410
|
var IMPROVING_THRESHOLD = 0.5;
|
|
7280
7411
|
async function collectJsonlFiles3(dirPath) {
|
|
7281
7412
|
const files = await readdir13(dirPath).catch(() => []);
|
|
7282
|
-
const result = files.filter((f) => f.endsWith(".jsonl")).map((f) =>
|
|
7413
|
+
const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join26(dirPath, f));
|
|
7283
7414
|
for (const entry of files) {
|
|
7284
7415
|
if (entry.endsWith(".jsonl")) continue;
|
|
7285
|
-
const subPath =
|
|
7416
|
+
const subPath = join26(dirPath, entry, "subagents");
|
|
7286
7417
|
const subFiles = await readdir13(subPath).catch(() => []);
|
|
7287
7418
|
for (const sf of subFiles) {
|
|
7288
|
-
if (sf.endsWith(".jsonl")) result.push(
|
|
7419
|
+
if (sf.endsWith(".jsonl")) result.push(join26(subPath, sf));
|
|
7289
7420
|
}
|
|
7290
7421
|
}
|
|
7291
7422
|
return result;
|
|
@@ -7475,16 +7606,16 @@ function isReadTool(name) {
|
|
|
7475
7606
|
function loadMcpConfigs(projectCwds) {
|
|
7476
7607
|
const servers = /* @__PURE__ */ new Map();
|
|
7477
7608
|
const configPaths = [
|
|
7478
|
-
|
|
7479
|
-
|
|
7609
|
+
join26(homedir13(), ".claude", "settings.json"),
|
|
7610
|
+
join26(homedir13(), ".claude", "settings.local.json")
|
|
7480
7611
|
];
|
|
7481
7612
|
for (const cwd of projectCwds) {
|
|
7482
|
-
configPaths.push(
|
|
7483
|
-
configPaths.push(
|
|
7484
|
-
configPaths.push(
|
|
7613
|
+
configPaths.push(join26(cwd, ".mcp.json"));
|
|
7614
|
+
configPaths.push(join26(cwd, ".claude", "settings.json"));
|
|
7615
|
+
configPaths.push(join26(cwd, ".claude", "settings.local.json"));
|
|
7485
7616
|
}
|
|
7486
7617
|
for (const p of configPaths) {
|
|
7487
|
-
if (!
|
|
7618
|
+
if (!existsSync6(p)) continue;
|
|
7488
7619
|
const config = readJsonFile(p);
|
|
7489
7620
|
if (!config) continue;
|
|
7490
7621
|
let mtime = 0;
|
|
@@ -7634,13 +7765,13 @@ function expandImports(filePath, seen, depth) {
|
|
|
7634
7765
|
if (content === null) return { totalLines: 0, importedFiles: 0 };
|
|
7635
7766
|
let totalLines = content.split("\n").length;
|
|
7636
7767
|
let importedFiles = 0;
|
|
7637
|
-
const dir =
|
|
7768
|
+
const dir = join26(filePath, "..");
|
|
7638
7769
|
IMPORT_PATTERN.lastIndex = 0;
|
|
7639
7770
|
for (const match of content.matchAll(IMPORT_PATTERN)) {
|
|
7640
7771
|
const rawPath = match[1];
|
|
7641
7772
|
if (!rawPath) continue;
|
|
7642
|
-
const resolved = rawPath.startsWith("/") ? rawPath :
|
|
7643
|
-
if (!
|
|
7773
|
+
const resolved = rawPath.startsWith("/") ? rawPath : join26(dir, rawPath);
|
|
7774
|
+
if (!existsSync6(resolved)) continue;
|
|
7644
7775
|
const nested = expandImports(resolved, seen, depth + 1);
|
|
7645
7776
|
totalLines += nested.totalLines;
|
|
7646
7777
|
importedFiles += 1 + nested.importedFiles;
|
|
@@ -7651,8 +7782,8 @@ function detectBloatedClaudeMd(projectCwds) {
|
|
|
7651
7782
|
const bloated = [];
|
|
7652
7783
|
for (const cwd of projectCwds) {
|
|
7653
7784
|
for (const name of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
7654
|
-
const fullPath =
|
|
7655
|
-
if (!
|
|
7785
|
+
const fullPath = join26(cwd, name);
|
|
7786
|
+
if (!existsSync6(fullPath)) continue;
|
|
7656
7787
|
const { totalLines, importedFiles } = expandImports(fullPath, /* @__PURE__ */ new Set(), 0);
|
|
7657
7788
|
if (totalLines > CLAUDEMD_HEALTHY_LINES) {
|
|
7658
7789
|
bloated.push({ path: `${shortHomePath(cwd)}/${name}`, expandedLines: totalLines, imports: importedFiles });
|
|
@@ -7780,7 +7911,7 @@ function detectCacheBloat(apiCalls, projects, dateRange) {
|
|
|
7780
7911
|
};
|
|
7781
7912
|
}
|
|
7782
7913
|
async function listMarkdownFiles(dir) {
|
|
7783
|
-
if (!
|
|
7914
|
+
if (!existsSync6(dir)) return [];
|
|
7784
7915
|
try {
|
|
7785
7916
|
const entries = await readdir13(dir);
|
|
7786
7917
|
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(/\.md$/, ""));
|
|
@@ -7789,12 +7920,12 @@ async function listMarkdownFiles(dir) {
|
|
|
7789
7920
|
}
|
|
7790
7921
|
}
|
|
7791
7922
|
async function listSkillDirs(dir) {
|
|
7792
|
-
if (!
|
|
7923
|
+
if (!existsSync6(dir)) return [];
|
|
7793
7924
|
try {
|
|
7794
7925
|
const entries = await readdir13(dir);
|
|
7795
7926
|
const names = [];
|
|
7796
7927
|
for (const entry of entries) {
|
|
7797
|
-
if (
|
|
7928
|
+
if (existsSync6(join26(dir, entry, "SKILL.md"))) names.push(entry);
|
|
7798
7929
|
}
|
|
7799
7930
|
return names;
|
|
7800
7931
|
} catch {
|
|
@@ -7802,7 +7933,7 @@ async function listSkillDirs(dir) {
|
|
|
7802
7933
|
}
|
|
7803
7934
|
}
|
|
7804
7935
|
async function detectGhostAgents(calls) {
|
|
7805
|
-
const defined = await listMarkdownFiles(
|
|
7936
|
+
const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "agents"));
|
|
7806
7937
|
if (defined.length === 0) return null;
|
|
7807
7938
|
const invoked = /* @__PURE__ */ new Set();
|
|
7808
7939
|
for (const call of calls) {
|
|
@@ -7827,7 +7958,7 @@ async function detectGhostAgents(calls) {
|
|
|
7827
7958
|
};
|
|
7828
7959
|
}
|
|
7829
7960
|
async function detectGhostSkills(calls) {
|
|
7830
|
-
const defined = await listSkillDirs(
|
|
7961
|
+
const defined = await listSkillDirs(join26(homedir13(), ".claude", "skills"));
|
|
7831
7962
|
if (defined.length === 0) return null;
|
|
7832
7963
|
const invoked = /* @__PURE__ */ new Set();
|
|
7833
7964
|
for (const call of calls) {
|
|
@@ -7852,7 +7983,7 @@ async function detectGhostSkills(calls) {
|
|
|
7852
7983
|
};
|
|
7853
7984
|
}
|
|
7854
7985
|
async function detectGhostCommands(userMessages) {
|
|
7855
|
-
const defined = await listMarkdownFiles(
|
|
7986
|
+
const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "commands"));
|
|
7856
7987
|
if (defined.length === 0) return null;
|
|
7857
7988
|
const invoked = /* @__PURE__ */ new Set();
|
|
7858
7989
|
for (const msg of userMessages) {
|
|
@@ -7880,8 +8011,8 @@ async function detectGhostCommands(userMessages) {
|
|
|
7880
8011
|
}
|
|
7881
8012
|
function readShellProfileLimit() {
|
|
7882
8013
|
for (const profile of SHELL_PROFILES) {
|
|
7883
|
-
const path =
|
|
7884
|
-
if (!
|
|
8014
|
+
const path = join26(homedir13(), profile);
|
|
8015
|
+
if (!existsSync6(path)) continue;
|
|
7885
8016
|
const content = readSessionFileSync(path);
|
|
7886
8017
|
if (content === null) continue;
|
|
7887
8018
|
const match = content.match(/^\s*export\s+BASH_MAX_OUTPUT_LENGTH\s*=\s*['"]?(\d+)['"]?/m);
|
|
@@ -8104,7 +8235,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
8104
8235
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8105
8236
|
import { stat as stat18, writeFile as writeFile5 } from "fs/promises";
|
|
8106
8237
|
import { homedir as homedir14 } from "os";
|
|
8107
|
-
import { join as
|
|
8238
|
+
import { join as join27, resolve as resolve3 } from "path";
|
|
8108
8239
|
function codeBlock(content, language) {
|
|
8109
8240
|
const longest = (content.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
|
|
8110
8241
|
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
@@ -8198,7 +8329,7 @@ async function copyOptimizeMarkdown(markdown) {
|
|
|
8198
8329
|
throw new Error("Could not copy. Press e to save a Markdown file instead.");
|
|
8199
8330
|
}
|
|
8200
8331
|
async function saveOptimizeMarkdown(markdown, directory) {
|
|
8201
|
-
const downloads =
|
|
8332
|
+
const downloads = join27(homedir14(), "Downloads");
|
|
8202
8333
|
const folder = directory ?? ((await stat18(downloads).catch(() => null))?.isDirectory() ? downloads : process.cwd());
|
|
8203
8334
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8204
8335
|
const path = resolve3(folder, `exe-watcher-optimizer-${stamp}-${randomUUID4().slice(0, 8)}.md`);
|
|
@@ -8209,8 +8340,8 @@ async function saveOptimizeMarkdown(markdown, directory) {
|
|
|
8209
8340
|
// src/context-budget.ts
|
|
8210
8341
|
init_fs_utils();
|
|
8211
8342
|
import { readdir as readdir14 } from "fs/promises";
|
|
8212
|
-
import { existsSync as
|
|
8213
|
-
import { join as
|
|
8343
|
+
import { existsSync as existsSync7 } from "fs";
|
|
8344
|
+
import { join as join28 } from "path";
|
|
8214
8345
|
import { homedir as homedir15 } from "os";
|
|
8215
8346
|
var CHARS_PER_TOKEN2 = 4;
|
|
8216
8347
|
var SYSTEM_BASE_TOKENS = 10400;
|
|
@@ -8220,7 +8351,7 @@ function estimateTokens2(text) {
|
|
|
8220
8351
|
return Math.ceil(text.length / CHARS_PER_TOKEN2);
|
|
8221
8352
|
}
|
|
8222
8353
|
async function readConfigFile(path) {
|
|
8223
|
-
if (!
|
|
8354
|
+
if (!existsSync7(path)) return null;
|
|
8224
8355
|
const raw = await readSessionFile(path);
|
|
8225
8356
|
if (raw === null) return null;
|
|
8226
8357
|
try {
|
|
@@ -8232,13 +8363,13 @@ async function readConfigFile(path) {
|
|
|
8232
8363
|
async function countMcpTools(projectPath) {
|
|
8233
8364
|
const home = homedir15();
|
|
8234
8365
|
const configPaths = [
|
|
8235
|
-
|
|
8236
|
-
|
|
8366
|
+
join28(home, ".claude", "settings.json"),
|
|
8367
|
+
join28(home, ".claude", "settings.local.json")
|
|
8237
8368
|
];
|
|
8238
8369
|
if (projectPath) {
|
|
8239
|
-
configPaths.push(
|
|
8240
|
-
configPaths.push(
|
|
8241
|
-
configPaths.push(
|
|
8370
|
+
configPaths.push(join28(projectPath, ".mcp.json"));
|
|
8371
|
+
configPaths.push(join28(projectPath, ".claude", "settings.json"));
|
|
8372
|
+
configPaths.push(join28(projectPath, ".claude", "settings.local.json"));
|
|
8242
8373
|
}
|
|
8243
8374
|
const servers = /* @__PURE__ */ new Set();
|
|
8244
8375
|
let toolCount = 0;
|
|
@@ -8255,16 +8386,16 @@ async function countMcpTools(projectPath) {
|
|
|
8255
8386
|
return toolCount;
|
|
8256
8387
|
}
|
|
8257
8388
|
async function countSkills(projectPath) {
|
|
8258
|
-
const dirs = [
|
|
8259
|
-
if (projectPath) dirs.push(
|
|
8389
|
+
const dirs = [join28(homedir15(), ".claude", "skills")];
|
|
8390
|
+
if (projectPath) dirs.push(join28(projectPath, ".claude", "skills"));
|
|
8260
8391
|
let count = 0;
|
|
8261
8392
|
for (const dir of dirs) {
|
|
8262
|
-
if (!
|
|
8393
|
+
if (!existsSync7(dir)) continue;
|
|
8263
8394
|
try {
|
|
8264
8395
|
const entries = await readdir14(dir);
|
|
8265
8396
|
for (const entry of entries) {
|
|
8266
|
-
const skillFile =
|
|
8267
|
-
if (
|
|
8397
|
+
const skillFile = join28(dir, entry, "SKILL.md");
|
|
8398
|
+
if (existsSync7(skillFile)) count++;
|
|
8268
8399
|
}
|
|
8269
8400
|
} catch {
|
|
8270
8401
|
continue;
|
|
@@ -8276,15 +8407,15 @@ async function scanMemoryFiles(projectPath) {
|
|
|
8276
8407
|
const home = homedir15();
|
|
8277
8408
|
const files = [];
|
|
8278
8409
|
const paths = [
|
|
8279
|
-
{ path:
|
|
8410
|
+
{ path: join28(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
|
|
8280
8411
|
];
|
|
8281
8412
|
if (projectPath) {
|
|
8282
|
-
paths.push({ path:
|
|
8283
|
-
paths.push({ path:
|
|
8284
|
-
paths.push({ path:
|
|
8413
|
+
paths.push({ path: join28(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
|
|
8414
|
+
paths.push({ path: join28(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
|
|
8415
|
+
paths.push({ path: join28(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
|
|
8285
8416
|
}
|
|
8286
8417
|
for (const { path, name } of paths) {
|
|
8287
|
-
if (!
|
|
8418
|
+
if (!existsSync7(path)) continue;
|
|
8288
8419
|
const content = await readSessionFile(path);
|
|
8289
8420
|
if (content === null) continue;
|
|
8290
8421
|
files.push({ name, tokens: estimateTokens2(content) });
|
|
@@ -8318,7 +8449,7 @@ async function discoverProjectCwd(sessionDir) {
|
|
|
8318
8449
|
if (files.length === 0) return null;
|
|
8319
8450
|
const firstFile = files[0];
|
|
8320
8451
|
if (!firstFile) return null;
|
|
8321
|
-
const content = await readSessionFile(
|
|
8452
|
+
const content = await readSessionFile(join28(sessionDir, firstFile));
|
|
8322
8453
|
if (content === null) return null;
|
|
8323
8454
|
for (const line of content.split("\n")) {
|
|
8324
8455
|
if (!line.trim()) continue;
|
|
@@ -9012,20 +9143,20 @@ function owlFrame(tick) {
|
|
|
9012
9143
|
}
|
|
9013
9144
|
|
|
9014
9145
|
// src/dashboard.tsx
|
|
9015
|
-
import { join as
|
|
9146
|
+
import { join as join30 } from "path";
|
|
9016
9147
|
|
|
9017
9148
|
// src/time-index.ts
|
|
9018
9149
|
init_cache_dir();
|
|
9019
9150
|
init_file_lock();
|
|
9020
9151
|
init_resource_budget();
|
|
9021
|
-
import { createHash as
|
|
9152
|
+
import { createHash as createHash9, randomUUID as randomUUID5 } from "crypto";
|
|
9022
9153
|
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
|
|
9154
|
+
import { basename as basename11, dirname as dirname6, join as join29 } from "path";
|
|
9024
9155
|
import { gzip as gzip2, gunzip as gunzip2 } from "zlib";
|
|
9025
9156
|
import { promisify as promisify2 } from "util";
|
|
9026
9157
|
|
|
9027
9158
|
// src/time-tracking.ts
|
|
9028
|
-
import { createHash as
|
|
9159
|
+
import { createHash as createHash8 } from "crypto";
|
|
9029
9160
|
var TIME_HISTORY_START = (/* @__PURE__ */ new Date("2025-07-01T00:00:00")).getTime();
|
|
9030
9161
|
var TIME_PROVIDERS = ["claude", "codex", "cursor", "cursor-agent", "copilot", "opencode", "kimi", "pi", "omp"];
|
|
9031
9162
|
function timestamp(value) {
|
|
@@ -9238,7 +9369,7 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9238
9369
|
runnableFiles: pending.length - waitingFiles,
|
|
9239
9370
|
waitingFiles,
|
|
9240
9371
|
pendingBytes: pending.reduce((n, f) => n + Math.max(0, f.size - f.offset), 0),
|
|
9241
|
-
token:
|
|
9372
|
+
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
9373
|
};
|
|
9243
9374
|
return {
|
|
9244
9375
|
indexedAt: index.updatedAt,
|
|
@@ -9371,7 +9502,7 @@ var MAX_DISK2 = 16 * 1024 ** 2;
|
|
|
9371
9502
|
var MAX_FILES = 2e4;
|
|
9372
9503
|
var BLOCK = 2 * 1024 ** 2;
|
|
9373
9504
|
var adapterVersion = (provider) => provider === "opencode" ? 2 : 1;
|
|
9374
|
-
var indexPath2 = () =>
|
|
9505
|
+
var indexPath2 = () => join29(getCacheDir(), "time-index-v1.json.gz");
|
|
9375
9506
|
var empty = () => ({ version: 1, files: /* @__PURE__ */ Object.create(null), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), warnings: [] });
|
|
9376
9507
|
async function readTimeIndex() {
|
|
9377
9508
|
try {
|
|
@@ -9434,11 +9565,11 @@ async function expandSources(sources, warnings) {
|
|
|
9434
9565
|
continue;
|
|
9435
9566
|
}
|
|
9436
9567
|
for (const entry of await readdir15(source.path, { withFileTypes: true }).catch(() => [])) {
|
|
9437
|
-
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
9568
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join29(source.path, entry.name) });
|
|
9438
9569
|
else if (entry.isDirectory()) {
|
|
9439
|
-
const folder =
|
|
9570
|
+
const folder = join29(source.path, entry.name, "subagents");
|
|
9440
9571
|
for (const agent of await readdir15(folder, { withFileTypes: true }).catch(() => [])) {
|
|
9441
|
-
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
9572
|
+
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join29(folder, agent.name) });
|
|
9442
9573
|
}
|
|
9443
9574
|
}
|
|
9444
9575
|
}
|
|
@@ -9452,7 +9583,7 @@ async function anchor(path, offset) {
|
|
|
9452
9583
|
await handle.read(head, 0, head.length, 0);
|
|
9453
9584
|
await handle.read(tail, 0, tail.length, Math.max(0, offset - tail.length));
|
|
9454
9585
|
chargeRead(head.length + tail.length);
|
|
9455
|
-
return
|
|
9586
|
+
return createHash9("sha256").update(head).update(tail).digest("hex");
|
|
9456
9587
|
} finally {
|
|
9457
9588
|
await handle.close();
|
|
9458
9589
|
}
|
|
@@ -9535,7 +9666,7 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9535
9666
|
let current;
|
|
9536
9667
|
let release;
|
|
9537
9668
|
try {
|
|
9538
|
-
release = await acquireFileLock(
|
|
9669
|
+
release = await acquireFileLock(join29(getCacheDir(), "time-index.lock"), 100);
|
|
9539
9670
|
} catch {
|
|
9540
9671
|
current = await readTimeIndex();
|
|
9541
9672
|
current.warnings.push("Another timing update is running; showing the saved timing snapshot.");
|
|
@@ -9555,17 +9686,17 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9555
9686
|
let fileCount = Object.keys(current.files).length;
|
|
9556
9687
|
for (const source of expanded) {
|
|
9557
9688
|
chargeRead(0);
|
|
9558
|
-
const
|
|
9559
|
-
const s = await stat19(
|
|
9689
|
+
const database2 = source.provider === "opencode" ? source.path.slice(0, source.path.lastIndexOf(":")) : source.path;
|
|
9690
|
+
const s = await stat19(database2).catch(() => null);
|
|
9560
9691
|
if (!s) continue;
|
|
9561
|
-
const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(
|
|
9692
|
+
const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(database2 + "-wal").catch(() => null) : null;
|
|
9562
9693
|
candidates.push({ source, s, wal });
|
|
9563
9694
|
}
|
|
9564
9695
|
candidates.sort((a, b) => b.s.mtimeMs - a.s.mtimeMs || a.source.path.localeCompare(b.source.path));
|
|
9565
9696
|
if (candidates.length > MAX_FILES) current.warnings.push("Some timing sources exceed the bounded file limit; their time remains unknown.");
|
|
9566
9697
|
const deadline = Date.now() + (options.history ? 15e3 : 750);
|
|
9567
9698
|
for (const { source, s, wal } of candidates) {
|
|
9568
|
-
const key2 =
|
|
9699
|
+
const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
|
|
9569
9700
|
if (!current.files[key2] && fileCount < MAX_FILES) {
|
|
9570
9701
|
current.files[key2] = fresh(source, `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`, s.size, s.mtimeMs, s.ctimeMs);
|
|
9571
9702
|
changed = true;
|
|
@@ -9577,7 +9708,7 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9577
9708
|
}
|
|
9578
9709
|
for (const { source, s, wal } of candidates) {
|
|
9579
9710
|
chargeRead(0);
|
|
9580
|
-
const key2 =
|
|
9711
|
+
const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
|
|
9581
9712
|
const identity = `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`;
|
|
9582
9713
|
let row = current.files[key2];
|
|
9583
9714
|
const sameAdapter = (row?.adapterVersion ?? 1) === adapterVersion(source.provider);
|
|
@@ -10482,11 +10613,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
|
|
|
10482
10613
|
useEffect2(() => {
|
|
10483
10614
|
let cancelled = false;
|
|
10484
10615
|
async function loadBudgets() {
|
|
10485
|
-
const claudeDir =
|
|
10616
|
+
const claudeDir = join30(homedir16(), ".claude", "projects");
|
|
10486
10617
|
const budgets = /* @__PURE__ */ new Map();
|
|
10487
10618
|
for (const project of projects.slice(0, 8)) {
|
|
10488
10619
|
if (cancelled) return;
|
|
10489
|
-
const cwd = await discoverProjectCwd(
|
|
10620
|
+
const cwd = await discoverProjectCwd(join30(claudeDir, project.project));
|
|
10490
10621
|
if (!cwd) continue;
|
|
10491
10622
|
budgets.set(project.project, await estimateContextBudget(cwd));
|
|
10492
10623
|
}
|
|
@@ -10809,6 +10940,10 @@ function shouldSkipBlockedChunk(cursor2, date2, maxAttempts = MAX_BLOCKED_CHUNK_
|
|
|
10809
10940
|
// src/period-snapshots.ts
|
|
10810
10941
|
init_day_aggregator();
|
|
10811
10942
|
init_models();
|
|
10943
|
+
import { createHash as createHash10 } from "crypto";
|
|
10944
|
+
function hasRecordedHistory(cache, today) {
|
|
10945
|
+
return cache.days.some((day) => day.date < today && (day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0));
|
|
10946
|
+
}
|
|
10812
10947
|
function assertHistoryComplete(cache, start, today) {
|
|
10813
10948
|
if (start >= today) return;
|
|
10814
10949
|
const required = Math.round((Date.parse(today) - Date.parse(start)) / 864e5);
|
|
@@ -10830,7 +10965,26 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10830
10965
|
for (const window of windows) {
|
|
10831
10966
|
const required = Math.max(0, Math.round((Date.parse(today) - Date.parse(window.start)) / 864e5));
|
|
10832
10967
|
const covered = [...dates].filter((d) => d >= window.start && !partial.has(d)).length;
|
|
10833
|
-
|
|
10968
|
+
const incomplete = window.key !== "today" && (cache.needsRepricing || cache.historyBuild || cache.missingProviders?.length || covered !== required);
|
|
10969
|
+
const committed = closed.filter((day) => day.date >= window.start).sort((a, b) => a.date.localeCompare(b.date));
|
|
10970
|
+
const recorded = (day) => day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0;
|
|
10971
|
+
const hasData = committed.some(recorded) || window.key !== "all" && live2.some(recorded);
|
|
10972
|
+
const savedHistory = incomplete && (window.key !== "all" || hasData) ? {
|
|
10973
|
+
through: committed.at(-1)?.date ?? today,
|
|
10974
|
+
hasData,
|
|
10975
|
+
progressToken: createHash10("sha256").update(JSON.stringify([
|
|
10976
|
+
window.start,
|
|
10977
|
+
cache.lastComputedDate,
|
|
10978
|
+
cache.days.length,
|
|
10979
|
+
cache.historyBuild?.completedProviders,
|
|
10980
|
+
cache.historyBuild?.chunked,
|
|
10981
|
+
cache.missingProviders,
|
|
10982
|
+
cache.partialDates,
|
|
10983
|
+
cache.backfill
|
|
10984
|
+
])).digest("hex"),
|
|
10985
|
+
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.`
|
|
10986
|
+
} : void 0;
|
|
10987
|
+
if (incomplete && !savedHistory) continue;
|
|
10834
10988
|
const selected = periodDays(window.start);
|
|
10835
10989
|
const scoped = provider === "all" ? selected : filterDaysToProvider(selected, provider);
|
|
10836
10990
|
const data = buildPeriodDataFromDays(scoped, window.label);
|
|
@@ -10852,6 +11006,7 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10852
11006
|
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
11007
|
const built = buildMenubarPayload(data, providers, null);
|
|
10854
11008
|
result[window.key] = {
|
|
11009
|
+
...savedHistory ? { savedHistory } : {},
|
|
10855
11010
|
current: built.current,
|
|
10856
11011
|
projectSpend: buildProjectSpendFromDays(selected, live2, periodDays(week.start), periodDays(month.start)),
|
|
10857
11012
|
diagnostics: { daysCount: selected.length, parseTimeMs, warnings: notes },
|
|
@@ -10862,6 +11017,23 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
|
|
|
10862
11017
|
return result;
|
|
10863
11018
|
}
|
|
10864
11019
|
|
|
11020
|
+
// src/saved-history.ts
|
|
11021
|
+
init_day_aggregator();
|
|
11022
|
+
function buildSavedHistory(cache, today, provider, window = { start: "0000-01-01", label: "All Time" }) {
|
|
11023
|
+
if (!hasRecordedHistory(cache, today)) throw new Error("No saved history yet. Watcher is recovering local usage.");
|
|
11024
|
+
const closed = cache.days.filter((day) => day.date >= window.start && day.date < today).sort((a, b) => a.date.localeCompare(b.date));
|
|
11025
|
+
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.");
|
|
11026
|
+
const days = provider === "all" ? closed : filterDaysToProvider(closed, provider);
|
|
11027
|
+
const current = buildPeriodDataFromDays(days, window.label);
|
|
11028
|
+
const totals = /* @__PURE__ */ new Map();
|
|
11029
|
+
for (const day of days) for (const [name, data] of Object.entries(day.providers)) totals.set(name, (totals.get(name) ?? 0) + data.cost);
|
|
11030
|
+
const payload = buildMenubarPayload(current, [...totals].map(([name, cost]) => ({ name, cost })), null);
|
|
11031
|
+
return { ...payload, savedHistory: {
|
|
11032
|
+
through: closed.at(-1).date,
|
|
11033
|
+
message: `Saved ${window.label} history through ${closed.at(-1).date}. Newer usage has not loaded yet.`
|
|
11034
|
+
} };
|
|
11035
|
+
}
|
|
11036
|
+
|
|
10865
11037
|
// src/history-build.ts
|
|
10866
11038
|
init_day_aggregator();
|
|
10867
11039
|
init_report_summary();
|
|
@@ -10961,7 +11133,7 @@ async function completeHistoryBuild(cache, start, end, providers, scan, save2, o
|
|
|
10961
11133
|
|
|
10962
11134
|
// src/cli.ts
|
|
10963
11135
|
import { createInterface } from "readline";
|
|
10964
|
-
import { createRequire as
|
|
11136
|
+
import { createRequire as createRequire3 } from "module";
|
|
10965
11137
|
|
|
10966
11138
|
// src/menubar-lifecycle.ts
|
|
10967
11139
|
var live = { parent: () => process.ppid, checkAlive: (pid) => {
|
|
@@ -10995,8 +11167,8 @@ function buildTimeSnapshots(index, ranges, provider = "all", include, exclude) {
|
|
|
10995
11167
|
}
|
|
10996
11168
|
|
|
10997
11169
|
// src/cli.ts
|
|
10998
|
-
var
|
|
10999
|
-
var { version } =
|
|
11170
|
+
var require3 = createRequire3(import.meta.url);
|
|
11171
|
+
var { version } = require3("../package.json");
|
|
11000
11172
|
var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
|
|
11001
11173
|
var configuredHistoryStart = null;
|
|
11002
11174
|
function toDateString(date2) {
|
|
@@ -11111,14 +11283,14 @@ async function runJsonReport(period, provider, project, exclude) {
|
|
|
11111
11283
|
console.log(JSON.stringify({ ...report, pricing: getPricingMetadata() }, null, 2));
|
|
11112
11284
|
}
|
|
11113
11285
|
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) => {
|
|
11286
|
+
program.hook("preAction", async (thisCommand, command) => {
|
|
11115
11287
|
const config = await readConfig();
|
|
11116
11288
|
setModelAliases(config.modelAliases ?? {});
|
|
11117
11289
|
configuredHistoryStart = parseHistoryStartDate(config.historyStartDate);
|
|
11118
11290
|
if (thisCommand.opts().verbose) {
|
|
11119
11291
|
process.env["EXE_WATCHER_VERBOSE"] = "1";
|
|
11120
11292
|
}
|
|
11121
|
-
await loadCurrency();
|
|
11293
|
+
if (!command.opts().cachedOnly) await loadCurrency();
|
|
11122
11294
|
});
|
|
11123
11295
|
function buildJsonReport(projects, period, periodKey) {
|
|
11124
11296
|
const sessions = projects.flatMap((p) => p.sessions);
|
|
@@ -11308,7 +11480,19 @@ function buildPeriodData(label, projects) {
|
|
|
11308
11480
|
models: Object.entries(modelTotals).sort(([, a], [, b]) => b.cost - a.cost).map(([name, d]) => ({ name, ...d }))
|
|
11309
11481
|
};
|
|
11310
11482
|
}
|
|
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) => {
|
|
11483
|
+
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) => {
|
|
11484
|
+
if (opts.cachedOnly) {
|
|
11485
|
+
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");
|
|
11486
|
+
const cache = await loadDailyCache(buildDailyCacheScopeKey(opts.project, opts.exclude));
|
|
11487
|
+
const window = getDateRange2(opts.period);
|
|
11488
|
+
console.log(JSON.stringify(buildSavedHistory(
|
|
11489
|
+
cache,
|
|
11490
|
+
toDateString(/* @__PURE__ */ new Date()),
|
|
11491
|
+
opts.provider,
|
|
11492
|
+
{ start: opts.period === "all" ? "0000-01-01" : toDateString(window.range.start), label: window.label }
|
|
11493
|
+
)));
|
|
11494
|
+
return;
|
|
11495
|
+
}
|
|
11312
11496
|
await loadPricing();
|
|
11313
11497
|
const pf = opts.provider;
|
|
11314
11498
|
const fp = (p) => filterProjectsByName(p, opts.project, opts.exclude);
|
|
@@ -11388,7 +11572,7 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11388
11572
|
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
11573
|
return true;
|
|
11390
11574
|
};
|
|
11391
|
-
const cache = await withDailyCacheLock(async () => {
|
|
11575
|
+
const cache = await withScanBudget(() => withScanSlice(() => withDailyCacheLock(async () => {
|
|
11392
11576
|
let c = await loadDailyCache(dailyCacheScope);
|
|
11393
11577
|
if (opts.period === "today" && opts.optimize === false) return c;
|
|
11394
11578
|
if (c.missingProviders?.length && c.days.length && !c.needsRepricing) {
|
|
@@ -11405,19 +11589,13 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11405
11589
|
c = await buildHistory2(c, from, through);
|
|
11406
11590
|
if (pausedHistory(c)) return c;
|
|
11407
11591
|
}
|
|
11408
|
-
if (
|
|
11592
|
+
if (opts.period === "all" && c.days.length === 0) {
|
|
11409
11593
|
const start = parseHistoryStartDate(c.previousHistoryStart) ?? historyStart;
|
|
11410
11594
|
if (start <= yesterdayEnd) {
|
|
11411
11595
|
c = await buildHistory2(c, start, yesterdayEnd);
|
|
11412
11596
|
if (pausedHistory(c)) return c;
|
|
11413
11597
|
}
|
|
11414
11598
|
}
|
|
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
11599
|
const oldestCachedDate = c.days.length > 0 ? c.days[0]?.date ?? null : null;
|
|
11422
11600
|
const effectiveGapStart = computeProgressiveBackfillStart({
|
|
11423
11601
|
lastComputedDate: c.lastComputedDate,
|
|
@@ -11442,8 +11620,10 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11442
11620
|
}
|
|
11443
11621
|
}
|
|
11444
11622
|
return c;
|
|
11445
|
-
});
|
|
11446
|
-
|
|
11623
|
+
}), { maxMs: 1e4, maxBytes: 8 * 1024 ** 3, maxRecords: 2e6 }), 2e6, void 0, 8 * 1024 ** 3);
|
|
11624
|
+
if (opts.optimize !== false || opts.period === "all" && !hasRecordedHistory(cache, toDateString(todayStart))) {
|
|
11625
|
+
assertHistoryComplete(cache, toDateString(periodInfo.range.start), toDateString(todayStart));
|
|
11626
|
+
}
|
|
11447
11627
|
let currentData;
|
|
11448
11628
|
const scanRange = periodInfo.range;
|
|
11449
11629
|
const todayRange = { start: todayStart, end: periodInfo.range.end };
|
|
@@ -11517,7 +11697,7 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11517
11697
|
let historicalRecovery = null;
|
|
11518
11698
|
if ((opts.period === "all" || opts.optimize === false) && (isAllProviders || pf === "claude") && !opts.project.length && !opts.exclude.length && !cache.partialDates?.length && !cache.backfill) {
|
|
11519
11699
|
const snapshot = await readClaudeUsageSnapshot();
|
|
11520
|
-
historicalRecovery = recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart);
|
|
11700
|
+
historicalRecovery = lockClaudeRecovery(recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart));
|
|
11521
11701
|
if (historicalRecovery && opts.period === "all") {
|
|
11522
11702
|
applyClaudeRecovery(currentData, historicalRecovery);
|
|
11523
11703
|
const claudeProvider = providers.find((p) => p.name.toLowerCase() === "claude");
|
|
@@ -11585,13 +11765,13 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11585
11765
|
`);
|
|
11586
11766
|
}
|
|
11587
11767
|
}
|
|
11588
|
-
const exeOsDir =
|
|
11589
|
-
const exeOsDetected =
|
|
11768
|
+
const exeOsDir = join31(homedir17(), ".exe-os");
|
|
11769
|
+
const exeOsDetected = existsSync8(exeOsDir);
|
|
11590
11770
|
let agentStats = null;
|
|
11591
11771
|
let statsFileAge = null;
|
|
11592
11772
|
try {
|
|
11593
|
-
const statsPath =
|
|
11594
|
-
if (
|
|
11773
|
+
const statsPath = join31(exeOsDir, "agent-stats.json");
|
|
11774
|
+
if (existsSync8(statsPath)) {
|
|
11595
11775
|
statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
|
|
11596
11776
|
const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
|
|
11597
11777
|
if (parsed && Array.isArray(parsed.agents) && typeof parsed.generated === "string") {
|
|
@@ -11654,7 +11834,15 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11654
11834
|
if (periodSnapshots) for (const [key2, snapshot] of Object.entries(periodSnapshots)) {
|
|
11655
11835
|
snapshot.time = summarizeTime(timeIndex, getDateRange2(key2, now).range, pf, opts.project, opts.exclude);
|
|
11656
11836
|
}
|
|
11657
|
-
const json = JSON.stringify({
|
|
11837
|
+
const json = JSON.stringify({
|
|
11838
|
+
...payload,
|
|
11839
|
+
time,
|
|
11840
|
+
periodSnapshots,
|
|
11841
|
+
...periodSnapshots?.[opts.period]?.savedHistory ? { savedHistory: periodSnapshots[opts.period].savedHistory } : {},
|
|
11842
|
+
costBasis: "api-equivalent",
|
|
11843
|
+
pricing: getPricingMetadata(),
|
|
11844
|
+
historicalRecovery: opts.period === "all" ? historicalRecovery : null
|
|
11845
|
+
});
|
|
11658
11846
|
console.log(json);
|
|
11659
11847
|
return;
|
|
11660
11848
|
}
|
|
@@ -11984,12 +12172,12 @@ program.command("compare").description("Compare two AI models side-by-side").opt
|
|
|
11984
12172
|
await renderCompare(range, opts.provider);
|
|
11985
12173
|
});
|
|
11986
12174
|
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 &&
|
|
12175
|
+
const exeOsDir = join31(homedir17(), ".exe-os");
|
|
12176
|
+
const statsPath = join31(exeOsDir, "agent-stats.json");
|
|
12177
|
+
const pidPath = join31(exeOsDir, "exed.pid");
|
|
12178
|
+
const dirExists = existsSync8(exeOsDir);
|
|
12179
|
+
const statsExists = dirExists && existsSync8(statsPath);
|
|
12180
|
+
const pidExists = dirExists && existsSync8(pidPath);
|
|
11993
12181
|
const lines = [];
|
|
11994
12182
|
lines.push(`exe-os directory: ${dirExists ? "\u2713 found" : "\u2717 not found"} (${exeOsDir})`);
|
|
11995
12183
|
lines.push(`daemon pid file: ${pidExists ? "\u2713 found" : "\u2717 not found"}`);
|
|
@@ -12021,7 +12209,7 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
|
|
|
12021
12209
|
var releaseScan;
|
|
12022
12210
|
var stopParentWatch = watchMenubarParent(Number(process.env["EXE_WATCHER_PARENT_PID"]));
|
|
12023
12211
|
program.hook("preAction", async (_root, command) => {
|
|
12024
|
-
if (command.name() === "status") releaseScan = await acquireFileLock(
|
|
12212
|
+
if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join31(getCacheDir(), "scan.lock"));
|
|
12025
12213
|
});
|
|
12026
12214
|
program.hook("postAction", async () => {
|
|
12027
12215
|
await releaseScan?.();
|