exe-watcher 0.2.70 → 0.2.71

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/dist/cli.js CHANGED
@@ -1,54 +1,451 @@
1
1
  #!/usr/bin/env -S node --max-old-space-size=256 --expose-gc
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
8
  var __esm = (fn, res) => function __init() {
5
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
10
  };
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
7
14
  var __export = (target, all) => {
8
15
  for (var name in all)
9
16
  __defProp(target, name, { get: all[name], enumerable: true });
10
17
  };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key2 of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
22
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
11
34
 
12
- // src/cache-dir.ts
35
+ // vendor/odd-core-analytics/dist/privacy/schema.js
36
+ var require_schema = __commonJS({
37
+ "vendor/odd-core-analytics/dist/privacy/schema.js"(exports) {
38
+ "use strict";
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.ActiveClock = exports.UUID = exports.PLATFORMS = exports.DIAGNOSTICS_DISCLOSURE = exports.USAGE_DISCLOSURE = exports.DEFAULT_FRAMES = exports.ERROR_CATEGORIES = exports.HANSEL_FEATURES = exports.DEFAULT_COLLECTOR = void 0;
41
+ exports.record = record;
42
+ exports.sanitizeMetric = sanitizeMetric;
43
+ exports.sanitizeDiagnostic = sanitizeDiagnostic2;
44
+ exports.collectorOrigin = collectorOrigin;
45
+ exports.DEFAULT_COLLECTOR = "https://data.odd.community";
46
+ exports.HANSEL_FEATURES = ["home", "recordings", "vocabulary", "shortcuts", "dictation"];
47
+ exports.ERROR_CATEGORIES = ["crash", "unhandled_exception", "render_error", "ipc_error", "ml_error"];
48
+ exports.DEFAULT_FRAMES = ["app:///main/index.js", "app:///renderer/index.js"];
49
+ exports.USAGE_DISCLOSURE = "Share active time, recording duration, feature counts and outcomes, app version and platform with our own servers. A random ID unique to this app measures returning installations. Never audio, transcripts, prompts, account details or screen recordings.";
50
+ exports.DIAGNOSTICS_DISCLOSURE = "Share sanitized error categories, app version and code locations with our own Sentry server. Never error text, account details, recordings or memory dumps.";
51
+ exports.PLATFORMS = ["macos", "windows", "linux", "ios", "android", "web"];
52
+ exports.UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
53
+ var CLASSES = ["Error", "TypeError", "RangeError", "ReferenceError", "SyntaxError", "URIError", "EvalError", "AggregateError"];
54
+ function record(input) {
55
+ return input !== null && typeof input === "object" && !Array.isArray(input) ? input : null;
56
+ }
57
+ function sanitizeMetric(input, features = exports.HANSEL_FEATURES) {
58
+ const v = record(input);
59
+ if (!v)
60
+ return null;
61
+ if (v.event === "active")
62
+ return { event: "active" };
63
+ if (v.event === "feature" && typeof v.feature === "string" && features.includes(v.feature))
64
+ return { event: "feature", feature: v.feature };
65
+ if (v.event === "result" && typeof v.feature === "string" && features.includes(v.feature) && (v.outcome === "success" || v.outcome === "failure"))
66
+ return { event: "result", feature: v.feature, outcome: v.outcome };
67
+ if (v.event === "usage" && Number.isInteger(v.active_ms) && Number(v.active_ms) > 0 && Number(v.active_ms) <= 6e4)
68
+ return { event: "usage", active_ms: Number(v.active_ms) };
69
+ if (v.event === "recording" && Number.isInteger(v.recording_ms) && Number(v.recording_ms) > 0 && Number(v.recording_ms) <= 864e5)
70
+ return { event: "recording", recording_ms: Number(v.recording_ms) };
71
+ return null;
72
+ }
73
+ function sanitizeDiagnostic2(input, allowedFrames = exports.DEFAULT_FRAMES) {
74
+ const v = record(input);
75
+ if (!v || v.event !== "error" || !exports.ERROR_CATEGORIES.includes(v.category))
76
+ return null;
77
+ const frames = [];
78
+ if (Array.isArray(v.frames))
79
+ for (const inputFrame of v.frames.slice(-8)) {
80
+ const f = record(inputFrame);
81
+ if (f && typeof f.filename === "string" && allowedFrames.includes(f.filename) && Number.isInteger(f.lineno) && Number(f.lineno) > 0 && Number(f.lineno) <= 1e7 && Number.isInteger(f.colno) && Number(f.colno) >= 0 && Number(f.colno) <= 1e7) {
82
+ frames.push({ filename: f.filename, lineno: Number(f.lineno), colno: Number(f.colno) });
83
+ }
84
+ }
85
+ return {
86
+ event: "error",
87
+ category: v.category,
88
+ error_class: typeof v.error_class === "string" && CLASSES.includes(v.error_class) ? v.error_class : "Error",
89
+ version: typeof v.version === "string" && /^\d{1,4}\.\d{1,4}\.\d{1,4}$/.test(v.version) ? v.version : "0.0.0",
90
+ frames
91
+ };
92
+ }
93
+ function collectorOrigin(input) {
94
+ const u = new URL(input);
95
+ if (u.protocol !== "https:" || u.username || u.password || u.pathname !== "/" || u.search || u.hash || /(^|\.)(posthog\.com|posthog\.net|google\.com|googleapis\.com|google-analytics\.com|googletagmanager\.com|firebaseapp\.com|firebaseio\.com)$/.test(u.hostname))
96
+ throw new Error("Use an operator-controlled HTTPS collector origin");
97
+ return u.origin;
98
+ }
99
+ var ActiveClock = class {
100
+ constructor() {
101
+ this.previous = null;
102
+ }
103
+ sample(time, focused, idleSeconds, recording = false) {
104
+ if (!Number.isFinite(time) || !Number.isFinite(idleSeconds) || idleSeconds < 0) {
105
+ this.reset();
106
+ return 0;
107
+ }
108
+ const active2 = recording || focused && idleSeconds < 60;
109
+ const previous = this.previous;
110
+ this.previous = { time, active: active2 };
111
+ if (!previous || !previous.active || !active2)
112
+ return 0;
113
+ const delta = time - previous.time;
114
+ return delta > 0 && delta <= 15e3 ? Math.floor(delta) : 0;
115
+ }
116
+ reset() {
117
+ this.previous = null;
118
+ }
119
+ };
120
+ exports.ActiveClock = ActiveClock;
121
+ }
122
+ });
123
+
124
+ // vendor/odd-core-analytics/dist/privacy/client.js
125
+ var require_client = __commonJS({
126
+ "vendor/odd-core-analytics/dist/privacy/client.js"(exports) {
127
+ "use strict";
128
+ Object.defineProperty(exports, "__esModule", { value: true });
129
+ exports.PrivacyClient = void 0;
130
+ var schema_1 = require_schema();
131
+ var WEEK = 7 * 864e5;
132
+ var empty2 = () => ({ consent: { usage: false, diagnostics: false }, id: "", usage: [], diagnostics: [] });
133
+ var PrivacyClient2 = class _PrivacyClient {
134
+ constructor(options) {
135
+ this.options = options;
136
+ this.state = empty2();
137
+ this.serial = Promise.resolve();
138
+ this.generation = { usage: 0, diagnostics: 0 };
139
+ this.sending = {};
140
+ this.options = { ...options };
141
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(options.app) || ![options.usageAlias, options.diagnosticsAlias].every((v) => /^[a-zA-Z0-9_-]{1,128}$/.test(v)) || options.features.length > 64 || !options.features.every((v) => /^[a-z][a-z0-9_]{0,63}$/.test(v)))
142
+ throw new Error("Invalid static app configuration");
143
+ if (options.appVersion !== void 0 && !/^\d{1,4}\.\d{1,4}\.\d{1,4}$/.test(options.appVersion))
144
+ throw new Error("Invalid app version");
145
+ if (options.platform !== void 0 && !schema_1.PLATFORMS.includes(options.platform))
146
+ throw new Error("Invalid platform");
147
+ this.features = [...options.features];
148
+ this.frames = [...options.frames ?? schema_1.DEFAULT_FRAMES];
149
+ if (this.frames.length > 16 || !this.frames.every((v) => /^app:\/\/\/[a-zA-Z0-9_/-]+\.(js|ts|swift|kt|dart)$/.test(v) && !v.includes("..")))
150
+ throw new Error("Invalid logical source configuration");
151
+ this.origin = (0, schema_1.collectorOrigin)(options.collector ?? schema_1.DEFAULT_COLLECTOR);
152
+ this.key = `odd-privacy-v1:${options.app}`;
153
+ this.now = options.now ?? Date.now;
154
+ }
155
+ static async create(options) {
156
+ const client = new _PrivacyClient(options);
157
+ try {
158
+ const input = (0, schema_1.record)(JSON.parse(await options.storage.getItem(client.key) ?? "null"));
159
+ if (!input || input.binding !== client.binding())
160
+ return client;
161
+ const consent = (0, schema_1.record)(input.consent);
162
+ client.state.consent.diagnostics = consent?.diagnostics === true;
163
+ client.state.consent.usage = consent?.usage === true && typeof input.id === "string" && schema_1.UUID.test(input.id);
164
+ client.state.id = client.state.consent.usage ? input.id : "";
165
+ for (const kind of ["usage", "diagnostics"]) {
166
+ if (!client.state.consent[kind] || !Array.isArray(input[kind]))
167
+ continue;
168
+ for (const raw of input[kind].slice(-500)) {
169
+ const row = (0, schema_1.record)(raw);
170
+ const metric = client.sanitize(kind, row?.metric);
171
+ if (row && metric && typeof row.uuid === "string" && schema_1.UUID.test(row.uuid) && client.fresh(row.timestamp)) {
172
+ client.state[kind].push({ uuid: row.uuid, timestamp: new Date(Date.parse(row.timestamp)).toISOString(), metric });
173
+ }
174
+ }
175
+ }
176
+ await client.persist();
177
+ } catch {
178
+ client.state = empty2();
179
+ }
180
+ return client;
181
+ }
182
+ binding() {
183
+ return JSON.stringify([this.origin, this.options.app, this.options.usageAlias, this.options.diagnosticsAlias]);
184
+ }
185
+ fresh(time) {
186
+ const value = typeof time === "string" ? Date.parse(time) : NaN;
187
+ return Number.isFinite(value) && value <= this.now() && this.now() - value < WEEK;
188
+ }
189
+ sanitize(kind, input) {
190
+ return kind === "usage" ? (0, schema_1.sanitizeMetric)(input, this.features) : (0, schema_1.sanitizeDiagnostic)(input, this.frames);
191
+ }
192
+ uuid() {
193
+ const id = this.options.randomUUID();
194
+ if (typeof id !== "string" || !schema_1.UUID.test(id))
195
+ throw new Error("A cryptographic UUID provider is required");
196
+ return id;
197
+ }
198
+ run(fn) {
199
+ const next = this.serial.then(fn);
200
+ this.serial = next.catch(() => void 0);
201
+ return next;
202
+ }
203
+ async persist() {
204
+ try {
205
+ await this.options.storage.setItem(this.key, JSON.stringify({ binding: this.binding(), ...this.state }));
206
+ return true;
207
+ } catch {
208
+ this.state = empty2();
209
+ this.abort();
210
+ return false;
211
+ }
212
+ }
213
+ status() {
214
+ return { ...this.state.consent };
215
+ }
216
+ /** Call only after the app presents disclosure and records an explicit user choice. */
217
+ setConsent(update) {
218
+ for (const kind of ["usage", "diagnostics"])
219
+ if (update[kind] === false) {
220
+ this.generation[kind]++;
221
+ this.sending[kind]?.abort();
222
+ }
223
+ return this.run(async () => {
224
+ try {
225
+ for (const kind of ["usage", "diagnostics"])
226
+ if (typeof update[kind] === "boolean" && this.state.consent[kind] !== update[kind]) {
227
+ this.generation[kind]++;
228
+ this.sending[kind]?.abort();
229
+ this.state.consent[kind] = update[kind];
230
+ this.state[kind] = [];
231
+ if (kind === "usage")
232
+ this.state.id = update[kind] ? this.uuid() : "";
233
+ }
234
+ return await this.persist();
235
+ } catch {
236
+ this.state = empty2();
237
+ this.abort();
238
+ return false;
239
+ }
240
+ });
241
+ }
242
+ track(input) {
243
+ return this.enqueue("usage", input);
244
+ }
245
+ report(input) {
246
+ return this.enqueue("diagnostics", input);
247
+ }
248
+ enqueue(kind, input) {
249
+ const metric = this.sanitize(kind, input);
250
+ const generation = this.generation[kind];
251
+ const allowed = this.state.consent[kind];
252
+ return this.run(async () => {
253
+ if (!allowed || !this.state.consent[kind] || generation !== this.generation[kind] || !metric)
254
+ return false;
255
+ try {
256
+ this.state[kind] = this.state[kind].filter((row) => this.fresh(row.timestamp)).slice(-499);
257
+ this.state[kind].push({ uuid: this.uuid(), timestamp: new Date(this.now()).toISOString(), metric });
258
+ return await this.persist();
259
+ } catch {
260
+ return false;
261
+ }
262
+ });
263
+ }
264
+ /** One bounded batch per channel; schedule retries in the app's lifecycle. */
265
+ async flush() {
266
+ await Promise.all([this.flushKind("usage"), this.flushKind("diagnostics")]);
267
+ }
268
+ async flushKind(kind) {
269
+ const requestedGeneration = this.generation[kind];
270
+ const pending = await this.run(async () => {
271
+ if (this.sending[kind] || !this.state.consent[kind] || requestedGeneration !== this.generation[kind])
272
+ return null;
273
+ const before = this.state[kind].length;
274
+ this.state[kind] = this.state[kind].filter((row) => this.fresh(row.timestamp));
275
+ if (before !== this.state[kind].length && !await this.persist())
276
+ return null;
277
+ const batch2 = this.state[kind].slice(0, kind === "usage" ? 50 : 10);
278
+ if (!batch2.length)
279
+ return null;
280
+ const controller2 = new AbortController();
281
+ this.sending[kind] = controller2;
282
+ return { batch: batch2, controller: controller2, generation: this.generation[kind], id: this.state.id };
283
+ });
284
+ if (!pending)
285
+ return;
286
+ const { batch, controller, generation, id } = pending;
287
+ const timer = setTimeout(() => controller.abort(), kind === "usage" ? 5e3 : 1e4);
288
+ try {
289
+ if (controller.signal.aborted)
290
+ return;
291
+ const response = await (this.options.send ?? fetch)(`${this.origin}/${kind === "usage" ? "batch" : "errors"}/`, {
292
+ method: "POST",
293
+ redirect: "error",
294
+ credentials: "omit",
295
+ referrerPolicy: "no-referrer",
296
+ signal: controller.signal,
297
+ headers: { "Content-Type": "application/json" },
298
+ body: JSON.stringify({
299
+ api_key: kind === "usage" ? this.options.usageAlias : this.options.diagnosticsAlias,
300
+ batch: batch.map((row) => kind === "diagnostics" ? { uuid: row.uuid, timestamp: row.timestamp, diagnostic: row.metric } : {
301
+ uuid: row.uuid,
302
+ timestamp: row.timestamp,
303
+ event: `app_${row.metric.event}`,
304
+ properties: {
305
+ distinct_id: id,
306
+ app: this.options.app,
307
+ ...row.metric,
308
+ ...this.options.appVersion ? { app_version: this.options.appVersion } : {},
309
+ ...this.options.platform ? { app_platform: this.options.platform } : {},
310
+ $process_person_profile: false,
311
+ $geoip_disable: true
312
+ }
313
+ })
314
+ })
315
+ });
316
+ if (response.ok)
317
+ await this.run(async () => {
318
+ if (generation !== this.generation[kind])
319
+ return;
320
+ const sent = new Set(batch.map((row) => row.uuid));
321
+ this.state[kind] = this.state[kind].filter((row) => !sent.has(row.uuid));
322
+ await this.persist();
323
+ });
324
+ await response.body?.cancel();
325
+ } catch {
326
+ } finally {
327
+ clearTimeout(timer);
328
+ if (this.sending[kind] === controller)
329
+ delete this.sending[kind];
330
+ }
331
+ }
332
+ abort() {
333
+ this.sending.usage?.abort();
334
+ this.sending.diagnostics?.abort();
335
+ }
336
+ };
337
+ exports.PrivacyClient = PrivacyClient2;
338
+ }
339
+ });
340
+
341
+ // vendor/odd-core-analytics/dist/privacy/index.js
342
+ var require_privacy = __commonJS({
343
+ "vendor/odd-core-analytics/dist/privacy/index.js"(exports) {
344
+ "use strict";
345
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
346
+ if (k2 === void 0) k2 = k;
347
+ var desc = Object.getOwnPropertyDescriptor(m, k);
348
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
349
+ desc = { enumerable: true, get: function() {
350
+ return m[k];
351
+ } };
352
+ }
353
+ Object.defineProperty(o, k2, desc);
354
+ }) : (function(o, m, k, k2) {
355
+ if (k2 === void 0) k2 = k;
356
+ o[k2] = m[k];
357
+ }));
358
+ var __exportStar = exports && exports.__exportStar || function(m, exports2) {
359
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
360
+ };
361
+ Object.defineProperty(exports, "__esModule", { value: true });
362
+ __exportStar(require_schema(), exports);
363
+ __exportStar(require_client(), exports);
364
+ }
365
+ });
366
+
367
+ // src/config.ts
368
+ import { readFile, mkdir, rename } from "fs/promises";
369
+ import { open } from "fs/promises";
13
370
  import { join } from "path";
14
371
  import { homedir } from "os";
15
- function getCacheDir() {
16
- const explicit = process.env["EXE_WATCHER_CACHE_DIR"];
17
- if (explicit) return explicit;
18
- const xdgCache = process.env["XDG_CACHE_HOME"];
19
- return join(xdgCache || join(homedir(), ".cache"), "exe-watcher");
372
+ import { randomBytes } from "crypto";
373
+ function getConfigDir() {
374
+ const xdgConfig = process.env["XDG_CONFIG_HOME"];
375
+ return join(xdgConfig || join(homedir(), ".config"), "exe-watcher");
20
376
  }
21
- var init_cache_dir = __esm({
22
- "src/cache-dir.ts"() {
377
+ function getConfigPath() {
378
+ return join(getConfigDir(), "config.json");
379
+ }
380
+ async function readConfig() {
381
+ try {
382
+ const raw = await readFile(getConfigPath(), "utf-8");
383
+ return JSON.parse(raw);
384
+ } catch {
385
+ return {};
386
+ }
387
+ }
388
+ async function saveConfig(config) {
389
+ await mkdir(getConfigDir(), { recursive: true });
390
+ const configPath = getConfigPath();
391
+ const tmpPath = `${configPath}.${randomBytes(8).toString("hex")}.tmp`;
392
+ const handle = await open(tmpPath, "w", 384);
393
+ try {
394
+ await handle.writeFile(JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8" });
395
+ await handle.sync();
396
+ } finally {
397
+ await handle.close();
398
+ }
399
+ await rename(tmpPath, configPath);
400
+ }
401
+ async function readPlan() {
402
+ const config = await readConfig();
403
+ return config.plan;
404
+ }
405
+ async function savePlan(plan) {
406
+ const config = await readConfig();
407
+ config.plan = plan;
408
+ await saveConfig(config);
409
+ }
410
+ async function clearPlan() {
411
+ const config = await readConfig();
412
+ delete config.plan;
413
+ await saveConfig(config);
414
+ }
415
+ function getConfigFilePath() {
416
+ return getConfigPath();
417
+ }
418
+ var init_config = __esm({
419
+ "src/config.ts"() {
23
420
  "use strict";
24
421
  }
25
422
  });
26
423
 
27
424
  // src/file-lock.ts
28
- import { mkdir, open, readFile, stat, unlink } from "fs/promises";
425
+ import { mkdir as mkdir2, open as open2, readFile as readFile2, stat, unlink } from "fs/promises";
29
426
  import { dirname } from "path";
30
427
  async function acquireFileLock(path, timeoutMs = 15e3) {
31
- await mkdir(dirname(path), { recursive: true });
428
+ await mkdir2(dirname(path), { recursive: true });
32
429
  const token = `${process.pid}:${Date.now()}:${Math.random()}`;
33
430
  const started = Date.now();
34
431
  while (true) {
35
432
  try {
36
- const handle = await open(path, "wx", 384);
433
+ const handle = await open2(path, "wx", 384);
37
434
  await handle.writeFile(token);
38
435
  await handle.close();
39
436
  return async () => {
40
- if (await readFile(path, "utf8").catch(() => "") === token) await unlink(path).catch(() => {
437
+ if (await readFile2(path, "utf8").catch(() => "") === token) await unlink(path).catch(() => {
41
438
  });
42
439
  };
43
440
  } catch (error) {
44
441
  if (error.code !== "EEXIST") throw error;
45
442
  }
46
443
  const reaperPath = `${path}.reaper`;
47
- const reaper = await open(reaperPath, "wx", 384).catch(() => null);
444
+ const reaper = await open2(reaperPath, "wx", 384).catch(() => null);
48
445
  if (reaper) {
49
446
  try {
50
447
  await reaper.writeFile(token);
51
- const owner = await readFile(path, "utf8").catch(() => "");
448
+ const owner = await readFile2(path, "utf8").catch(() => "");
52
449
  const pid = Number(owner.split(":")[0]);
53
450
  let dead = false;
54
451
  if (pid > 0) {
@@ -69,7 +466,7 @@ async function acquireFileLock(path, timeoutMs = 15e3) {
69
466
  });
70
467
  }
71
468
  } else {
72
- const owner = await readFile(reaperPath, "utf8").catch(() => "");
469
+ const owner = await readFile2(reaperPath, "utf8").catch(() => "");
73
470
  const pid = Number(owner.split(":")[0]);
74
471
  let dead = false;
75
472
  if (pid > 0) {
@@ -82,7 +479,7 @@ async function acquireFileLock(path, timeoutMs = 15e3) {
82
479
  const s = await stat(reaperPath).catch(() => null);
83
480
  dead = !!s && Date.now() - s.mtimeMs > 5e3;
84
481
  }
85
- if (dead && await readFile(reaperPath, "utf8").catch(() => "") === owner) await unlink(reaperPath).catch(() => {
482
+ if (dead && await readFile2(reaperPath, "utf8").catch(() => "") === owner) await unlink(reaperPath).catch(() => {
86
483
  });
87
484
  }
88
485
  if (Date.now() - started >= timeoutMs) throw new Error("Another Watcher scan is still running. Keeping the last successful refresh.");
@@ -95,6 +492,21 @@ var init_file_lock = __esm({
95
492
  }
96
493
  });
97
494
 
495
+ // src/cache-dir.ts
496
+ import { join as join3 } from "path";
497
+ import { homedir as homedir2 } from "os";
498
+ function getCacheDir() {
499
+ const explicit = process.env["EXE_WATCHER_CACHE_DIR"];
500
+ if (explicit) return explicit;
501
+ const xdgCache = process.env["XDG_CACHE_HOME"];
502
+ return join3(xdgCache || join3(homedir2(), ".cache"), "exe-watcher");
503
+ }
504
+ var init_cache_dir = __esm({
505
+ "src/cache-dir.ts"() {
506
+ "use strict";
507
+ }
508
+ });
509
+
98
510
  // src/resource-budget.ts
99
511
  import { getHeapStatistics } from "v8";
100
512
  import { AsyncLocalStorage } from "async_hooks";
@@ -174,66 +586,9 @@ var init_types = __esm({
174
586
  }
175
587
  });
176
588
 
177
- // src/config.ts
178
- import { readFile as readFile2, mkdir as mkdir3, rename as rename2 } from "fs/promises";
179
- import { open as open2 } from "fs/promises";
180
- import { join as join3 } from "path";
181
- import { homedir as homedir3 } from "os";
182
- import { randomBytes } from "crypto";
183
- function getConfigDir() {
184
- const xdgConfig = process.env["XDG_CONFIG_HOME"];
185
- return join3(xdgConfig || join3(homedir3(), ".config"), "exe-watcher");
186
- }
187
- function getConfigPath() {
188
- return join3(getConfigDir(), "config.json");
189
- }
190
- async function readConfig() {
191
- try {
192
- const raw = await readFile2(getConfigPath(), "utf-8");
193
- return JSON.parse(raw);
194
- } catch {
195
- return {};
196
- }
197
- }
198
- async function saveConfig(config) {
199
- await mkdir3(getConfigDir(), { recursive: true });
200
- const configPath = getConfigPath();
201
- const tmpPath = `${configPath}.${randomBytes(8).toString("hex")}.tmp`;
202
- const handle = await open2(tmpPath, "w", 384);
203
- try {
204
- await handle.writeFile(JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8" });
205
- await handle.sync();
206
- } finally {
207
- await handle.close();
208
- }
209
- await rename2(tmpPath, configPath);
210
- }
211
- async function readPlan() {
212
- const config = await readConfig();
213
- return config.plan;
214
- }
215
- async function savePlan(plan) {
216
- const config = await readConfig();
217
- config.plan = plan;
218
- await saveConfig(config);
219
- }
220
- async function clearPlan() {
221
- const config = await readConfig();
222
- delete config.plan;
223
- await saveConfig(config);
224
- }
225
- function getConfigFilePath() {
226
- return getConfigPath();
227
- }
228
- var init_config = __esm({
229
- "src/config.ts"() {
230
- "use strict";
231
- }
232
- });
233
-
234
589
  // src/compare-stats.ts
235
- import { readdir, readFile as readFile4 } from "fs/promises";
236
- import { join as join5 } from "path";
590
+ import { readdir, readFile as readFile5 } from "fs/promises";
591
+ import { join as join6 } from "path";
237
592
  function aggregateModelStats(projects) {
238
593
  const byModel = /* @__PURE__ */ new Map();
239
594
  const ensure = (model) => {
@@ -404,12 +759,12 @@ async function collectJsonlFiles(sessionDir) {
404
759
  const files = [];
405
760
  for (const entry of entries) {
406
761
  if (entry.isFile() && entry.name.endsWith(".jsonl") && !isCompactFile(entry.name)) {
407
- files.push(join5(sessionDir, entry.name));
762
+ files.push(join6(sessionDir, entry.name));
408
763
  } else if (entry.isDirectory() && entry.name === "subagents") {
409
- const subEntries = await readdir(join5(sessionDir, entry.name), { withFileTypes: true });
764
+ const subEntries = await readdir(join6(sessionDir, entry.name), { withFileTypes: true });
410
765
  for (const sub of subEntries) {
411
766
  if (sub.isFile() && sub.name.endsWith(".jsonl") && !isCompactFile(sub.name)) {
412
- files.push(join5(sessionDir, entry.name, sub.name));
767
+ files.push(join6(sessionDir, entry.name, sub.name));
413
768
  }
414
769
  }
415
770
  }
@@ -429,10 +784,10 @@ async function scanSelfCorrections(projectDirs) {
429
784
  const allFiles = [];
430
785
  for (const entry of entries) {
431
786
  if (entry.isFile() && entry.name.endsWith(".jsonl") && !isCompactFile(entry.name)) {
432
- allFiles.push(join5(dir, entry.name));
787
+ allFiles.push(join6(dir, entry.name));
433
788
  } else if (entry.isDirectory()) {
434
789
  try {
435
- const sessionFiles = await collectJsonlFiles(join5(dir, entry.name));
790
+ const sessionFiles = await collectJsonlFiles(join6(dir, entry.name));
436
791
  allFiles.push(...sessionFiles);
437
792
  } catch {
438
793
  continue;
@@ -442,7 +797,7 @@ async function scanSelfCorrections(projectDirs) {
442
797
  for (const file of allFiles) {
443
798
  let raw;
444
799
  try {
445
- raw = await readFile4(file, "utf8");
800
+ raw = await readFile5(file, "utf8");
446
801
  } catch {
447
802
  continue;
448
803
  }
@@ -2116,9 +2471,9 @@ var init_verified_pricing = __esm({
2116
2471
  });
2117
2472
 
2118
2473
  // src/models.ts
2119
- import { readFile as readFile5, mkdir as mkdir6, rename as rename4, unlink as unlink3 } from "fs/promises";
2474
+ import { readFile as readFile6, mkdir as mkdir7, rename as rename5, unlink as unlink4 } from "fs/promises";
2120
2475
  import { open as open4 } from "fs/promises";
2121
- import { join as join7 } from "path";
2476
+ import { join as join8 } from "path";
2122
2477
  import { createHash, randomBytes as randomBytes3 } from "crypto";
2123
2478
  function getPricingWarnings() {
2124
2479
  return _pricingWarnings;
@@ -2134,10 +2489,10 @@ function getPricingMetadata() {
2134
2489
  };
2135
2490
  }
2136
2491
  function getCachePath() {
2137
- return join7(getCacheDir(), "litellm-pricing.json");
2492
+ return join8(getCacheDir(), "litellm-pricing.json");
2138
2493
  }
2139
2494
  function getLastKnownGoodPath() {
2140
- return join7(getCacheDir(), "pricing-cache.json");
2495
+ return join8(getCacheDir(), "pricing-cache.json");
2141
2496
  }
2142
2497
  function parseLiteLLMEntry(entry) {
2143
2498
  if (entry.input_cost_per_token === void 0 || entry.output_cost_per_token === void 0) return null;
@@ -2190,7 +2545,7 @@ async function fetchAndCachePricing() {
2190
2545
  for (const [name, costs2] of official.value.models) pricing.set(name, costs2);
2191
2546
  }
2192
2547
  if (!pricing.size) throw new Error("No pricing sources available");
2193
- await mkdir6(getCacheDir(), { recursive: true });
2548
+ await mkdir7(getCacheDir(), { recursive: true });
2194
2549
  const finalPath = getCachePath();
2195
2550
  const tmpPath = `${finalPath}.${randomBytes3(8).toString("hex")}.tmp`;
2196
2551
  const handle = await open4(tmpPath, "w", 384);
@@ -2206,10 +2561,10 @@ async function fetchAndCachePricing() {
2206
2561
  await handle.close();
2207
2562
  }
2208
2563
  try {
2209
- await rename4(tmpPath, finalPath);
2564
+ await rename5(tmpPath, finalPath);
2210
2565
  } catch (err) {
2211
2566
  try {
2212
- await unlink3(tmpPath);
2567
+ await unlink4(tmpPath);
2213
2568
  } catch {
2214
2569
  }
2215
2570
  throw err;
@@ -2229,9 +2584,9 @@ async function fetchAndCachePricing() {
2229
2584
  } finally {
2230
2585
  await lkgHandle.close();
2231
2586
  }
2232
- await rename4(lkgTmp, lkgPath).catch(() => {
2587
+ await rename5(lkgTmp, lkgPath).catch(() => {
2233
2588
  try {
2234
- unlink3(lkgTmp);
2589
+ unlink4(lkgTmp);
2235
2590
  } catch {
2236
2591
  }
2237
2592
  });
@@ -2241,7 +2596,7 @@ async function fetchAndCachePricing() {
2241
2596
  }
2242
2597
  async function loadCachedPricing() {
2243
2598
  try {
2244
- const raw = await readFile5(getCachePath(), "utf-8");
2599
+ const raw = await readFile6(getCachePath(), "utf-8");
2245
2600
  const cached = JSON.parse(raw);
2246
2601
  if (cached.schemaVersion !== PRICING_CACHE_SCHEMA) return null;
2247
2602
  if (Date.now() - cached.timestamp > CACHE_TTL_MS2) return null;
@@ -2253,7 +2608,7 @@ async function loadCachedPricing() {
2253
2608
  }
2254
2609
  async function loadLastKnownGoodPricing() {
2255
2610
  try {
2256
- const raw = await readFile5(getLastKnownGoodPath(), "utf-8");
2611
+ const raw = await readFile6(getLastKnownGoodPath(), "utf-8");
2257
2612
  const cached = JSON.parse(raw);
2258
2613
  if (cached.schemaVersion !== PRICING_CACHE_SCHEMA) return null;
2259
2614
  _pricingWarnings.push(...cached.warnings ?? []);
@@ -2545,11 +2900,11 @@ var init_models = __esm({
2545
2900
  });
2546
2901
 
2547
2902
  // src/discovery-cache.ts
2548
- import { randomUUID } from "crypto";
2549
- import { mkdir as mkdir7, readFile as readFile6, rename as rename5, stat as stat4, unlink as unlink4, writeFile as writeFile2 } from "fs/promises";
2550
- import { join as join8 } from "path";
2903
+ import { randomUUID as randomUUID2 } from "crypto";
2904
+ import { mkdir as mkdir8, readFile as readFile7, rename as rename6, stat as stat4, unlink as unlink5, writeFile as writeFile3 } from "fs/promises";
2905
+ import { join as join9 } from "path";
2551
2906
  function manifestPath() {
2552
- return join8(getCacheDir(), MANIFEST);
2907
+ return join9(getCacheDir(), MANIFEST);
2553
2908
  }
2554
2909
  function fingerprintOf(s) {
2555
2910
  return [s.dev, s.ino, s.size, s.mtimeMs, s.ctimeMs].join(":");
@@ -2581,7 +2936,7 @@ async function load() {
2581
2936
  const s = await stat4(path);
2582
2937
  if (s.size > MAX_MANIFEST_BYTES) return loaded;
2583
2938
  chargeRead(s.size);
2584
- loaded = sanitize(JSON.parse(await readFile6(path, "utf8")));
2939
+ loaded = sanitize(JSON.parse(await readFile7(path, "utf8")));
2585
2940
  } catch (err) {
2586
2941
  if (err instanceof ResourceBudgetError) throw err;
2587
2942
  }
@@ -2639,10 +2994,10 @@ async function flushDiscoveryCache() {
2639
2994
  const path = manifestPath();
2640
2995
  const dir = getCacheDir();
2641
2996
  try {
2642
- await mkdir7(dir, { recursive: true });
2643
- const release = await acquireFileLock(join8(dir, "discovery-index.lock"));
2997
+ await mkdir8(dir, { recursive: true });
2998
+ const release = await acquireFileLock(join9(dir, "discovery-index.lock"));
2644
2999
  try {
2645
- const onDisk = await readFile6(path, "utf8").then((raw) => sanitize(JSON.parse(raw))).catch(() => emptyManifest());
3000
+ const onDisk = await readFile7(path, "utf8").then((raw) => sanitize(JSON.parse(raw))).catch(() => emptyManifest());
2646
3001
  for (const [key2, entry] of Object.entries(mine.e)) {
2647
3002
  const other = onDisk.e[key2];
2648
3003
  if (!other || other.t <= entry.t) onDisk.e[key2] = entry;
@@ -2650,12 +3005,12 @@ async function flushDiscoveryCache() {
2650
3005
  evict(onDisk);
2651
3006
  const payload = JSON.stringify(onDisk);
2652
3007
  if (Buffer.byteLength(payload) > MAX_MANIFEST_BYTES) return;
2653
- const tmp = `${path}.${randomUUID()}.tmp`;
3008
+ const tmp = `${path}.${randomUUID2()}.tmp`;
2654
3009
  try {
2655
- await writeFile2(tmp, payload, { mode: 384 });
2656
- await rename5(tmp, path);
3010
+ await writeFile3(tmp, payload, { mode: 384 });
3011
+ await rename6(tmp, path);
2657
3012
  } finally {
2658
- await unlink4(tmp).catch(() => {
3013
+ await unlink5(tmp).catch(() => {
2659
3014
  });
2660
3015
  }
2661
3016
  loaded = onDisk;
@@ -2771,10 +3126,10 @@ var init_sqlite = __esm({
2771
3126
  import { createHash as createHash2 } from "crypto";
2772
3127
  import { createRequire as createRequire2 } from "module";
2773
3128
  import { chmodSync, existsSync, lstatSync, mkdirSync } from "fs";
2774
- import { join as join9 } from "path";
3129
+ import { join as join10 } from "path";
2775
3130
  function database() {
2776
3131
  const directory = getCacheDir();
2777
- const path = join9(directory, "recorded-costs.sqlite");
3132
+ const path = join10(directory, "recorded-costs.sqlite");
2778
3133
  if (opened?.path === path && existsSync(path)) return opened.db;
2779
3134
  opened?.db.close();
2780
3135
  opened = void 0;
@@ -2867,9 +3222,9 @@ var init_recorded_costs = __esm({
2867
3222
  // src/parsed-file-cache.ts
2868
3223
  import { gzip, gunzip } from "zlib";
2869
3224
  import { promisify } from "util";
2870
- import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
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";
2872
- import { join as join10 } from "path";
3225
+ import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
3226
+ import { mkdir as mkdir9, readFile as readFile8, stat as stat5, writeFile as writeFile4, rename as rename7, unlink as unlink6, readdir as readdir3 } from "fs/promises";
3227
+ import { join as join11 } from "path";
2873
3228
  function encodeEntry(value) {
2874
3229
  let bytes = 0;
2875
3230
  try {
@@ -2892,7 +3247,7 @@ function fileProgress(path, dir, cachePath, before) {
2892
3247
  const size = (await stat5(cachePath)).size;
2893
3248
  if (size > MAX_ENTRY) return;
2894
3249
  chargeRead(size);
2895
- const saved = JSON.parse((await decompress(await readFile7(cachePath), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
3250
+ const saved = JSON.parse((await decompress(await readFile8(cachePath), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
2896
3251
  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
3252
  startingOffset = saved.offset;
2898
3253
  return { offset: saved.offset, state: saved.state };
@@ -2917,12 +3272,12 @@ async function fingerprint(path) {
2917
3272
  }
2918
3273
  }
2919
3274
  async function cachedFileParse(path, kind, parse, warnings, range) {
2920
- const dir = join10(getCacheDir(), "parsed-files");
3275
+ const dir = join11(getCacheDir(), "parsed-files");
2921
3276
  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`);
3277
+ const cachePath = join11(dir, `v${VERSION2}-${key2}.json.gz`);
3278
+ const progressPath = join11(dir, `v${VERSION2}-progress-${key2}.json.gz`);
2924
3279
  const legacyKey = createHash3("sha256").update(`3:${kind}:${path}:${getPricingFingerprint()}`).digest("hex");
2925
- const legacyPath = join10(dir, `v3-${legacyKey}.json.gz`);
3280
+ const legacyPath = join11(dir, `v3-${legacyKey}.json.gz`);
2926
3281
  const fingerprintPath = kind === "opencode" ? path.slice(0, path.lastIndexOf(":")) : path;
2927
3282
  const before = await fingerprint(fingerprintPath);
2928
3283
  const rangeKind = `usage-range:3:${kind}`;
@@ -2950,7 +3305,7 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2950
3305
  const s = await stat5(candidate);
2951
3306
  if (s.size <= MAX_ENTRY) {
2952
3307
  chargeRead(s.size);
2953
- const saved = JSON.parse((await decompress(await readFile7(candidate), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
3308
+ const saved = JSON.parse((await decompress(await readFile8(candidate), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
2954
3309
  if ((saved.v === VERSION2 || saved.v === 3) && saved.fingerprint === before && Array.isArray(saved.data)) {
2955
3310
  if (Array.isArray(saved.warnings)) warnings?.push(...saved.warnings.filter((w) => typeof w === "string"));
2956
3311
  await rememberRange(saved.data);
@@ -2984,20 +3339,20 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2984
3339
  return data;
2985
3340
  }
2986
3341
  async function storeParsedEntry(dir, cachePath, payload, obsolete) {
2987
- await mkdir8(dir, { recursive: true });
3342
+ await mkdir9(dir, { recursive: true });
2988
3343
  const encoded = await compress(Buffer.from(payload));
2989
- const release = await acquireFileLock(join10(dir, "writes.lock"));
3344
+ const release = await acquireFileLock(join11(dir, "writes.lock"));
2990
3345
  try {
2991
- const quotaPath = join10(dir, "quota");
2992
- const quota = await readFile7(quotaPath, "utf8").catch(() => "");
3346
+ const quotaPath = join11(dir, "quota");
3347
+ const quota = await readFile8(quotaPath, "utf8").catch(() => "");
2993
3348
  let total = /^\d+$/.test(quota) ? Number(quota) : NaN;
2994
3349
  const writeQuota = async (bytes) => {
2995
- const tmp = `${quotaPath}.${randomUUID2()}.tmp`;
3350
+ const tmp = `${quotaPath}.${randomUUID3()}.tmp`;
2996
3351
  try {
2997
- await writeFile3(tmp, String(bytes), { mode: 384 });
2998
- await rename6(tmp, quotaPath);
3352
+ await writeFile4(tmp, String(bytes), { mode: 384 });
3353
+ await rename7(tmp, quotaPath);
2999
3354
  } finally {
3000
- await unlink5(tmp).catch(() => {
3355
+ await unlink6(tmp).catch(() => {
3001
3356
  });
3002
3357
  }
3003
3358
  };
@@ -3007,23 +3362,23 @@ async function storeParsedEntry(dir, cachePath, payload, obsolete) {
3007
3362
  for (const name of files) {
3008
3363
  if (!name.endsWith(".json") && !name.endsWith(".json.gz") && !name.endsWith(".tmp")) continue;
3009
3364
  if (!name.startsWith(`v${VERSION2}-`) && !name.startsWith("v3-")) {
3010
- await unlink5(join10(dir, name)).catch(() => {
3365
+ await unlink6(join11(dir, name)).catch(() => {
3011
3366
  });
3012
3367
  continue;
3013
3368
  }
3014
- const p = join10(dir, name), s = await stat5(p).catch(() => null);
3369
+ const p = join11(dir, name), s = await stat5(p).catch(() => null);
3015
3370
  if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
3016
3371
  }
3017
3372
  total = rows2.reduce((n, r) => n + r.size, 0);
3018
3373
  for (const row of rows2.sort((a, b) => a.mtime - b.mtime)) {
3019
3374
  if (total <= MAX_DISK) break;
3020
- await unlink5(row.path).catch(() => {
3375
+ await unlink6(row.path).catch(() => {
3021
3376
  });
3022
3377
  total -= row.size;
3023
3378
  }
3024
3379
  await writeQuota(total);
3025
3380
  }
3026
- if (!(await readFile7(cachePath).catch(() => Buffer.alloc(0))).equals(encoded)) {
3381
+ if (!(await readFile8(cachePath).catch(() => Buffer.alloc(0))).equals(encoded)) {
3027
3382
  const oldSize = (await stat5(cachePath).catch(() => null))?.size ?? 0;
3028
3383
  let nextSize = total + encoded.length;
3029
3384
  if (!Number.isFinite(nextSize)) return;
@@ -3031,36 +3386,36 @@ async function storeParsedEntry(dir, cachePath, payload, obsolete) {
3031
3386
  const rows2 = [];
3032
3387
  for (const name of await readdir3(dir)) {
3033
3388
  if (!name.endsWith(".json.gz")) continue;
3034
- const p = join10(dir, name);
3389
+ const p = join11(dir, name);
3035
3390
  if (p === cachePath) continue;
3036
3391
  const s = await stat5(p).catch(() => null);
3037
3392
  if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
3038
3393
  }
3039
3394
  for (const row of rows2.sort((a, b) => a.mtime - b.mtime)) {
3040
3395
  if (nextSize <= MAX_DISK * 0.75) break;
3041
- await unlink5(row.path).catch(() => {
3396
+ await unlink6(row.path).catch(() => {
3042
3397
  });
3043
3398
  nextSize -= row.size;
3044
3399
  }
3045
3400
  if (nextSize > MAX_DISK) return;
3046
3401
  }
3047
- const tmp = `${cachePath}.${randomUUID2()}.tmp`;
3402
+ const tmp = `${cachePath}.${randomUUID3()}.tmp`;
3048
3403
  try {
3049
3404
  await writeQuota(nextSize);
3050
- await writeFile3(tmp, encoded, { mode: 384 });
3051
- await rename6(tmp, cachePath);
3405
+ await writeFile4(tmp, encoded, { mode: 384 });
3406
+ await rename7(tmp, cachePath);
3052
3407
  mutationCount++;
3053
3408
  if (oldSize) await writeQuota(nextSize - oldSize);
3054
3409
  total = nextSize - oldSize;
3055
3410
  } finally {
3056
- await unlink5(tmp).catch(() => {
3411
+ await unlink6(tmp).catch(() => {
3057
3412
  });
3058
3413
  }
3059
3414
  }
3060
3415
  if (obsolete) {
3061
3416
  const size = (await stat5(obsolete).catch(() => null))?.size ?? 0;
3062
3417
  if (size) {
3063
- await unlink5(obsolete);
3418
+ await unlink6(obsolete);
3064
3419
  await writeQuota(Math.max(0, total - size));
3065
3420
  }
3066
3421
  }
@@ -3263,15 +3618,15 @@ var init_fs_utils = __esm({
3263
3618
 
3264
3619
  // src/providers/claude.ts
3265
3620
  import { readdir as readdir4, stat as stat6 } from "fs/promises";
3266
- import { basename as basename2, join as join11, resolve as resolve2 } from "path";
3621
+ import { basename as basename2, join as join12, resolve as resolve2 } from "path";
3267
3622
  import { homedir as homedir4 } from "os";
3268
3623
  function getClaudeDir() {
3269
- return process.env["CLAUDE_CONFIG_DIR"] || join11(homedir4(), ".claude");
3624
+ return process.env["CLAUDE_CONFIG_DIR"] || join12(homedir4(), ".claude");
3270
3625
  }
3271
3626
  function getDesktopSessionsDir() {
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");
3627
+ if (process.platform === "darwin") return join12(homedir4(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
3628
+ if (process.platform === "win32") return join12(homedir4(), "AppData", "Roaming", "Claude", "local-agent-mode-sessions");
3629
+ return join12(homedir4(), ".config", "Claude", "local-agent-mode-sessions");
3275
3630
  }
3276
3631
  async function findDesktopProjectDirs(base) {
3277
3632
  const results = [];
@@ -3280,13 +3635,13 @@ async function findDesktopProjectDirs(base) {
3280
3635
  const entries = await readdir4(dir).catch(() => []);
3281
3636
  for (const entry of entries) {
3282
3637
  if (entry === "node_modules" || entry === ".git") continue;
3283
- const full = join11(dir, entry);
3638
+ const full = join12(dir, entry);
3284
3639
  const s = await stat6(full).catch(() => null);
3285
3640
  if (!s?.isDirectory()) continue;
3286
3641
  if (entry === "projects") {
3287
3642
  const projectDirs = await readdir4(full).catch(() => []);
3288
3643
  for (const pd of projectDirs) {
3289
- const pdFull = join11(full, pd);
3644
+ const pdFull = join12(full, pd);
3290
3645
  const pdStat = await stat6(pdFull).catch(() => null);
3291
3646
  if (pdStat?.isDirectory()) results.push(pdFull);
3292
3647
  }
@@ -3340,11 +3695,11 @@ var init_claude = __esm({
3340
3695
  const config = await readConfig();
3341
3696
  const roots = /* @__PURE__ */ new Set([getClaudeDir(), ...config.claudeConfigDirs ?? []]);
3342
3697
  for (const root of roots) {
3343
- const projectsDir = join11(root.startsWith("~/") ? join11(homedir4(), root.slice(2)) : resolve2(root), "projects");
3698
+ const projectsDir = join12(root.startsWith("~/") ? join12(homedir4(), root.slice(2)) : resolve2(root), "projects");
3344
3699
  try {
3345
3700
  const entries = await readdir4(projectsDir);
3346
3701
  for (const dirName of entries) {
3347
- const dirPath = join11(projectsDir, dirName);
3702
+ const dirPath = join12(projectsDir, dirName);
3348
3703
  const dirStat = await stat6(dirPath).catch(() => null);
3349
3704
  if (dirStat?.isDirectory()) {
3350
3705
  sources.push({ path: dirPath, project: dirName, provider: "claude" });
@@ -3371,10 +3726,10 @@ var init_claude = __esm({
3371
3726
 
3372
3727
  // src/providers/codex.ts
3373
3728
  import { readdir as readdir5, stat as stat7 } from "fs/promises";
3374
- import { basename as basename3, join as join12 } from "path";
3729
+ import { basename as basename3, join as join13 } from "path";
3375
3730
  import { homedir as homedir5 } from "os";
3376
3731
  function getCodexDir(override) {
3377
- return override ?? process.env["CODEX_HOME"] ?? join12(homedir5(), ".codex");
3732
+ return override ?? process.env["CODEX_HOME"] ?? join13(homedir5(), ".codex");
3378
3733
  }
3379
3734
  function sanitizeProject(cwd) {
3380
3735
  return cwd.replace(/^\//, "").replace(/\//g, "-");
@@ -3395,12 +3750,12 @@ async function readCodexHeader(filePath) {
3395
3750
  return { project: sanitizeProject(entry.payload?.cwd ?? "unknown") };
3396
3751
  }
3397
3752
  async function discoverSessionsInDir(codexDir) {
3398
- const sessionsDir = join12(codexDir, "sessions");
3753
+ const sessionsDir = join13(codexDir, "sessions");
3399
3754
  const sources = [];
3400
- const archivedDir = join12(codexDir, "archived_sessions");
3755
+ const archivedDir = join13(codexDir, "archived_sessions");
3401
3756
  for (const file of await readdir5(archivedDir).catch(() => [])) {
3402
3757
  if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
3403
- const filePath = join12(archivedDir, file);
3758
+ const filePath = join13(archivedDir, file);
3404
3759
  const s = await stat7(filePath).catch(() => null);
3405
3760
  if (!s?.isFile()) continue;
3406
3761
  const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
@@ -3414,19 +3769,19 @@ async function discoverSessionsInDir(codexDir) {
3414
3769
  }
3415
3770
  for (const year of years) {
3416
3771
  if (!/^\d{4}$/.test(year)) continue;
3417
- const yearDir = join12(sessionsDir, year);
3772
+ const yearDir = join13(sessionsDir, year);
3418
3773
  const months = await readdir5(yearDir).catch(() => []);
3419
3774
  for (const month of months) {
3420
3775
  if (!/^\d{2}$/.test(month)) continue;
3421
- const monthDir = join12(yearDir, month);
3776
+ const monthDir = join13(yearDir, month);
3422
3777
  const days = await readdir5(monthDir).catch(() => []);
3423
3778
  for (const day of days) {
3424
3779
  if (!/^\d{2}$/.test(day)) continue;
3425
- const dayDir = join12(monthDir, day);
3780
+ const dayDir = join13(monthDir, day);
3426
3781
  const files = await readdir5(dayDir).catch(() => []);
3427
3782
  for (const file of files) {
3428
3783
  if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
3429
- const filePath = join12(dayDir, file);
3784
+ const filePath = join13(dayDir, file);
3430
3785
  const s = await stat7(filePath).catch(() => null);
3431
3786
  if (!s?.isFile()) continue;
3432
3787
  const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
@@ -3645,10 +4000,10 @@ var init_codex = __esm({
3645
4000
 
3646
4001
  // src/providers/copilot.ts
3647
4002
  import { readdir as readdir6, stat as stat8 } from "fs/promises";
3648
- import { basename as basename4, dirname as dirname3, join as join13 } from "path";
4003
+ import { basename as basename4, dirname as dirname4, join as join14 } from "path";
3649
4004
  import { homedir as homedir6 } from "os";
3650
4005
  function getCopilotSessionStateDir(override) {
3651
- return override ?? join13(homedir6(), ".copilot", "session-state");
4006
+ return override ?? join14(homedir6(), ".copilot", "session-state");
3652
4007
  }
3653
4008
  function parseCwd(yaml) {
3654
4009
  const match = yaml.match(/^cwd:\s*(.+)$/m);
@@ -3659,7 +4014,7 @@ function parseCwd(yaml) {
3659
4014
  function createParser2(source, seenKeys) {
3660
4015
  return {
3661
4016
  async *parse() {
3662
- const sessionId = basename4(dirname3(source.path));
4017
+ const sessionId = basename4(dirname4(source.path));
3663
4018
  let currentModel = "";
3664
4019
  let pendingUserMessage = "";
3665
4020
  let lineCount = 0;
@@ -3723,11 +4078,11 @@ async function discoverSessionsInDir2(sessionStateDir) {
3723
4078
  return sources;
3724
4079
  }
3725
4080
  for (const sessionId of sessionDirs) {
3726
- const eventsPath = join13(sessionStateDir, sessionId, "events.jsonl");
4081
+ const eventsPath = join14(sessionStateDir, sessionId, "events.jsonl");
3727
4082
  const s = await stat8(eventsPath).catch(() => null);
3728
4083
  if (!s?.isFile()) continue;
3729
4084
  let project = sessionId;
3730
- const yaml = await readSessionFile(join13(sessionStateDir, sessionId, "workspace.yaml"));
4085
+ const yaml = await readSessionFile(join14(sessionStateDir, sessionId, "workspace.yaml"));
3731
4086
  if (yaml !== null) {
3732
4087
  const cwd = parseCwd(yaml);
3733
4088
  if (cwd) project = basename4(cwd);
@@ -3841,13 +4196,13 @@ var init_bash_utils = __esm({
3841
4196
 
3842
4197
  // src/providers/pi.ts
3843
4198
  import { readdir as readdir7, stat as stat9 } from "fs/promises";
3844
- import { basename as basename6, join as join14 } from "path";
4199
+ import { basename as basename6, join as join15 } from "path";
3845
4200
  import { homedir as homedir7 } from "os";
3846
4201
  function getPiSessionsDir(override) {
3847
- return override ?? join14(homedir7(), ".pi", "agent", "sessions");
4202
+ return override ?? join15(homedir7(), ".pi", "agent", "sessions");
3848
4203
  }
3849
4204
  function getOmpSessionsDir(override) {
3850
- return override ?? join14(homedir7(), ".omp", "agent", "sessions");
4205
+ return override ?? join15(homedir7(), ".omp", "agent", "sessions");
3851
4206
  }
3852
4207
  async function readFirstEntry(filePath) {
3853
4208
  const line = await readSessionFirstLine(filePath);
@@ -3867,7 +4222,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3867
4222
  return sources;
3868
4223
  }
3869
4224
  for (const dirName of projectDirs) {
3870
- const dirPath = join14(sessionsDir, dirName);
4225
+ const dirPath = join15(sessionsDir, dirName);
3871
4226
  const dirStat = await stat9(dirPath).catch(() => null);
3872
4227
  if (!dirStat?.isDirectory()) continue;
3873
4228
  let files;
@@ -3878,7 +4233,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3878
4233
  }
3879
4234
  for (const file of files) {
3880
4235
  if (!file.endsWith(".jsonl")) continue;
3881
- const filePath = join14(dirPath, file);
4236
+ const filePath = join15(dirPath, file);
3882
4237
  const fileStat = await stat9(filePath).catch(() => null);
3883
4238
  if (!fileStat?.isFile()) continue;
3884
4239
  const first = await readFirstEntry(filePath);
@@ -4036,11 +4391,11 @@ var init_pi = __esm({
4036
4391
 
4037
4392
  // src/providers/kimi.ts
4038
4393
  import { createHash as createHash4 } from "crypto";
4039
- import { readdir as readdir8, readFile as readFile8, stat as stat10 } from "fs/promises";
4040
- import { basename as basename7, dirname as dirname4, join as join15 } from "path";
4394
+ import { readdir as readdir8, readFile as readFile9, stat as stat10 } from "fs/promises";
4395
+ import { basename as basename7, dirname as dirname5, join as join16 } from "path";
4041
4396
  import { homedir as homedir8 } from "os";
4042
4397
  function createKimiProvider(home) {
4043
- const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join15(homedir8(), ".kimi-code");
4398
+ const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join16(homedir8(), ".kimi-code");
4044
4399
  return {
4045
4400
  name: "kimi",
4046
4401
  displayName: "Kimi",
@@ -4048,12 +4403,12 @@ function createKimiProvider(home) {
4048
4403
  toolDisplayName: (tool) => tool,
4049
4404
  async discoverSessions() {
4050
4405
  const projects = /* @__PURE__ */ new Map();
4051
- const catalog = join15(root(), "workspaces.json");
4406
+ const catalog = join16(root(), "workspaces.json");
4052
4407
  try {
4053
4408
  const size = (await stat10(catalog)).size;
4054
4409
  if (size <= 2 * 1024 * 1024) {
4055
4410
  chargeRead(size);
4056
- const data = JSON.parse(await readFile8(catalog, "utf8"));
4411
+ const data = JSON.parse(await readFile9(catalog, "utf8"));
4057
4412
  for (const [id, entry] of Object.entries(data.workspaces ?? {})) {
4058
4413
  if (entry && typeof entry === "object" && "root" in entry && typeof entry.root === "string") {
4059
4414
  projects.set(id, basename7(entry.root));
@@ -4070,16 +4425,16 @@ function createKimiProvider(home) {
4070
4425
  const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
4071
4426
  for (const entry of entries) {
4072
4427
  if (entry.name.startsWith(".")) continue;
4073
- const path = join15(dir, entry.name);
4428
+ const path = join16(dir, entry.name);
4074
4429
  if (entry.isDirectory()) await walk(path, project, depth + 1);
4075
4430
  else if (entry.isFile() && entry.name === "wire.jsonl") sources.push({ path, project, provider: "kimi" });
4076
4431
  }
4077
4432
  }
4078
- const sessions = join15(root(), "sessions");
4433
+ const sessions = join16(root(), "sessions");
4079
4434
  for (const entry of await readdir8(sessions, { withFileTypes: true }).catch(() => [])) {
4080
4435
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
4081
4436
  const project = projects.get(entry.name) ?? entry.name.replace(/^wd_/, "").replace(/_[a-f\d]{12}$/, "");
4082
- await walk(join15(sessions, entry.name), project, 0);
4437
+ await walk(join16(sessions, entry.name), project, 0);
4083
4438
  }
4084
4439
  return sources.sort((a, b) => a.path.localeCompare(b.path));
4085
4440
  },
@@ -4088,8 +4443,8 @@ function createKimiProvider(home) {
4088
4443
  return {
4089
4444
  warnings,
4090
4445
  async *parse() {
4091
- const sessionDir = source.path.split("/").reverse().find((part) => part.startsWith("session_")) ?? basename7(dirname4(source.path));
4092
- const agent = basename7(dirname4(source.path));
4446
+ const sessionDir = source.path.split("/").reverse().find((part) => part.startsWith("session_")) ?? basename7(dirname5(source.path));
4447
+ const agent = basename7(dirname5(source.path));
4093
4448
  for await (const line of readSessionLines(source.path)) {
4094
4449
  if (!/"type"\s*:\s*"usage\.record"/.test(line.slice(0, 256))) continue;
4095
4450
  let data;
@@ -4166,12 +4521,12 @@ var init_kimi = __esm({
4166
4521
  });
4167
4522
 
4168
4523
  // src/cursor-cache.ts
4169
- import { readFile as readFile9, mkdir as mkdir9, stat as stat11, rename as rename7 } from "fs/promises";
4524
+ import { readFile as readFile10, mkdir as mkdir10, stat as stat11, rename as rename8 } from "fs/promises";
4170
4525
  import { open as open6 } from "fs/promises";
4171
- import { join as join16 } from "path";
4526
+ import { join as join17 } from "path";
4172
4527
  import { randomBytes as randomBytes4 } from "crypto";
4173
4528
  function getCachePath2() {
4174
- return join16(getCacheDir(), CACHE_FILE);
4529
+ return join17(getCacheDir(), CACHE_FILE);
4175
4530
  }
4176
4531
  async function getDbFingerprint(dbPath) {
4177
4532
  try {
@@ -4189,7 +4544,7 @@ async function readCachedResults(dbPath) {
4189
4544
  const size = (await stat11(getCachePath2())).size;
4190
4545
  if (size > 16 * 1024 * 1024) return null;
4191
4546
  chargeRead(size);
4192
- const raw = await readFile9(getCachePath2(), "utf-8");
4547
+ const raw = await readFile10(getCachePath2(), "utf-8");
4193
4548
  const cache = JSON.parse(raw);
4194
4549
  if (cache.fingerprint === fp) {
4195
4550
  return cache.calls;
@@ -4205,7 +4560,7 @@ async function writeCachedResults(dbPath, calls, expected) {
4205
4560
  const fp = await getDbFingerprint(dbPath);
4206
4561
  if (!fp || expected !== void 0 && expected !== fp) return;
4207
4562
  const dir = getCacheDir();
4208
- await mkdir9(dir, { recursive: true });
4563
+ await mkdir10(dir, { recursive: true });
4209
4564
  const cache = {
4210
4565
  fingerprint: fp,
4211
4566
  calls
@@ -4221,7 +4576,7 @@ async function writeCachedResults(dbPath, calls, expected) {
4221
4576
  } finally {
4222
4577
  await handle.close();
4223
4578
  }
4224
- await rename7(tmpPath, finalPath);
4579
+ await rename8(tmpPath, finalPath);
4225
4580
  } catch {
4226
4581
  }
4227
4582
  }
@@ -4243,16 +4598,16 @@ __export(cursor_exports, {
4243
4598
  cursor: () => cursor
4244
4599
  });
4245
4600
  import { existsSync as existsSync2 } from "fs";
4246
- import { join as join17 } from "path";
4601
+ import { join as join18 } from "path";
4247
4602
  import { homedir as homedir9 } from "os";
4248
4603
  function getCursorDbPath() {
4249
4604
  if (process.platform === "darwin") {
4250
- return join17(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
4605
+ return join18(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
4251
4606
  }
4252
4607
  if (process.platform === "win32") {
4253
- return join17(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
4608
+ return join18(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
4254
4609
  }
4255
- return join17(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
4610
+ return join18(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
4256
4611
  }
4257
4612
  function extractLanguages(codeBlocksJson) {
4258
4613
  if (!codeBlocksJson) return [];
@@ -4495,19 +4850,19 @@ __export(opencode_exports, {
4495
4850
  opencode: () => opencode
4496
4851
  });
4497
4852
  import { readdir as readdir9 } from "fs/promises";
4498
- import { join as join18 } from "path";
4853
+ import { join as join19 } from "path";
4499
4854
  import { homedir as homedir10 } from "os";
4500
4855
  function sanitize2(dir) {
4501
4856
  return dir.replace(/^\//, "").replace(/\//g, "-");
4502
4857
  }
4503
4858
  function getDataDir(dataDir) {
4504
- const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join18(homedir10(), ".local", "share");
4505
- return join18(base, "opencode");
4859
+ const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join19(homedir10(), ".local", "share");
4860
+ return join19(base, "opencode");
4506
4861
  }
4507
4862
  async function findDbFiles(dir) {
4508
4863
  try {
4509
4864
  const entries = await readdir9(dir);
4510
- return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join18(dir, f));
4865
+ return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join19(dir, f));
4511
4866
  } catch (error) {
4512
4867
  if (error instanceof ResourceBudgetError) throw error;
4513
4868
  return [];
@@ -4739,17 +5094,17 @@ __export(cursor_agent_exports, {
4739
5094
  import { createHash as createHash5 } from "crypto";
4740
5095
  import { existsSync as existsSync3 } from "fs";
4741
5096
  import { readdir as readdir10, stat as stat12 } from "fs/promises";
4742
- import { join as join19, basename as basename8 } from "path";
5097
+ import { join as join20, basename as basename8 } from "path";
4743
5098
  import { homedir as homedir11 } from "os";
4744
5099
  function getCursorAgentBaseDir(baseDirOverride) {
4745
5100
  if (baseDirOverride) return baseDirOverride;
4746
- return join19(homedir11(), ".cursor");
5101
+ return join20(homedir11(), ".cursor");
4747
5102
  }
4748
5103
  function getProjectsDir(baseDir) {
4749
- return join19(baseDir, "projects");
5104
+ return join20(baseDir, "projects");
4750
5105
  }
4751
5106
  function getAttributionDbPath(baseDir) {
4752
- return join19(baseDir, "ai-tracking", "ai-code-tracking.db");
5107
+ return join20(baseDir, "ai-tracking", "ai-code-tracking.db");
4753
5108
  }
4754
5109
  function estimateTokens(charCount) {
4755
5110
  if (charCount <= 0) return 0;
@@ -5008,13 +5363,13 @@ function createCursorAgentProvider(baseDirOverride) {
5008
5363
  for (const entry of projectEntries) {
5009
5364
  if (!entry.isDirectory()) continue;
5010
5365
  const projectId = prettifyProjectId(entry.name);
5011
- const transcriptDir = join19(projectsDir, entry.name, "agent-transcripts");
5366
+ const transcriptDir = join20(projectsDir, entry.name, "agent-transcripts");
5012
5367
  if (!existsSync3(transcriptDir)) continue;
5013
5368
  const transcriptEntries = await readdir10(transcriptDir, { withFileTypes: true });
5014
5369
  for (const transcript of transcriptEntries) {
5015
5370
  if (!transcript.isFile()) continue;
5016
5371
  if (!transcript.name.endsWith(".txt")) continue;
5017
- const transcriptPath = join19(transcriptDir, transcript.name);
5372
+ const transcriptPath = join20(transcriptDir, transcript.name);
5018
5373
  sources.push({
5019
5374
  path: transcriptPath,
5020
5375
  project: projectId,
@@ -5329,19 +5684,19 @@ var init_classifier = __esm({
5329
5684
  // src/session-index.ts
5330
5685
  import { stat as stat13 } from "fs/promises";
5331
5686
  import { existsSync as existsSync4, unlinkSync } from "fs";
5332
- import { readFile as readFile11, mkdir as mkdir10, rename as rename8, unlink as unlink6, open as open7 } from "fs/promises";
5333
- import { join as join20 } from "path";
5687
+ import { readFile as readFile12, mkdir as mkdir11, rename as rename9, unlink as unlink7, open as open7 } from "fs/promises";
5688
+ import { join as join21 } from "path";
5334
5689
  import { randomBytes as randomBytes5 } from "crypto";
5335
5690
  function emptyIndex() {
5336
5691
  return { v: SESSION_INDEX_VERSION, e: {} };
5337
5692
  }
5338
5693
  function indexPath() {
5339
- return join20(getCacheDir(), INDEX_FILENAME);
5694
+ return join21(getCacheDir(), INDEX_FILENAME);
5340
5695
  }
5341
5696
  async function loadSessionIndex() {
5342
5697
  if (currentIndex) return currentIndex;
5343
5698
  try {
5344
- const raw = await readFile11(indexPath(), "utf-8");
5699
+ const raw = await readFile12(indexPath(), "utf-8");
5345
5700
  const parsed = JSON.parse(raw);
5346
5701
  if (parsed.v !== SESSION_INDEX_VERSION) {
5347
5702
  currentIndex = emptyIndex();
@@ -5356,7 +5711,7 @@ async function loadSessionIndex() {
5356
5711
  }
5357
5712
  async function saveSessionIndex(index) {
5358
5713
  const dir = getCacheDir();
5359
- if (!existsSync4(dir)) await mkdir10(dir, { recursive: true });
5714
+ if (!existsSync4(dir)) await mkdir11(dir, { recursive: true });
5360
5715
  const finalPath = indexPath();
5361
5716
  const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
5362
5717
  const payload = JSON.stringify(index);
@@ -5368,10 +5723,10 @@ async function saveSessionIndex(index) {
5368
5723
  await handle.close();
5369
5724
  }
5370
5725
  try {
5371
- await rename8(tempPath, finalPath);
5726
+ await rename9(tempPath, finalPath);
5372
5727
  } catch (err) {
5373
5728
  try {
5374
- await unlink6(tempPath);
5729
+ await unlink7(tempPath);
5375
5730
  } catch {
5376
5731
  }
5377
5732
  throw err;
@@ -5438,7 +5793,7 @@ __export(parser_exports, {
5438
5793
  });
5439
5794
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
5440
5795
  import { readdir as readdir11, stat as stat14 } from "fs/promises";
5441
- import { basename as basename9, join as join21 } from "path";
5796
+ import { basename as basename9, join as join22 } from "path";
5442
5797
  function getParseWarnings() {
5443
5798
  return _parseWarnings;
5444
5799
  }
@@ -5752,13 +6107,13 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
5752
6107
  }
5753
6108
  async function collectJsonlFiles2(dirPath) {
5754
6109
  const files = await readdir11(dirPath).catch(() => []);
5755
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join21(dirPath, f));
6110
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join22(dirPath, f));
5756
6111
  for (const entry of files) {
5757
6112
  if (entry.endsWith(".jsonl")) continue;
5758
- const subagentsPath = join21(dirPath, entry, "subagents");
6113
+ const subagentsPath = join22(dirPath, entry, "subagents");
5759
6114
  const subFiles = await readdir11(subagentsPath).catch(() => []);
5760
6115
  for (const sf of subFiles) {
5761
- if (sf.endsWith(".jsonl")) jsonlFiles.push(join21(subagentsPath, sf));
6116
+ if (sf.endsWith(".jsonl")) jsonlFiles.push(join22(subagentsPath, sf));
5762
6117
  }
5763
6118
  }
5764
6119
  return jsonlFiles;
@@ -6120,21 +6475,199 @@ var init_parser = __esm({
6120
6475
  }
6121
6476
  });
6122
6477
 
6478
+ // src/telemetry.ts
6479
+ var import_privacy = __toESM(require_privacy(), 1);
6480
+ init_config();
6481
+ init_file_lock();
6482
+ import { randomUUID } from "crypto";
6483
+ import { mkdir as mkdir3, readFile as readFile3, rename as rename2, unlink as unlink2, writeFile, chmod } from "fs/promises";
6484
+ import { dirname as dirname2, join as join2 } from "path";
6485
+ var ALIAS = "public-watcher-cli-v1";
6486
+ var BASIC_NOTICE = "Watcher shares basic app activity (daily activity, foreground time, feature outcomes, app version and platform) with Odd\u2019s own servers. A random installation ID measures returning installations. Your content stays private. Turn off anytime: exe-watcher telemetry --usage off. Details: https://github.com/AskExe/exe-watcher#optional-analytics-and-error-reporting";
6487
+ var FEATURES = /* @__PURE__ */ new Set(["status", "report", "optimize", "compare", "export", "menubar", "plan", "currency", "agent-status"]);
6488
+ function resolveTelemetry(config = {}, env = process.env) {
6489
+ if (!config || typeof config !== "object" || Array.isArray(config)) return { usageEnabled: false, diagnosticsEnabled: false };
6490
+ const disabled = config.enabled === false || env["EXE_WATCHER_TELEMETRY"] === "0" || env["DO_NOT_TRACK"] === "1" || Boolean(env["EXE_WATCHER_PARENT_PID"]);
6491
+ return { usageEnabled: !disabled && (config.usageEnabled === void 0 || config.usageEnabled === true), diagnosticsEnabled: !disabled && config.diagnosticsEnabled === true };
6492
+ }
6493
+ function safeError(error, version2 = "0.0.0") {
6494
+ return (0, import_privacy.sanitizeDiagnostic)({ event: "error", category: "unhandled_exception", error_class: error instanceof Error ? error.name : "Error", version: version2, frames: [] }, []);
6495
+ }
6496
+ var silent = () => ({ async capture(_event, _command, _success = true) {
6497
+ }, async error(_error) {
6498
+ }, async shutdown() {
6499
+ }, abort() {
6500
+ } });
6501
+ async function createTelemetry(config, version2, foreground = Boolean(process.stdin.isTTY)) {
6502
+ if (process.env["EXE_WATCHER_PARENT_PID"]) return silent();
6503
+ const initial = resolveTelemetry(config);
6504
+ if (initial.usageEnabled && config?.usageEnabled === void 0 && config?.basicNoticeVersion !== 1 && config?.diagnosticsEnabled !== true) return silent();
6505
+ const root = dirname2(getConfigFilePath()), directory = join2(root, "telemetry-cli"), file = join2(directory, "state.json");
6506
+ if (!initial.usageEnabled && !initial.diagnosticsEnabled) {
6507
+ try {
6508
+ await readFile3(file);
6509
+ } catch {
6510
+ return silent();
6511
+ }
6512
+ }
6513
+ let release;
6514
+ try {
6515
+ await mkdir3(directory, { recursive: true, mode: 448 });
6516
+ await chmod(directory, 448);
6517
+ release = await acquireFileLock(join2(directory, "owner.lock"), 0);
6518
+ let firstId = false;
6519
+ let installation;
6520
+ if (initial.usageEnabled) {
6521
+ const path = join2(root, "telemetry-id");
6522
+ try {
6523
+ await writeFile(path, randomUUID(), { flag: "wx", mode: 384 });
6524
+ } catch (e) {
6525
+ if (e.code !== "EEXIST") throw e;
6526
+ }
6527
+ const candidate = (await readFile3(path, "utf8")).trim();
6528
+ if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(candidate)) installation = candidate;
6529
+ } else if (config?.usageEnabled !== true) await unlink2(join2(root, "telemetry-id")).catch(() => {
6530
+ });
6531
+ const client = await import_privacy.PrivacyClient.create({
6532
+ app: "watcher",
6533
+ usageAlias: ALIAS,
6534
+ diagnosticsAlias: ALIAS,
6535
+ appVersion: /^\d{1,4}\.\d{1,4}\.\d{1,4}$/.test(version2) ? version2 : "0.0.0",
6536
+ platform: process.platform === "darwin" ? "macos" : process.platform === "win32" ? "windows" : process.platform === "linux" ? "linux" : void 0,
6537
+ features: [...FEATURES].map((name) => `cli_${name.replaceAll("-", "_")}`),
6538
+ frames: [],
6539
+ randomUUID: () => {
6540
+ if (firstId) {
6541
+ firstId = false;
6542
+ return installation ?? randomUUID();
6543
+ }
6544
+ ;
6545
+ return randomUUID();
6546
+ },
6547
+ storage: {
6548
+ async getItem() {
6549
+ try {
6550
+ const data = await readFile3(file, "utf8");
6551
+ return data.length <= 524288 ? data : null;
6552
+ } catch {
6553
+ return null;
6554
+ }
6555
+ },
6556
+ async setItem(_key, value) {
6557
+ const temp = `${file}.${randomUUID()}.tmp`;
6558
+ try {
6559
+ await writeFile(temp, value, { mode: 384, flag: "wx" });
6560
+ await rename2(temp, file);
6561
+ } finally {
6562
+ await unlink2(temp).catch(() => {
6563
+ });
6564
+ }
6565
+ }
6566
+ },
6567
+ send: (url, options) => fetch(url, { ...options, signal: AbortSignal.any([options.signal, AbortSignal.timeout(1500)]) })
6568
+ });
6569
+ const epochs = { usage: config?.usageEpoch ?? "initial", diagnostics: config?.diagnosticsEpoch ?? "initial" };
6570
+ let prior = {};
6571
+ try {
6572
+ prior = JSON.parse(await readFile3(join2(directory, "consent.json"), "utf8"));
6573
+ } catch {
6574
+ }
6575
+ if (prior.usage !== epochs.usage) await client.setConsent({ usage: false });
6576
+ if (prior.diagnostics !== epochs.diagnostics) await client.setConsent({ diagnostics: false });
6577
+ firstId = initial.usageEnabled && !client.status().usage;
6578
+ await client.setConsent({ usage: initial.usageEnabled, diagnostics: initial.diagnosticsEnabled });
6579
+ firstId = false;
6580
+ const epochTemp = join2(directory, `${randomUUID()}.tmp`);
6581
+ try {
6582
+ await writeFile(epochTemp, JSON.stringify(epochs), { mode: 384, flag: "wx" });
6583
+ await rename2(epochTemp, join2(directory, "consent.json"));
6584
+ } finally {
6585
+ await unlink2(epochTemp).catch(() => {
6586
+ });
6587
+ }
6588
+ const syncConsent = async () => {
6589
+ let current = { usageEnabled: false, diagnosticsEnabled: false };
6590
+ let saved = {};
6591
+ try {
6592
+ saved = JSON.parse(await readFile3(getConfigFilePath(), "utf8")).telemetry ?? {};
6593
+ current = resolveTelemetry(saved);
6594
+ } catch (error) {
6595
+ if (error.code === "ENOENT") current = resolveTelemetry(void 0);
6596
+ }
6597
+ if ((saved.usageEpoch ?? "initial") !== epochs.usage) current.usageEnabled = false;
6598
+ if ((saved.diagnosticsEpoch ?? "initial") !== epochs.diagnostics) current.diagnosticsEnabled = false;
6599
+ const status = client.status();
6600
+ const usage = initial.usageEnabled && current.usageEnabled, diagnostics = initial.diagnosticsEnabled && current.diagnosticsEnabled;
6601
+ if (status.usage !== usage || status.diagnostics !== diagnostics) await client.setConsent({ usage, diagnostics });
6602
+ };
6603
+ let marked = false, stopped = false;
6604
+ return {
6605
+ async capture(event, command, success = true) {
6606
+ try {
6607
+ await syncConsent();
6608
+ if (!foreground || !FEATURES.has(command)) return;
6609
+ const feature = `cli_${command.replaceAll("-", "_")}`;
6610
+ if (event === "command_started") {
6611
+ if (!marked) {
6612
+ await client.track({ event: "active" });
6613
+ marked = true;
6614
+ }
6615
+ await client.track({ event: "feature", feature });
6616
+ } else if (event === "command_finished" && marked) {
6617
+ await client.track({ event: "result", feature, outcome: success ? "success" : "failure" });
6618
+ }
6619
+ } catch {
6620
+ }
6621
+ },
6622
+ async error(error) {
6623
+ try {
6624
+ await syncConsent();
6625
+ await client.report(safeError(error, version2));
6626
+ } catch {
6627
+ }
6628
+ },
6629
+ async shutdown() {
6630
+ if (stopped) return;
6631
+ stopped = true;
6632
+ const revocation = setInterval(() => {
6633
+ void syncConsent().catch(() => {
6634
+ });
6635
+ }, 100);
6636
+ try {
6637
+ await syncConsent();
6638
+ await client.flush();
6639
+ } finally {
6640
+ clearInterval(revocation);
6641
+ client.abort();
6642
+ await release?.();
6643
+ }
6644
+ },
6645
+ abort() {
6646
+ client.abort();
6647
+ }
6648
+ };
6649
+ } catch {
6650
+ await release?.().catch(() => {
6651
+ });
6652
+ return silent();
6653
+ }
6654
+ }
6655
+
6123
6656
  // src/cli.ts
6124
6657
  init_cache_dir();
6125
6658
  init_file_lock();
6126
6659
  init_resource_budget();
6127
6660
  import { existsSync as existsSync8, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
6128
- import { join as join32 } from "path";
6661
+ import { dirname as dirname8, join as join33 } from "path";
6129
6662
  import { homedir as homedir17 } from "os";
6130
6663
  import { Command } from "commander";
6131
6664
 
6132
6665
  // src/menubar-installer.ts
6133
6666
  import { spawn } from "child_process";
6134
6667
  import { createWriteStream } from "fs";
6135
- import { cp, mkdir as mkdir2, mkdtemp, rename, rm, stat as stat2 } from "fs/promises";
6136
- import { homedir as homedir2, platform, tmpdir } from "os";
6137
- import { basename, join as join2 } from "path";
6668
+ import { cp, mkdir as mkdir4, mkdtemp, rename as rename3, rm, stat as stat2 } from "fs/promises";
6669
+ import { homedir as homedir3, platform, tmpdir } from "os";
6670
+ import { basename, join as join4 } from "path";
6138
6671
  import { pipeline } from "stream/promises";
6139
6672
  import { Readable } from "stream";
6140
6673
  var RELEASE_API_LATEST = "https://api.github.com/repos/AskExe/exe-watcher/releases/latest";
@@ -6148,7 +6681,7 @@ var DEFAULT_APP_EXIT_TIMEOUT_MS = 5e3;
6148
6681
  var DEFAULT_APP_LAUNCH_TIMEOUT_MS = 5e3;
6149
6682
  var POLL_INTERVAL_MS = 150;
6150
6683
  function userApplicationsDir() {
6151
- return join2(homedir2(), "Applications");
6684
+ return join4(homedir3(), "Applications");
6152
6685
  }
6153
6686
  async function exists(path) {
6154
6687
  try {
@@ -6294,7 +6827,7 @@ async function signalPids(signal, pids) {
6294
6827
  }
6295
6828
  async function movePath(source, dest) {
6296
6829
  try {
6297
- await rename(source, dest);
6830
+ await rename3(source, dest);
6298
6831
  } catch (err) {
6299
6832
  if (err.code === "EXDEV") {
6300
6833
  await cp(source, dest, { recursive: true });
@@ -6333,7 +6866,7 @@ async function killRunningApp() {
6333
6866
  async function installMenubarApp(options = {}) {
6334
6867
  await ensureSupportedPlatform();
6335
6868
  const appsDir = userApplicationsDir();
6336
- const targetPath = join2(appsDir, APP_BUNDLE_NAME);
6869
+ const targetPath = join4(appsDir, APP_BUNDLE_NAME);
6337
6870
  const alreadyInstalled = await exists(targetPath);
6338
6871
  if (alreadyInstalled && !options.force) {
6339
6872
  if (!await isAppRunning()) {
@@ -6343,21 +6876,21 @@ async function installMenubarApp(options = {}) {
6343
6876
  }
6344
6877
  console.log(options.version ? `Looking up Exe Watcher Menubar release ${options.version}...` : "Looking up the latest Exe Watcher Menubar release...");
6345
6878
  const asset = await fetchLatestReleaseAsset(options.version);
6346
- const stagingDir = await mkdtemp(join2(tmpdir(), "exe-watcher-menubar-"));
6879
+ const stagingDir = await mkdtemp(join4(tmpdir(), "exe-watcher-menubar-"));
6347
6880
  try {
6348
- const archivePath = join2(stagingDir, asset.name);
6881
+ const archivePath = join4(stagingDir, asset.name);
6349
6882
  console.log(`Downloading ${asset.name}...`);
6350
6883
  await downloadToFile(asset.browser_download_url, archivePath);
6351
6884
  console.log("Unpacking...");
6352
6885
  await runCommand("/usr/bin/ditto", ["-x", "-k", archivePath, stagingDir]);
6353
- const unpackedApp = join2(stagingDir, APP_BUNDLE_NAME);
6886
+ const unpackedApp = join4(stagingDir, APP_BUNDLE_NAME);
6354
6887
  if (!await exists(unpackedApp)) {
6355
6888
  throw new Error(`Archive did not contain ${APP_BUNDLE_NAME}.`);
6356
6889
  }
6357
6890
  await runCommand("/usr/bin/xattr", ["-dr", "com.apple.quarantine", unpackedApp]).catch(() => {
6358
6891
  });
6359
- await mkdir2(appsDir, { recursive: true });
6360
- const backupPath = alreadyInstalled ? join2(appsDir, `${basename(APP_BUNDLE_NAME, ".app")}.backup-${Date.now()}.app`) : void 0;
6892
+ await mkdir4(appsDir, { recursive: true });
6893
+ const backupPath = alreadyInstalled ? join4(appsDir, `${basename(APP_BUNDLE_NAME, ".app")}.backup-${Date.now()}.app`) : void 0;
6361
6894
  let targetReplaced = false;
6362
6895
  try {
6363
6896
  if (alreadyInstalled && backupPath) {
@@ -6391,15 +6924,15 @@ async function installMenubarApp(options = {}) {
6391
6924
 
6392
6925
  // src/export.ts
6393
6926
  init_types();
6394
- import { writeFile, mkdir as mkdir5, readdir as readdir2, stat as stat3, rm as rm2 } from "fs/promises";
6395
- import { dirname as dirname2, join as join6, resolve } from "path";
6927
+ import { writeFile as writeFile2, mkdir as mkdir6, readdir as readdir2, stat as stat3, rm as rm2 } from "fs/promises";
6928
+ import { dirname as dirname3, join as join7, resolve } from "path";
6396
6929
 
6397
6930
  // src/currency.ts
6398
6931
  init_cache_dir();
6399
6932
  init_config();
6400
- import { readFile as readFile3, mkdir as mkdir4, rename as rename3, unlink as unlink2 } from "fs/promises";
6933
+ import { readFile as readFile4, mkdir as mkdir5, rename as rename4, unlink as unlink3 } from "fs/promises";
6401
6934
  import { open as open3 } from "fs/promises";
6402
- import { join as join4 } from "path";
6935
+ import { join as join5 } from "path";
6403
6936
  import { randomBytes as randomBytes2 } from "crypto";
6404
6937
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6405
6938
  var FRANKFURTER_URL = "https://api.frankfurter.app/latest?from=USD&to=";
@@ -6432,7 +6965,7 @@ function getFractionDigits(code) {
6432
6965
  }).resolvedOptions().maximumFractionDigits ?? 2;
6433
6966
  }
6434
6967
  function getRateCachePath() {
6435
- return join4(getCacheDir(), "exchange-rate.json");
6968
+ return join5(getCacheDir(), "exchange-rate.json");
6436
6969
  }
6437
6970
  async function fetchRate(code) {
6438
6971
  const response = await fetch(`${FRANKFURTER_URL}${code}`, {
@@ -6446,7 +6979,7 @@ async function fetchRate(code) {
6446
6979
  }
6447
6980
  async function loadCachedRate(code) {
6448
6981
  try {
6449
- const raw = await readFile3(getRateCachePath(), "utf-8");
6982
+ const raw = await readFile4(getRateCachePath(), "utf-8");
6450
6983
  const cached = JSON.parse(raw);
6451
6984
  if (typeof cached.code !== "string" || cached.code !== code) return null;
6452
6985
  if (typeof cached.timestamp !== "number" || !Number.isFinite(cached.timestamp)) return null;
@@ -6458,7 +6991,7 @@ async function loadCachedRate(code) {
6458
6991
  }
6459
6992
  }
6460
6993
  async function cacheRate(code, rate) {
6461
- await mkdir4(getCacheDir(), { recursive: true });
6994
+ await mkdir5(getCacheDir(), { recursive: true });
6462
6995
  const finalPath = getRateCachePath();
6463
6996
  const tmpPath = `${finalPath}.${randomBytes2(8).toString("hex")}.tmp`;
6464
6997
  const handle = await open3(tmpPath, "w", 384);
@@ -6469,10 +7002,10 @@ async function cacheRate(code, rate) {
6469
7002
  await handle.close();
6470
7003
  }
6471
7004
  try {
6472
- await rename3(tmpPath, finalPath);
7005
+ await rename4(tmpPath, finalPath);
6473
7006
  } catch (err) {
6474
7007
  try {
6475
- await unlink2(tmpPath);
7008
+ await unlink3(tmpPath);
6476
7009
  } catch {
6477
7010
  }
6478
7011
  throw err;
@@ -6736,13 +7269,13 @@ function buildReadme(periods) {
6736
7269
  }
6737
7270
  var EXPORT_MARKER_FILE = ".exe-watcher-export";
6738
7271
  async function isExeWatcherExportFolder(path) {
6739
- const markerStat = await stat3(join6(path, EXPORT_MARKER_FILE)).catch(() => null);
7272
+ const markerStat = await stat3(join7(path, EXPORT_MARKER_FILE)).catch(() => null);
6740
7273
  return markerStat?.isFile() ?? false;
6741
7274
  }
6742
7275
  async function clearExeWatcherExportFolder(path) {
6743
7276
  const entries = await readdir2(path);
6744
7277
  for (const entry of entries) {
6745
- await rm2(join6(path, entry), { recursive: true, force: true });
7278
+ await rm2(join7(path, entry), { recursive: true, force: true });
6746
7279
  }
6747
7280
  }
6748
7281
  async function exportCsv(periods, outputPath) {
@@ -6764,20 +7297,20 @@ async function exportCsv(periods, outputPath) {
6764
7297
  }
6765
7298
  await clearExeWatcherExportFolder(folder);
6766
7299
  }
6767
- await mkdir5(folder, { recursive: true });
6768
- await writeFile(join6(folder, EXPORT_MARKER_FILE), "", "utf-8");
7300
+ await mkdir6(folder, { recursive: true });
7301
+ await writeFile2(join7(folder, EXPORT_MARKER_FILE), "", "utf-8");
6769
7302
  const dailyRows = periods.flatMap((p) => buildDailyRows(p.projects, p.label));
6770
7303
  const activityRows = periods.flatMap((p) => buildActivityRows(p.projects, p.label));
6771
7304
  const modelRows = periods.flatMap((p) => buildModelRows(p.projects, p.label));
6772
- await writeFile(join6(folder, "README.txt"), buildReadme(periods), "utf-8");
6773
- await writeFile(join6(folder, "summary.csv"), rowsToCsv(buildSummaryRows(periods)), "utf-8");
6774
- await writeFile(join6(folder, "daily.csv"), rowsToCsv(dailyRows), "utf-8");
6775
- await writeFile(join6(folder, "activity.csv"), rowsToCsv(activityRows), "utf-8");
6776
- await writeFile(join6(folder, "models.csv"), rowsToCsv(modelRows), "utf-8");
6777
- await writeFile(join6(folder, "projects.csv"), rowsToCsv(buildProjectRows(thirtyDayProjects)), "utf-8");
6778
- await writeFile(join6(folder, "sessions.csv"), rowsToCsv(buildSessionRows(thirtyDayProjects)), "utf-8");
6779
- await writeFile(join6(folder, "tools.csv"), rowsToCsv(buildToolRows(thirtyDayProjects)), "utf-8");
6780
- await writeFile(join6(folder, "shell-commands.csv"), rowsToCsv(buildBashRows(thirtyDayProjects)), "utf-8");
7305
+ await writeFile2(join7(folder, "README.txt"), buildReadme(periods), "utf-8");
7306
+ await writeFile2(join7(folder, "summary.csv"), rowsToCsv(buildSummaryRows(periods)), "utf-8");
7307
+ await writeFile2(join7(folder, "daily.csv"), rowsToCsv(dailyRows), "utf-8");
7308
+ await writeFile2(join7(folder, "activity.csv"), rowsToCsv(activityRows), "utf-8");
7309
+ await writeFile2(join7(folder, "models.csv"), rowsToCsv(modelRows), "utf-8");
7310
+ await writeFile2(join7(folder, "projects.csv"), rowsToCsv(buildProjectRows(thirtyDayProjects)), "utf-8");
7311
+ await writeFile2(join7(folder, "sessions.csv"), rowsToCsv(buildSessionRows(thirtyDayProjects)), "utf-8");
7312
+ await writeFile2(join7(folder, "tools.csv"), rowsToCsv(buildToolRows(thirtyDayProjects)), "utf-8");
7313
+ await writeFile2(join7(folder, "shell-commands.csv"), rowsToCsv(buildBashRows(thirtyDayProjects)), "utf-8");
6781
7314
  return folder;
6782
7315
  }
6783
7316
  async function exportJson(periods, outputPath) {
@@ -6801,8 +7334,8 @@ async function exportJson(periods, outputPath) {
6801
7334
  shellCommands: buildBashRows(thirtyDayProjects)
6802
7335
  };
6803
7336
  const target = resolve(outputPath.toLowerCase().endsWith(".json") ? outputPath : `${outputPath}.json`);
6804
- await mkdir5(dirname2(target), { recursive: true });
6805
- await writeFile(target, JSON.stringify(data, null, 2), "utf-8");
7337
+ await mkdir6(dirname3(target), { recursive: true });
7338
+ await writeFile2(target, JSON.stringify(data, null, 2), "utf-8");
6806
7339
  return target;
6807
7340
  }
6808
7341
 
@@ -6856,12 +7389,12 @@ function renderStatusBar(projects) {
6856
7389
 
6857
7390
  // src/menubar-json.ts
6858
7391
  import { readdirSync, readFileSync } from "fs";
6859
- import { join as join22, dirname as dirname5 } from "path";
7392
+ import { join as join23, dirname as dirname6 } from "path";
6860
7393
  init_models();
6861
7394
  import { fileURLToPath } from "url";
6862
7395
  function getCliVersion() {
6863
7396
  try {
6864
- const pkgPath = join22(dirname5(fileURLToPath(import.meta.url)), "..", "package.json");
7397
+ const pkgPath = join23(dirname6(fileURLToPath(import.meta.url)), "..", "package.json");
6865
7398
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
6866
7399
  return pkg.version ?? "0.0.0";
6867
7400
  } catch {
@@ -7026,16 +7559,16 @@ init_cache_dir();
7026
7559
  init_models();
7027
7560
  import { createHash as createHash6, randomBytes as randomBytes6 } from "crypto";
7028
7561
  import { existsSync as existsSync5 } from "fs";
7029
- import { mkdir as mkdir11, open as open8, readFile as readFile12, rename as rename9, unlink as unlink7 } from "fs/promises";
7030
- import { join as join23 } from "path";
7562
+ import { mkdir as mkdir12, open as open8, readFile as readFile13, rename as rename10, unlink as unlink8 } from "fs/promises";
7563
+ import { join as join24 } from "path";
7031
7564
  var DAILY_CACHE_VERSION = 9;
7032
7565
  var DEFAULT_DAILY_CACHE_SCOPE = "global";
7033
7566
  var DAILY_CACHE_FILENAME = "daily-cache.json";
7034
7567
  function getCachePath3(scopeKey) {
7035
7568
  if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
7036
- return join23(getCacheDir(), DAILY_CACHE_FILENAME);
7569
+ return join24(getCacheDir(), DAILY_CACHE_FILENAME);
7037
7570
  }
7038
- return join23(getCacheDir(), `daily-cache-${scopeKey}.json`);
7571
+ return join24(getCacheDir(), `daily-cache-${scopeKey}.json`);
7039
7572
  }
7040
7573
  function emptyCache(scopeKey) {
7041
7574
  return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
@@ -7071,7 +7604,7 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
7071
7604
  const path = getCachePath3(scopeKey);
7072
7605
  if (!existsSync5(path)) return emptyCache(scopeKey);
7073
7606
  try {
7074
- const raw = await readFile12(path, "utf-8");
7607
+ const raw = await readFile13(path, "utf-8");
7075
7608
  const parsed = JSON.parse(raw);
7076
7609
  if (!isValidCache(parsed)) return emptyCache(scopeKey);
7077
7610
  if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
@@ -7099,12 +7632,12 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
7099
7632
  }
7100
7633
  async function saveDailyCache(cache) {
7101
7634
  const dir = getCacheDir();
7102
- if (!existsSync5(dir)) await mkdir11(dir, { recursive: true });
7635
+ if (!existsSync5(dir)) await mkdir12(dir, { recursive: true });
7103
7636
  const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
7104
7637
  const finalPath = getCachePath3(scopeKey);
7105
7638
  const tempPath = `${finalPath}.${randomBytes6(8).toString("hex")}.tmp`;
7106
7639
  const payload = JSON.stringify({ ...cache, scopeKey, pricingFingerprint: getPricingFingerprint() });
7107
- if (await readFile12(finalPath, "utf8").catch(() => "") === payload) return;
7640
+ if (await readFile13(finalPath, "utf8").catch(() => "") === payload) return;
7108
7641
  const handle = await open8(tempPath, "w", 384);
7109
7642
  try {
7110
7643
  await handle.writeFile(payload, { encoding: "utf-8" });
@@ -7113,10 +7646,10 @@ async function saveDailyCache(cache) {
7113
7646
  await handle.close();
7114
7647
  }
7115
7648
  try {
7116
- await rename9(tempPath, finalPath);
7649
+ await rename10(tempPath, finalPath);
7117
7650
  } catch (err) {
7118
7651
  try {
7119
- await unlink7(tempPath);
7652
+ await unlink8(tempPath);
7120
7653
  } catch {
7121
7654
  }
7122
7655
  throw err;
@@ -7159,17 +7692,17 @@ init_types();
7159
7692
  // src/claude-recovery.ts
7160
7693
  init_cache_dir();
7161
7694
  init_models();
7162
- import { readFile as readFile13, stat as stat16, mkdir as mkdir12, writeFile as writeFile4, rename as rename10, unlink as unlink8 } from "fs/promises";
7163
- import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
7695
+ import { readFile as readFile14, stat as stat16, mkdir as mkdir13, writeFile as writeFile5, rename as rename11, unlink as unlink9 } from "fs/promises";
7696
+ import { createHash as createHash7, randomUUID as randomUUID4 } from "crypto";
7164
7697
  import { homedir as homedir12 } from "os";
7165
- import { join as join25 } from "path";
7698
+ import { join as join26 } from "path";
7166
7699
 
7167
7700
  // src/history-start.ts
7168
7701
  init_discovery_cache();
7169
7702
  init_fs_utils();
7170
7703
  init_parser();
7171
7704
  import { readdir as readdir12, stat as stat15 } from "fs/promises";
7172
- import { join as join24 } from "path";
7705
+ import { join as join25 } from "path";
7173
7706
  function parseHistoryStartDate(value) {
7174
7707
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
7175
7708
  const [year, month, day] = value.split("-").map(Number);
@@ -7189,7 +7722,7 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
7189
7722
  for (const source of sources) {
7190
7723
  const sourceStat = await stat15(source.path).catch(() => null);
7191
7724
  if (!sourceStat) continue;
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] : [];
7725
+ const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) => join25(source.path, name)) : source.path.endsWith(".jsonl") ? [source.path] : [];
7193
7726
  for (const path of paths) {
7194
7727
  const fileStat = await stat15(path).catch(() => null);
7195
7728
  if (!fileStat) continue;
@@ -7240,13 +7773,13 @@ function applyClaudeRecovery(data, recovery) {
7240
7773
  }
7241
7774
  }
7242
7775
  async function readClaudeUsageSnapshot() {
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`);
7776
+ const root = process.env["CLAUDE_CONFIG_DIR"] || join26(homedir12(), ".claude");
7777
+ const path = join26(root, "stats-cache.json");
7778
+ const savedPath = join26(getCacheDir(), `claude-lifetime-${createHash7("sha256").update(root).digest("hex").slice(0, 16)}.json`);
7246
7779
  const read = async (file) => {
7247
7780
  try {
7248
7781
  if ((await stat16(file)).size > 2 * 1024 ** 2) return null;
7249
- const data = JSON.parse(await readFile13(file, "utf8"));
7782
+ const data = JSON.parse(await readFile14(file, "utf8"));
7250
7783
  if (!data || typeof data.firstSessionDate !== "string" || !parseHistoryStartDate(data.lastComputedDate) || !data.modelUsage || typeof data.modelUsage !== "object") return null;
7251
7784
  if (!Number.isFinite(new Date(data.firstSessionDate).getTime())) return null;
7252
7785
  if (parseHistoryStartDate(data.lastComputedDate).getTime() > Date.now()) return null;
@@ -7270,15 +7803,15 @@ async function readClaudeUsageSnapshot() {
7270
7803
  }
7271
7804
  }
7272
7805
  const payload = JSON.stringify(combined);
7273
- if (await readFile13(savedPath, "utf8").catch(() => "") !== payload) {
7274
- await mkdir12(getCacheDir(), { recursive: true });
7275
- const tmp = `${savedPath}.${randomUUID3()}.tmp`;
7806
+ if (await readFile14(savedPath, "utf8").catch(() => "") !== payload) {
7807
+ await mkdir13(getCacheDir(), { recursive: true });
7808
+ const tmp = `${savedPath}.${randomUUID4()}.tmp`;
7276
7809
  try {
7277
- await writeFile4(tmp, payload, { mode: 384 });
7278
- await rename10(tmp, savedPath);
7810
+ await writeFile5(tmp, payload, { mode: 384 });
7811
+ await rename11(tmp, savedPath);
7279
7812
  } catch {
7280
7813
  } finally {
7281
- await unlink8(tmp).catch(() => {
7814
+ await unlink9(tmp).catch(() => {
7282
7815
  });
7283
7816
  }
7284
7817
  }
@@ -7411,7 +7944,7 @@ init_providers();
7411
7944
  import chalk2 from "chalk";
7412
7945
  import { readdir as readdir13, stat as stat17 } from "fs/promises";
7413
7946
  import { existsSync as existsSync6, statSync as statSync2 } from "fs";
7414
- import { basename as basename10, join as join26 } from "path";
7947
+ import { basename as basename10, join as join27 } from "path";
7415
7948
  import { homedir as homedir13 } from "os";
7416
7949
  var ORANGE = "#F59E0B";
7417
7950
  var DIM = "#666666";
@@ -7494,13 +8027,13 @@ var DEFAULT_TREND_PERIOD_MS = DEFAULT_TREND_PERIOD_DAYS * 24 * 60 * 60 * 1e3;
7494
8027
  var IMPROVING_THRESHOLD = 0.5;
7495
8028
  async function collectJsonlFiles3(dirPath) {
7496
8029
  const files = await readdir13(dirPath).catch(() => []);
7497
- const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join26(dirPath, f));
8030
+ const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join27(dirPath, f));
7498
8031
  for (const entry of files) {
7499
8032
  if (entry.endsWith(".jsonl")) continue;
7500
- const subPath = join26(dirPath, entry, "subagents");
8033
+ const subPath = join27(dirPath, entry, "subagents");
7501
8034
  const subFiles = await readdir13(subPath).catch(() => []);
7502
8035
  for (const sf of subFiles) {
7503
- if (sf.endsWith(".jsonl")) result.push(join26(subPath, sf));
8036
+ if (sf.endsWith(".jsonl")) result.push(join27(subPath, sf));
7504
8037
  }
7505
8038
  }
7506
8039
  return result;
@@ -7690,13 +8223,13 @@ function isReadTool(name) {
7690
8223
  function loadMcpConfigs(projectCwds) {
7691
8224
  const servers = /* @__PURE__ */ new Map();
7692
8225
  const configPaths = [
7693
- join26(homedir13(), ".claude", "settings.json"),
7694
- join26(homedir13(), ".claude", "settings.local.json")
8226
+ join27(homedir13(), ".claude", "settings.json"),
8227
+ join27(homedir13(), ".claude", "settings.local.json")
7695
8228
  ];
7696
8229
  for (const cwd of projectCwds) {
7697
- configPaths.push(join26(cwd, ".mcp.json"));
7698
- configPaths.push(join26(cwd, ".claude", "settings.json"));
7699
- configPaths.push(join26(cwd, ".claude", "settings.local.json"));
8230
+ configPaths.push(join27(cwd, ".mcp.json"));
8231
+ configPaths.push(join27(cwd, ".claude", "settings.json"));
8232
+ configPaths.push(join27(cwd, ".claude", "settings.local.json"));
7700
8233
  }
7701
8234
  for (const p of configPaths) {
7702
8235
  if (!existsSync6(p)) continue;
@@ -7849,12 +8382,12 @@ function expandImports(filePath, seen, depth) {
7849
8382
  if (content === null) return { totalLines: 0, importedFiles: 0 };
7850
8383
  let totalLines = content.split("\n").length;
7851
8384
  let importedFiles = 0;
7852
- const dir = join26(filePath, "..");
8385
+ const dir = join27(filePath, "..");
7853
8386
  IMPORT_PATTERN.lastIndex = 0;
7854
8387
  for (const match of content.matchAll(IMPORT_PATTERN)) {
7855
8388
  const rawPath = match[1];
7856
8389
  if (!rawPath) continue;
7857
- const resolved = rawPath.startsWith("/") ? rawPath : join26(dir, rawPath);
8390
+ const resolved = rawPath.startsWith("/") ? rawPath : join27(dir, rawPath);
7858
8391
  if (!existsSync6(resolved)) continue;
7859
8392
  const nested = expandImports(resolved, seen, depth + 1);
7860
8393
  totalLines += nested.totalLines;
@@ -7866,7 +8399,7 @@ function detectBloatedClaudeMd(projectCwds) {
7866
8399
  const bloated = [];
7867
8400
  for (const cwd of projectCwds) {
7868
8401
  for (const name of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
7869
- const fullPath = join26(cwd, name);
8402
+ const fullPath = join27(cwd, name);
7870
8403
  if (!existsSync6(fullPath)) continue;
7871
8404
  const { totalLines, importedFiles } = expandImports(fullPath, /* @__PURE__ */ new Set(), 0);
7872
8405
  if (totalLines > CLAUDEMD_HEALTHY_LINES) {
@@ -8009,7 +8542,7 @@ async function listSkillDirs(dir) {
8009
8542
  const entries = await readdir13(dir);
8010
8543
  const names = [];
8011
8544
  for (const entry of entries) {
8012
- if (existsSync6(join26(dir, entry, "SKILL.md"))) names.push(entry);
8545
+ if (existsSync6(join27(dir, entry, "SKILL.md"))) names.push(entry);
8013
8546
  }
8014
8547
  return names;
8015
8548
  } catch {
@@ -8017,7 +8550,7 @@ async function listSkillDirs(dir) {
8017
8550
  }
8018
8551
  }
8019
8552
  async function detectGhostAgents(calls) {
8020
- const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "agents"));
8553
+ const defined = await listMarkdownFiles(join27(homedir13(), ".claude", "agents"));
8021
8554
  if (defined.length === 0) return null;
8022
8555
  const invoked = /* @__PURE__ */ new Set();
8023
8556
  for (const call of calls) {
@@ -8042,7 +8575,7 @@ async function detectGhostAgents(calls) {
8042
8575
  };
8043
8576
  }
8044
8577
  async function detectGhostSkills(calls) {
8045
- const defined = await listSkillDirs(join26(homedir13(), ".claude", "skills"));
8578
+ const defined = await listSkillDirs(join27(homedir13(), ".claude", "skills"));
8046
8579
  if (defined.length === 0) return null;
8047
8580
  const invoked = /* @__PURE__ */ new Set();
8048
8581
  for (const call of calls) {
@@ -8067,7 +8600,7 @@ async function detectGhostSkills(calls) {
8067
8600
  };
8068
8601
  }
8069
8602
  async function detectGhostCommands(userMessages) {
8070
- const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "commands"));
8603
+ const defined = await listMarkdownFiles(join27(homedir13(), ".claude", "commands"));
8071
8604
  if (defined.length === 0) return null;
8072
8605
  const invoked = /* @__PURE__ */ new Set();
8073
8606
  for (const msg of userMessages) {
@@ -8095,7 +8628,7 @@ async function detectGhostCommands(userMessages) {
8095
8628
  }
8096
8629
  function readShellProfileLimit() {
8097
8630
  for (const profile of SHELL_PROFILES) {
8098
- const path = join26(homedir13(), profile);
8631
+ const path = join27(homedir13(), profile);
8099
8632
  if (!existsSync6(path)) continue;
8100
8633
  const content = readSessionFileSync(path);
8101
8634
  if (content === null) continue;
@@ -8316,10 +8849,10 @@ async function runOptimize(projects, periodLabel, dateRange, preScanned) {
8316
8849
 
8317
8850
  // src/optimize-share.ts
8318
8851
  import { spawn as spawn2 } from "child_process";
8319
- import { randomUUID as randomUUID4 } from "crypto";
8320
- import { stat as stat18, writeFile as writeFile5 } from "fs/promises";
8852
+ import { randomUUID as randomUUID5 } from "crypto";
8853
+ import { stat as stat18, writeFile as writeFile6 } from "fs/promises";
8321
8854
  import { homedir as homedir14 } from "os";
8322
- import { join as join27, resolve as resolve3 } from "path";
8855
+ import { join as join28, resolve as resolve3 } from "path";
8323
8856
  function codeBlock(content, language) {
8324
8857
  const longest = (content.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
8325
8858
  const fence = "`".repeat(Math.max(3, longest + 1));
@@ -8413,11 +8946,11 @@ async function copyOptimizeMarkdown(markdown) {
8413
8946
  throw new Error("Could not copy. Press e to save a Markdown file instead.");
8414
8947
  }
8415
8948
  async function saveOptimizeMarkdown(markdown, directory) {
8416
- const downloads = join27(homedir14(), "Downloads");
8949
+ const downloads = join28(homedir14(), "Downloads");
8417
8950
  const folder = directory ?? ((await stat18(downloads).catch(() => null))?.isDirectory() ? downloads : process.cwd());
8418
8951
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8419
- const path = resolve3(folder, `exe-watcher-optimizer-${stamp}-${randomUUID4().slice(0, 8)}.md`);
8420
- await writeFile5(path, markdown, { encoding: "utf8", mode: 384, flag: "wx" });
8952
+ const path = resolve3(folder, `exe-watcher-optimizer-${stamp}-${randomUUID5().slice(0, 8)}.md`);
8953
+ await writeFile6(path, markdown, { encoding: "utf8", mode: 384, flag: "wx" });
8421
8954
  return path;
8422
8955
  }
8423
8956
 
@@ -8425,7 +8958,7 @@ async function saveOptimizeMarkdown(markdown, directory) {
8425
8958
  init_fs_utils();
8426
8959
  import { readdir as readdir14 } from "fs/promises";
8427
8960
  import { existsSync as existsSync7 } from "fs";
8428
- import { join as join28 } from "path";
8961
+ import { join as join29 } from "path";
8429
8962
  import { homedir as homedir15 } from "os";
8430
8963
  var CHARS_PER_TOKEN2 = 4;
8431
8964
  var SYSTEM_BASE_TOKENS = 10400;
@@ -8447,13 +8980,13 @@ async function readConfigFile(path) {
8447
8980
  async function countMcpTools(projectPath) {
8448
8981
  const home = homedir15();
8449
8982
  const configPaths = [
8450
- join28(home, ".claude", "settings.json"),
8451
- join28(home, ".claude", "settings.local.json")
8983
+ join29(home, ".claude", "settings.json"),
8984
+ join29(home, ".claude", "settings.local.json")
8452
8985
  ];
8453
8986
  if (projectPath) {
8454
- configPaths.push(join28(projectPath, ".mcp.json"));
8455
- configPaths.push(join28(projectPath, ".claude", "settings.json"));
8456
- configPaths.push(join28(projectPath, ".claude", "settings.local.json"));
8987
+ configPaths.push(join29(projectPath, ".mcp.json"));
8988
+ configPaths.push(join29(projectPath, ".claude", "settings.json"));
8989
+ configPaths.push(join29(projectPath, ".claude", "settings.local.json"));
8457
8990
  }
8458
8991
  const servers = /* @__PURE__ */ new Set();
8459
8992
  let toolCount = 0;
@@ -8470,15 +9003,15 @@ async function countMcpTools(projectPath) {
8470
9003
  return toolCount;
8471
9004
  }
8472
9005
  async function countSkills(projectPath) {
8473
- const dirs = [join28(homedir15(), ".claude", "skills")];
8474
- if (projectPath) dirs.push(join28(projectPath, ".claude", "skills"));
9006
+ const dirs = [join29(homedir15(), ".claude", "skills")];
9007
+ if (projectPath) dirs.push(join29(projectPath, ".claude", "skills"));
8475
9008
  let count = 0;
8476
9009
  for (const dir of dirs) {
8477
9010
  if (!existsSync7(dir)) continue;
8478
9011
  try {
8479
9012
  const entries = await readdir14(dir);
8480
9013
  for (const entry of entries) {
8481
- const skillFile = join28(dir, entry, "SKILL.md");
9014
+ const skillFile = join29(dir, entry, "SKILL.md");
8482
9015
  if (existsSync7(skillFile)) count++;
8483
9016
  }
8484
9017
  } catch {
@@ -8491,12 +9024,12 @@ async function scanMemoryFiles(projectPath) {
8491
9024
  const home = homedir15();
8492
9025
  const files = [];
8493
9026
  const paths = [
8494
- { path: join28(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
9027
+ { path: join29(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
8495
9028
  ];
8496
9029
  if (projectPath) {
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" });
9030
+ paths.push({ path: join29(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
9031
+ paths.push({ path: join29(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
9032
+ paths.push({ path: join29(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
8500
9033
  }
8501
9034
  for (const { path, name } of paths) {
8502
9035
  if (!existsSync7(path)) continue;
@@ -8533,7 +9066,7 @@ async function discoverProjectCwd(sessionDir) {
8533
9066
  if (files.length === 0) return null;
8534
9067
  const firstFile = files[0];
8535
9068
  if (!firstFile) return null;
8536
- const content = await readSessionFile(join28(sessionDir, firstFile));
9069
+ const content = await readSessionFile(join29(sessionDir, firstFile));
8537
9070
  if (content === null) return null;
8538
9071
  for (const line of content.split("\n")) {
8539
9072
  if (!line.trim()) continue;
@@ -9227,15 +9760,15 @@ function owlFrame(tick) {
9227
9760
  }
9228
9761
 
9229
9762
  // src/dashboard.tsx
9230
- import { join as join31 } from "path";
9763
+ import { join as join32 } from "path";
9231
9764
 
9232
9765
  // src/time-index.ts
9233
9766
  init_cache_dir();
9234
9767
  init_file_lock();
9235
9768
  init_resource_budget();
9236
- import { createHash as createHash9, randomUUID as randomUUID5 } from "crypto";
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";
9238
- import { basename as basename11, dirname as dirname6, join as join30 } from "path";
9769
+ import { createHash as createHash9, randomUUID as randomUUID6 } from "crypto";
9770
+ import { mkdir as mkdir14, open as open9, readFile as readFile15, rename as rename12, stat as stat19, unlink as unlink10, readdir as readdir15 } from "fs/promises";
9771
+ import { basename as basename11, dirname as dirname7, join as join31 } from "path";
9239
9772
  import { gzip as gzip2, gunzip as gunzip2 } from "zlib";
9240
9773
  import { promisify as promisify2 } from "util";
9241
9774
 
@@ -9245,12 +9778,12 @@ import { createHash as createHash8 } from "crypto";
9245
9778
  // src/personal-activity.ts
9246
9779
  init_cache_dir();
9247
9780
  import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2 } from "fs";
9248
- import { join as join29 } from "path";
9781
+ import { join as join30 } from "path";
9249
9782
  var ACTIVITY_LIMIT = 4 * 1024 ** 2;
9250
9783
  function readActivityHistory() {
9251
9784
  let fd;
9252
9785
  try {
9253
- fd = openSync2(join29(getCacheDir(), "personal-activity-v1.json"), constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
9786
+ fd = openSync2(join30(getCacheDir(), "personal-activity-v1.json"), constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
9254
9787
  const stat20 = fstatSync2(fd);
9255
9788
  if (!stat20.isFile() || stat20.size > ACTIVITY_LIMIT) return void 0;
9256
9789
  const buffer = Buffer.alloc(stat20.size);
@@ -9679,12 +10212,12 @@ var MAX_DISK2 = 16 * 1024 ** 2;
9679
10212
  var MAX_FILES = 2e4;
9680
10213
  var BLOCK = 2 * 1024 ** 2;
9681
10214
  var adapterVersion = (provider) => provider === "opencode" ? 2 : 1;
9682
- var indexPath2 = () => join30(getCacheDir(), "time-index-v1.json.gz");
10215
+ var indexPath2 = () => join31(getCacheDir(), "time-index-v1.json.gz");
9683
10216
  var empty = () => ({ version: 1, files: /* @__PURE__ */ Object.create(null), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), warnings: [] });
9684
10217
  async function readTimeIndex() {
9685
10218
  try {
9686
10219
  if ((await stat19(indexPath2())).size > MAX_DISK2) throw new Error("Timing index exceeds its disk limit");
9687
- const value = JSON.parse((await decompress2(await readFile14(indexPath2()), { maxOutputLength: MAX_INDEX })).toString("utf8"));
10220
+ const value = JSON.parse((await decompress2(await readFile15(indexPath2()), { maxOutputLength: MAX_INDEX })).toString("utf8"));
9688
10221
  if (value.version !== 1 || !value.files || typeof value.files !== "object" || Array.isArray(value.files) || Object.keys(value.files).length > MAX_FILES || !Array.isArray(value.warnings) || !value.warnings.every((x) => typeof x === "string") || typeof value.updatedAt !== "string") throw new Error("Invalid timing index");
9689
10222
  for (const row of Object.values(value.files)) {
9690
10223
  const interval2 = (x) => Array.isArray(x) && x.length >= 3 && typeof x[0] === "number" && Number.isFinite(x[0]) && typeof x[1] === "number" && Number.isFinite(x[1]) && (x[2] === 0 || x[2] === 1) && (x[3] === void 0 || typeof x[3] === "string");
@@ -9703,8 +10236,8 @@ async function save(index) {
9703
10236
  if (Buffer.byteLength(data) > MAX_INDEX) throw new Error("Timing index reached its 32 MiB memory/disk snapshot limit; use a narrower history range.");
9704
10237
  const encoded = await compress2(Buffer.from(data));
9705
10238
  if (encoded.length > MAX_DISK2) throw new Error("Timing index reached its 16 MiB compressed disk limit.");
9706
- const path = indexPath2(), temp = `${path}.${randomUUID5()}.tmp`;
9707
- await mkdir13(getCacheDir(), { recursive: true });
10239
+ const path = indexPath2(), temp = `${path}.${randomUUID6()}.tmp`;
10240
+ await mkdir14(getCacheDir(), { recursive: true });
9708
10241
  try {
9709
10242
  const handle = await open9(temp, "wx", 384);
9710
10243
  try {
@@ -9713,9 +10246,9 @@ async function save(index) {
9713
10246
  } finally {
9714
10247
  await handle.close();
9715
10248
  }
9716
- await rename11(temp, path);
10249
+ await rename12(temp, path);
9717
10250
  } finally {
9718
- await unlink9(temp).catch(() => {
10251
+ await unlink10(temp).catch(() => {
9719
10252
  });
9720
10253
  }
9721
10254
  }
@@ -9742,11 +10275,11 @@ async function expandSources(sources, warnings) {
9742
10275
  continue;
9743
10276
  }
9744
10277
  for (const entry of await readdir15(source.path, { withFileTypes: true }).catch(() => [])) {
9745
- if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join30(source.path, entry.name) });
10278
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join31(source.path, entry.name) });
9746
10279
  else if (entry.isDirectory()) {
9747
- const folder = join30(source.path, entry.name, "subagents");
10280
+ const folder = join31(source.path, entry.name, "subagents");
9748
10281
  for (const agent of await readdir15(folder, { withFileTypes: true }).catch(() => [])) {
9749
- if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join30(folder, agent.name) });
10282
+ if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join31(folder, agent.name) });
9750
10283
  }
9751
10284
  }
9752
10285
  }
@@ -9766,9 +10299,9 @@ async function anchor(path, offset) {
9766
10299
  }
9767
10300
  }
9768
10301
  function fresh(source, identity, size, mtime, ctime) {
9769
- const child = source.path.includes("/subagents/") || basename11(source.path).startsWith("agent-") || source.provider === "kimi" && basename11(dirname6(source.path)) !== "main";
9770
- const kimiSession = source.path.split("/").find((part) => part.startsWith("session_")) ?? basename11(dirname6(dirname6(source.path)));
9771
- const agent = source.provider === "copilot" ? basename11(dirname6(source.path)) : source.provider === "kimi" ? `${kimiSession}/${basename11(dirname6(source.path))}` : `${child ? basename11(dirname6(dirname6(source.path))) + "/" : ""}${basename11(source.path, ".jsonl")}`;
10302
+ const child = source.path.includes("/subagents/") || basename11(source.path).startsWith("agent-") || source.provider === "kimi" && basename11(dirname7(source.path)) !== "main";
10303
+ const kimiSession = source.path.split("/").find((part) => part.startsWith("session_")) ?? basename11(dirname7(dirname7(source.path)));
10304
+ const agent = source.provider === "copilot" ? basename11(dirname7(source.path)) : source.provider === "kimi" ? `${kimiSession}/${basename11(dirname7(source.path))}` : `${child ? basename11(dirname7(dirname7(source.path))) + "/" : ""}${basename11(source.path, ".jsonl")}`;
9772
10305
  return {
9773
10306
  provider: source.provider,
9774
10307
  project: source.project,
@@ -9843,7 +10376,7 @@ async function refreshTimeIndex(options = {}) {
9843
10376
  let current;
9844
10377
  let release;
9845
10378
  try {
9846
- release = await acquireFileLock(join30(getCacheDir(), "time-index.lock"), 100);
10379
+ release = await acquireFileLock(join31(getCacheDir(), "time-index.lock"), 100);
9847
10380
  } catch {
9848
10381
  current = await readTimeIndex();
9849
10382
  current.warnings.push("Another timing update is running; showing the saved timing snapshot.");
@@ -10808,11 +11341,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
10808
11341
  useEffect2(() => {
10809
11342
  let cancelled = false;
10810
11343
  async function loadBudgets() {
10811
- const claudeDir = join31(homedir16(), ".claude", "projects");
11344
+ const claudeDir = join32(homedir16(), ".claude", "projects");
10812
11345
  const budgets = /* @__PURE__ */ new Map();
10813
11346
  for (const project of projects.slice(0, 8)) {
10814
11347
  if (cancelled) return;
10815
- const cwd = await discoverProjectCwd(join31(claudeDir, project.project));
11348
+ const cwd = await discoverProjectCwd(join32(claudeDir, project.project));
10816
11349
  if (!cwd) continue;
10817
11350
  budgets.set(project.project, await estimateContextBudget(cwd));
10818
11351
  }
@@ -11479,9 +12012,32 @@ async function runJsonReport(period, provider, project, exclude) {
11479
12012
  }
11480
12013
  console.log(JSON.stringify({ ...report, pricing: getPricingMetadata() }, null, 2));
11481
12014
  }
12015
+ var telemetry;
12016
+ var telemetryCommand = "unknown";
11482
12017
  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");
11483
12018
  program.hook("preAction", async (thisCommand, command) => {
11484
12019
  const config = await readConfig();
12020
+ if (command.name() !== "telemetry" && process.stdin.isTTY && resolveTelemetry(config.telemetry).usageEnabled && config.telemetry?.basicNoticeVersion !== 1) {
12021
+ const noticePath = join33(dirname8(getConfigFilePath()), "basic-analytics-notice-v1");
12022
+ if (!existsSync8(noticePath)) console.error(`
12023
+ ${BASIC_NOTICE}
12024
+ `);
12025
+ config.telemetry = { ...config.telemetry, basicNoticeVersion: 1 };
12026
+ try {
12027
+ const fs = await import("fs/promises");
12028
+ await fs.mkdir(dirname8(noticePath), { recursive: true, mode: 448 });
12029
+ try {
12030
+ await fs.writeFile(noticePath, "1\n", { flag: "wx", mode: 384 });
12031
+ } catch (error) {
12032
+ if (error.code !== "EEXIST") throw error;
12033
+ }
12034
+ } catch {
12035
+ config.telemetry.usageEnabled = false;
12036
+ }
12037
+ }
12038
+ telemetry = command.name() === "telemetry" ? void 0 : await createTelemetry(config.telemetry, version);
12039
+ telemetryCommand = command.name();
12040
+ await telemetry?.capture("command_started", telemetryCommand);
11485
12041
  setModelAliases(config.modelAliases ?? {});
11486
12042
  configuredHistoryStart = parseHistoryStartDate(config.historyStartDate);
11487
12043
  if (thisCommand.opts().verbose) {
@@ -11986,12 +12542,12 @@ program.command("status").description("Compact status output (today + week + mon
11986
12542
  `);
11987
12543
  }
11988
12544
  }
11989
- const exeOsDir = join32(homedir17(), ".exe-os");
12545
+ const exeOsDir = join33(homedir17(), ".exe-os");
11990
12546
  const exeOsDetected = existsSync8(exeOsDir);
11991
12547
  let agentStats = null;
11992
12548
  let statsFileAge = null;
11993
12549
  try {
11994
- const statsPath = join32(exeOsDir, "agent-stats.json");
12550
+ const statsPath = join33(exeOsDir, "agent-stats.json");
11995
12551
  if (existsSync8(statsPath)) {
11996
12552
  statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
11997
12553
  const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
@@ -12397,9 +12953,9 @@ program.command("compare").description("Compare two AI models side-by-side").opt
12397
12953
  await renderCompare(range, opts.provider);
12398
12954
  });
12399
12955
  program.command("agent-status").description("Diagnose exe-os agent data pipeline").action(() => {
12400
- const exeOsDir = join32(homedir17(), ".exe-os");
12401
- const statsPath = join32(exeOsDir, "agent-stats.json");
12402
- const pidPath = join32(exeOsDir, "exed.pid");
12956
+ const exeOsDir = join33(homedir17(), ".exe-os");
12957
+ const statsPath = join33(exeOsDir, "agent-stats.json");
12958
+ const pidPath = join33(exeOsDir, "exed.pid");
12403
12959
  const dirExists = existsSync8(exeOsDir);
12404
12960
  const statsExists = dirExists && existsSync8(statsPath);
12405
12961
  const pidExists = dirExists && existsSync8(pidPath);
@@ -12431,15 +12987,55 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
12431
12987
  }
12432
12988
  console.log(lines.join("\n"));
12433
12989
  });
12990
+ program.command("telemetry").description("Optional self-hosted usage statistics and sanitized error diagnostics (basic usage on; diagnostics off by default)").option("--usage <on|off>", "Allow pseudonymous installation and foreground feature counters").option("--errors <on|off>", "Consent to error category/class/version; no messages or content").option("--test-error", "Send one harmless handled diagnostic when error consent is enabled").action(async (opts) => {
12991
+ const config = await readConfig();
12992
+ for (const value of [opts.usage, opts.errors]) {
12993
+ if (value !== void 0 && value !== "on" && value !== "off") throw new Error("Use on or off for telemetry consent");
12994
+ }
12995
+ config.telemetry ??= {};
12996
+ if ((opts.usage !== void 0 || opts.errors !== void 0) && config.telemetry.enabled === false) {
12997
+ config.telemetry.usageEnabled = false;
12998
+ config.telemetry.diagnosticsEnabled = false;
12999
+ delete config.telemetry.enabled;
13000
+ }
13001
+ if (opts.usage !== void 0 && config.telemetry.usageEnabled !== (opts.usage === "on")) {
13002
+ config.telemetry.usageEnabled = opts.usage === "on";
13003
+ config.telemetry.usageEpoch = (await import("crypto")).randomUUID();
13004
+ }
13005
+ if (opts.errors !== void 0 && config.telemetry.diagnosticsEnabled !== (opts.errors === "on")) {
13006
+ config.telemetry.diagnosticsEnabled = opts.errors === "on";
13007
+ config.telemetry.diagnosticsEpoch = (await import("crypto")).randomUUID();
13008
+ }
13009
+ if (opts.usage !== void 0 || opts.errors !== void 0) await saveConfig(config);
13010
+ const effective = resolveTelemetry(config.telemetry);
13011
+ const current = await createTelemetry(config.telemetry, version);
13012
+ if (opts.testError && config.telemetry.diagnosticsEnabled === true) {
13013
+ try {
13014
+ throw new Error("Watcher harmless handled diagnostic");
13015
+ } catch (error) {
13016
+ await current.error(error);
13017
+ }
13018
+ }
13019
+ await current.shutdown();
13020
+ console.log(JSON.stringify({
13021
+ usage: effective.usageEnabled,
13022
+ errors: effective.diagnosticsEnabled,
13023
+ collector: "https://data.odd.community",
13024
+ recordingMinutes: "N/A",
13025
+ notice: BASIC_NOTICE
13026
+ }));
13027
+ });
12434
13028
  var releaseScan;
12435
13029
  var stopParentWatch = watchMenubarParent(Number(process.env["EXE_WATCHER_PARENT_PID"]));
12436
13030
  program.hook("preAction", async (_root, command) => {
12437
- if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join32(getCacheDir(), "scan.lock"));
13031
+ if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join33(getCacheDir(), "scan.lock"));
12438
13032
  });
12439
13033
  program.hook("postAction", async () => {
12440
13034
  await releaseScan?.();
12441
13035
  releaseScan = void 0;
12442
13036
  stopParentWatch();
13037
+ await telemetry?.capture("command_finished", telemetryCommand, !process.exitCode);
13038
+ await telemetry?.shutdown();
12443
13039
  });
12444
13040
  if (process.argv.length <= 2 && process.platform === "darwin") {
12445
13041
  const isTTY = process.stdin.isTTY && process.stdout.isTTY;
@@ -12468,6 +13064,9 @@ if (process.argv.length <= 2 && process.platform === "darwin") {
12468
13064
  program.parseAsync().catch(async (error) => {
12469
13065
  await releaseScan?.();
12470
13066
  stopParentWatch();
13067
+ await telemetry?.error(error);
13068
+ await telemetry?.capture("command_finished", telemetryCommand, false);
13069
+ await telemetry?.shutdown();
12471
13070
  console.error(error instanceof Error ? error.message : String(error));
12472
13071
  process.exitCode = 1;
12473
13072
  });