exe-watcher 0.2.69 → 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/README.md +29 -3
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/dist/cli.js +1055 -341
- package/package.json +5 -3
- package/vendor/odd-core-analytics/LICENSE +21 -0
- package/vendor/odd-core-analytics/PROVENANCE.json +28 -0
- package/vendor/odd-core-analytics/README.md +196 -0
- package/vendor/odd-core-analytics/dist/privacy/client.d.ts +58 -0
- package/vendor/odd-core-analytics/dist/privacy/client.js +195 -0
- package/vendor/odd-core-analytics/dist/privacy/index.d.ts +2 -0
- package/vendor/odd-core-analytics/dist/privacy/index.js +18 -0
- package/vendor/odd-core-analytics/dist/privacy/schema.d.ts +46 -0
- package/vendor/odd-core-analytics/dist/privacy/schema.js +82 -0
- package/vendor/odd-core-analytics/gateway/server.mjs +128 -0
- package/vendor/odd-core-analytics/package.json +13 -0
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
|
-
//
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
236
|
-
import { join as
|
|
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(
|
|
762
|
+
files.push(join6(sessionDir, entry.name));
|
|
408
763
|
} else if (entry.isDirectory() && entry.name === "subagents") {
|
|
409
|
-
const subEntries = await readdir(
|
|
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(
|
|
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(
|
|
787
|
+
allFiles.push(join6(dir, entry.name));
|
|
433
788
|
} else if (entry.isDirectory()) {
|
|
434
789
|
try {
|
|
435
|
-
const sessionFiles = await collectJsonlFiles(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2492
|
+
return join8(getCacheDir(), "litellm-pricing.json");
|
|
2138
2493
|
}
|
|
2139
2494
|
function getLastKnownGoodPath() {
|
|
2140
|
-
return
|
|
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
|
|
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
|
|
2564
|
+
await rename5(tmpPath, finalPath);
|
|
2210
2565
|
} catch (err) {
|
|
2211
2566
|
try {
|
|
2212
|
-
await
|
|
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
|
|
2587
|
+
await rename5(lkgTmp, lkgPath).catch(() => {
|
|
2233
2588
|
try {
|
|
2234
|
-
|
|
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
|
|
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
|
|
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
|
|
2550
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
2643
|
-
const release = await acquireFileLock(
|
|
2997
|
+
await mkdir8(dir, { recursive: true });
|
|
2998
|
+
const release = await acquireFileLock(join9(dir, "discovery-index.lock"));
|
|
2644
2999
|
try {
|
|
2645
|
-
const onDisk = await
|
|
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}.${
|
|
3008
|
+
const tmp = `${path}.${randomUUID2()}.tmp`;
|
|
2654
3009
|
try {
|
|
2655
|
-
await
|
|
2656
|
-
await
|
|
3010
|
+
await writeFile3(tmp, payload, { mode: 384 });
|
|
3011
|
+
await rename6(tmp, path);
|
|
2657
3012
|
} finally {
|
|
2658
|
-
await
|
|
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
|
|
3129
|
+
import { join as join10 } from "path";
|
|
2775
3130
|
function database() {
|
|
2776
3131
|
const directory = getCacheDir();
|
|
2777
|
-
const path =
|
|
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
|
|
2871
|
-
import { mkdir as
|
|
2872
|
-
import { join as
|
|
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
|
|
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 =
|
|
3275
|
+
const dir = join11(getCacheDir(), "parsed-files");
|
|
2921
3276
|
const key2 = createHash3("sha256").update(`${VERSION2}:${kind}:${path}`).digest("hex");
|
|
2922
|
-
const cachePath =
|
|
2923
|
-
const progressPath =
|
|
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 =
|
|
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
|
|
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
|
|
3342
|
+
await mkdir9(dir, { recursive: true });
|
|
2988
3343
|
const encoded = await compress(Buffer.from(payload));
|
|
2989
|
-
const release = await acquireFileLock(
|
|
3344
|
+
const release = await acquireFileLock(join11(dir, "writes.lock"));
|
|
2990
3345
|
try {
|
|
2991
|
-
const quotaPath =
|
|
2992
|
-
const quota = await
|
|
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}.${
|
|
3350
|
+
const tmp = `${quotaPath}.${randomUUID3()}.tmp`;
|
|
2996
3351
|
try {
|
|
2997
|
-
await
|
|
2998
|
-
await
|
|
3352
|
+
await writeFile4(tmp, String(bytes), { mode: 384 });
|
|
3353
|
+
await rename7(tmp, quotaPath);
|
|
2999
3354
|
} finally {
|
|
3000
|
-
await
|
|
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
|
|
3365
|
+
await unlink6(join11(dir, name)).catch(() => {
|
|
3011
3366
|
});
|
|
3012
3367
|
continue;
|
|
3013
3368
|
}
|
|
3014
|
-
const p =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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}.${
|
|
3402
|
+
const tmp = `${cachePath}.${randomUUID3()}.tmp`;
|
|
3048
3403
|
try {
|
|
3049
3404
|
await writeQuota(nextSize);
|
|
3050
|
-
await
|
|
3051
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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"] ||
|
|
3624
|
+
return process.env["CLAUDE_CONFIG_DIR"] || join12(homedir4(), ".claude");
|
|
3270
3625
|
}
|
|
3271
3626
|
function getDesktopSessionsDir() {
|
|
3272
|
-
if (process.platform === "darwin") return
|
|
3273
|
-
if (process.platform === "win32") return
|
|
3274
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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"] ??
|
|
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 =
|
|
3753
|
+
const sessionsDir = join13(codexDir, "sessions");
|
|
3399
3754
|
const sources = [];
|
|
3400
|
-
const archivedDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ??
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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 ??
|
|
4202
|
+
return override ?? join15(homedir7(), ".pi", "agent", "sessions");
|
|
3848
4203
|
}
|
|
3849
4204
|
function getOmpSessionsDir(override) {
|
|
3850
|
-
return override ??
|
|
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 =
|
|
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 =
|
|
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
|
|
4040
|
-
import { basename as basename7, dirname as
|
|
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"] ??
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
4092
|
-
const agent = basename7(
|
|
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
|
|
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
|
|
4526
|
+
import { join as join17 } from "path";
|
|
4172
4527
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
4173
4528
|
function getCachePath2() {
|
|
4174
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4605
|
+
return join18(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4251
4606
|
}
|
|
4252
4607
|
if (process.platform === "win32") {
|
|
4253
|
-
return
|
|
4608
|
+
return join18(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
4254
4609
|
}
|
|
4255
|
-
return
|
|
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
|
|
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"] ??
|
|
4505
|
-
return
|
|
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) =>
|
|
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
|
|
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
|
|
5101
|
+
return join20(homedir11(), ".cursor");
|
|
4747
5102
|
}
|
|
4748
5103
|
function getProjectsDir(baseDir) {
|
|
4749
|
-
return
|
|
5104
|
+
return join20(baseDir, "projects");
|
|
4750
5105
|
}
|
|
4751
5106
|
function getAttributionDbPath(baseDir) {
|
|
4752
|
-
return
|
|
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 =
|
|
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 =
|
|
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
|
|
5333
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5726
|
+
await rename9(tempPath, finalPath);
|
|
5372
5727
|
} catch (err) {
|
|
5373
5728
|
try {
|
|
5374
|
-
await
|
|
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
|
|
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) =>
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
6136
|
-
import { homedir as
|
|
6137
|
-
import { basename, join as
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
6879
|
+
const stagingDir = await mkdtemp(join4(tmpdir(), "exe-watcher-menubar-"));
|
|
6347
6880
|
try {
|
|
6348
|
-
const archivePath =
|
|
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 =
|
|
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
|
|
6360
|
-
const backupPath = alreadyInstalled ?
|
|
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
|
|
6395
|
-
import { dirname as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7005
|
+
await rename4(tmpPath, finalPath);
|
|
6473
7006
|
} catch (err) {
|
|
6474
7007
|
try {
|
|
6475
|
-
await
|
|
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(
|
|
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(
|
|
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
|
|
6768
|
-
await
|
|
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
|
|
6773
|
-
await
|
|
6774
|
-
await
|
|
6775
|
-
await
|
|
6776
|
-
await
|
|
6777
|
-
await
|
|
6778
|
-
await
|
|
6779
|
-
await
|
|
6780
|
-
await
|
|
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
|
|
6805
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
7030
|
-
import { join as
|
|
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
|
|
7569
|
+
return join24(getCacheDir(), DAILY_CACHE_FILENAME);
|
|
7037
7570
|
}
|
|
7038
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7649
|
+
await rename10(tempPath, finalPath);
|
|
7117
7650
|
} catch (err) {
|
|
7118
7651
|
try {
|
|
7119
|
-
await
|
|
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
|
|
7163
|
-
import { createHash as createHash7, randomUUID as
|
|
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
|
|
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
|
|
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) =>
|
|
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"] ||
|
|
7244
|
-
const path =
|
|
7245
|
-
const savedPath =
|
|
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
|
|
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
|
|
7274
|
-
await
|
|
7275
|
-
const tmp = `${savedPath}.${
|
|
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
|
|
7278
|
-
await
|
|
7810
|
+
await writeFile5(tmp, payload, { mode: 384 });
|
|
7811
|
+
await rename11(tmp, savedPath);
|
|
7279
7812
|
} catch {
|
|
7280
7813
|
} finally {
|
|
7281
|
-
await
|
|
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
|
|
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) =>
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
7694
|
-
|
|
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(
|
|
7698
|
-
configPaths.push(
|
|
7699
|
-
configPaths.push(
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
8320
|
-
import { stat as stat18, writeFile as
|
|
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
|
|
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 =
|
|
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}-${
|
|
8420
|
-
await
|
|
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
|
|
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
|
-
|
|
8451
|
-
|
|
8983
|
+
join29(home, ".claude", "settings.json"),
|
|
8984
|
+
join29(home, ".claude", "settings.local.json")
|
|
8452
8985
|
];
|
|
8453
8986
|
if (projectPath) {
|
|
8454
|
-
configPaths.push(
|
|
8455
|
-
configPaths.push(
|
|
8456
|
-
configPaths.push(
|
|
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 = [
|
|
8474
|
-
if (projectPath) dirs.push(
|
|
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 =
|
|
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:
|
|
9027
|
+
{ path: join29(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
|
|
8495
9028
|
];
|
|
8496
9029
|
if (projectPath) {
|
|
8497
|
-
paths.push({ path:
|
|
8498
|
-
paths.push({ path:
|
|
8499
|
-
paths.push({ path:
|
|
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(
|
|
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,22 +9760,94 @@ function owlFrame(tick) {
|
|
|
9227
9760
|
}
|
|
9228
9761
|
|
|
9229
9762
|
// src/dashboard.tsx
|
|
9230
|
-
import { join as
|
|
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
|
|
9237
|
-
import { mkdir as
|
|
9238
|
-
import { basename as basename11, dirname as
|
|
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
|
|
|
9242
9775
|
// src/time-tracking.ts
|
|
9243
9776
|
import { createHash as createHash8 } from "crypto";
|
|
9777
|
+
|
|
9778
|
+
// src/personal-activity.ts
|
|
9779
|
+
init_cache_dir();
|
|
9780
|
+
import { openSync as openSync2, readSync as readSync2, closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2 } from "fs";
|
|
9781
|
+
import { join as join30 } from "path";
|
|
9782
|
+
var ACTIVITY_LIMIT = 4 * 1024 ** 2;
|
|
9783
|
+
function readActivityHistory() {
|
|
9784
|
+
let fd;
|
|
9785
|
+
try {
|
|
9786
|
+
fd = openSync2(join30(getCacheDir(), "personal-activity-v1.json"), constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
|
|
9787
|
+
const stat20 = fstatSync2(fd);
|
|
9788
|
+
if (!stat20.isFile() || stat20.size > ACTIVITY_LIMIT) return void 0;
|
|
9789
|
+
const buffer = Buffer.alloc(stat20.size);
|
|
9790
|
+
if (readSync2(fd, buffer, 0, buffer.length, 0) !== buffer.length) return void 0;
|
|
9791
|
+
const x = JSON.parse(buffer.toString("utf8"));
|
|
9792
|
+
if (x.version !== 1 || typeof x.enabled !== "boolean" || typeof x.startedAt !== "string" || typeof x.updatedAt !== "string" || !Array.isArray(x.days) || x.days.length > 36600 || !Number.isFinite(Date.parse(x.startedAt)) || !Number.isFinite(Date.parse(x.updatedAt))) return void 0;
|
|
9793
|
+
let end = 0;
|
|
9794
|
+
for (const day of x.days) {
|
|
9795
|
+
if (!Number.isFinite(day.start) || !Number.isFinite(day.end) || day.start < end || day.end <= day.start || day.end - day.start > 26 * 36e5) return void 0;
|
|
9796
|
+
let total = 0;
|
|
9797
|
+
for (const map of [day.active, day.contextual]) {
|
|
9798
|
+
if (!map || typeof map !== "object" || Array.isArray(map) || Object.keys(map).length > 32) return void 0;
|
|
9799
|
+
for (const value of Object.values(map)) {
|
|
9800
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
9801
|
+
total += value;
|
|
9802
|
+
}
|
|
9803
|
+
}
|
|
9804
|
+
if (total > day.end - day.start) return void 0;
|
|
9805
|
+
end = day.end;
|
|
9806
|
+
}
|
|
9807
|
+
return x;
|
|
9808
|
+
} catch {
|
|
9809
|
+
return void 0;
|
|
9810
|
+
} finally {
|
|
9811
|
+
if (fd !== void 0) closeSync2(fd);
|
|
9812
|
+
}
|
|
9813
|
+
}
|
|
9814
|
+
function summarizeActivity(history, range, provider = "all") {
|
|
9815
|
+
if (!history) return void 0;
|
|
9816
|
+
let activeMs = 0, contextualMs = 0;
|
|
9817
|
+
for (const day of history.days) {
|
|
9818
|
+
const overlap = Math.max(0, Math.min(day.end, range.end.getTime()) - Math.max(day.start, range.start.getTime()));
|
|
9819
|
+
if (!overlap) continue;
|
|
9820
|
+
if (range.start.getTime() > day.start || range.end.getTime() < Math.min(day.end, Date.parse(history.updatedAt))) continue;
|
|
9821
|
+
const sum2 = (map) => provider === "all" ? Object.values(map).reduce((a, b) => a + b, 0) : Object.hasOwn(map, provider) ? map[provider] : 0;
|
|
9822
|
+
activeMs += sum2(day.active);
|
|
9823
|
+
contextualMs += sum2(day.contextual);
|
|
9824
|
+
}
|
|
9825
|
+
return { activeMs, contextualMs, startedAt: history.startedAt, updatedAt: history.updatedAt, enabled: history.enabled };
|
|
9826
|
+
}
|
|
9827
|
+
|
|
9828
|
+
// src/time-tracking.ts
|
|
9244
9829
|
var TIME_HISTORY_START = (/* @__PURE__ */ new Date("2025-07-01T00:00:00")).getTime();
|
|
9245
9830
|
var TIME_PROVIDERS = ["claude", "codex", "cursor", "cursor-agent", "copilot", "opencode", "kimi", "pi", "omp"];
|
|
9831
|
+
function personalTimeMs(time) {
|
|
9832
|
+
if (!time) return null;
|
|
9833
|
+
if (time.personalTimeVersion !== 1 && !time.sessionEstimate) return time.userActiveMs;
|
|
9834
|
+
if (!time.sessionEstimate && !time.appActivity) return null;
|
|
9835
|
+
return (time.sessionEstimate?.milliseconds ?? 0) + (time.appActivity?.activeMs ?? 0) + (time.appActivity?.contextualMs ?? 0);
|
|
9836
|
+
}
|
|
9837
|
+
function estimateSessions(points, range, through = range.end.getTime()) {
|
|
9838
|
+
const sorted = [...new Set(points.filter(Number.isFinite))].sort((a, b) => a - b);
|
|
9839
|
+
const end = Math.min(through, range.end.getTime(), Date.now());
|
|
9840
|
+
const start = Math.max(range.start.getTime(), TIME_HISTORY_START);
|
|
9841
|
+
let milliseconds = 0, lowerMs = 0, upperMs = 0;
|
|
9842
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
9843
|
+
const a = sorted[i - 1], b = sorted[i], gap = b - a;
|
|
9844
|
+
const duration = Math.max(0, Math.min(b, end) - Math.max(a, start));
|
|
9845
|
+
if (gap <= 30 * 6e4) upperMs += duration;
|
|
9846
|
+
if (gap <= 15 * 6e4) milliseconds += duration;
|
|
9847
|
+
if (gap <= 10 * 6e4) lowerMs += duration;
|
|
9848
|
+
}
|
|
9849
|
+
return upperMs > 0 ? { milliseconds, lowerMs, upperMs, method: "main-chat-gaps-v1", through: new Date(end).toISOString() } : void 0;
|
|
9850
|
+
}
|
|
9246
9851
|
function timestamp(value) {
|
|
9247
9852
|
const n = typeof value === "number" ? value < 1e11 ? value * 1e3 : value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
9248
9853
|
return Number.isFinite(n) && n >= TIME_HISTORY_START && n <= Date.now() + 864e5 ? n : void 0;
|
|
@@ -9394,7 +9999,7 @@ function unionIntervals(intervals, range) {
|
|
|
9394
9999
|
return out;
|
|
9395
10000
|
}
|
|
9396
10001
|
var sum = (rows2) => rows2.reduce((n, [a, b]) => n + b - a, 0);
|
|
9397
|
-
function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
10002
|
+
function summarizeTime(index, range, provider = "all", include, exclude, activity) {
|
|
9398
10003
|
const matches = (name, terms) => terms?.some((t) => name.toLowerCase().includes(t.toLowerCase()));
|
|
9399
10004
|
const files = Object.values(index.files).filter((f) => (provider === "all" || f.provider === provider) && (!include?.length || matches(f.project, include)) && !matches(f.project, exclude));
|
|
9400
10005
|
const agents = /* @__PURE__ */ new Map(), estimatedAgents = /* @__PURE__ */ new Map(), user = [];
|
|
@@ -9414,9 +10019,16 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9414
10019
|
states.set(file, copies);
|
|
9415
10020
|
}
|
|
9416
10021
|
const consumed = /* @__PURE__ */ new Set();
|
|
10022
|
+
const mainTurns = [];
|
|
10023
|
+
const recentMainTurns = /* @__PURE__ */ new Map();
|
|
9417
10024
|
const byProvider = /* @__PURE__ */ new Map();
|
|
9418
10025
|
let pendingFiles = 0;
|
|
9419
10026
|
for (const file of files) {
|
|
10027
|
+
for (const original of [file.state, ...Object.values(file.state.children ?? {})]) {
|
|
10028
|
+
if (!original.child && original.pending !== void 0 && original.pending <= Date.now()) {
|
|
10029
|
+
recentMainTurns.set(file.provider, Math.max(recentMainTurns.get(file.provider) ?? 0, original.pending));
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
9420
10032
|
if (file.offset < file.size || file.unavailable) pendingFiles++;
|
|
9421
10033
|
const row = byProvider.get(file.provider) ?? { files: 0, timedFiles: 0, agents: /* @__PURE__ */ new Map() };
|
|
9422
10034
|
row.files++;
|
|
@@ -9431,6 +10043,12 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9431
10043
|
return true;
|
|
9432
10044
|
});
|
|
9433
10045
|
const ai = unionIntervals(unique, range);
|
|
10046
|
+
if (!state.child) {
|
|
10047
|
+
for (const interval2 of unique) {
|
|
10048
|
+
mainTurns.push(interval2[0]);
|
|
10049
|
+
if (interval2[0] <= Date.now()) recentMainTurns.set(file.provider, Math.max(recentMainTurns.get(file.provider) ?? 0, interval2[0]));
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
9434
10052
|
const inferred = estimatedAgents.get(state.agent) ?? [];
|
|
9435
10053
|
inferred.push(...unique.filter((interval2) => interval2[2]));
|
|
9436
10054
|
estimatedAgents.set(state.agent, inferred);
|
|
@@ -9447,6 +10065,9 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9447
10065
|
const all = [...agents.values()].flatMap((v) => unionIntervals(v, range));
|
|
9448
10066
|
const estimated = [...estimatedAgents.values()].flatMap((v) => unionIntervals(v, range));
|
|
9449
10067
|
const busy = unionIntervals(all, range), human = unionIntervals(user, range);
|
|
10068
|
+
const cutover = activity ? Date.parse(activity.startedAt) : range.end.getTime();
|
|
10069
|
+
const sessionEstimate = estimateSessions(mainTurns, range, cutover);
|
|
10070
|
+
const appActivity = include?.length || exclude?.length ? void 0 : summarizeActivity(activity, range, provider);
|
|
9450
10071
|
const pending = files.filter((f) => f.offset < f.size || f.unavailable);
|
|
9451
10072
|
const waitingFiles = pending.filter((f) => f.waitingForAppend && !f.unavailable).length;
|
|
9452
10073
|
const recovery = {
|
|
@@ -9464,6 +10085,9 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9464
10085
|
elapsedBusyMs: busy.length ? sum(busy) : null,
|
|
9465
10086
|
userActiveMs: human.length ? sum(human) : null,
|
|
9466
10087
|
estimatedAiMs: sum(estimated),
|
|
10088
|
+
personalTimeVersion: 1,
|
|
10089
|
+
sessionEstimate,
|
|
10090
|
+
appActivity,
|
|
9467
10091
|
coverage: all.length ? "partial" : "missing",
|
|
9468
10092
|
pendingFiles,
|
|
9469
10093
|
indexedFiles: files.length,
|
|
@@ -9472,11 +10096,13 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
|
|
|
9472
10096
|
providers: (provider === "all" ? TIME_PROVIDERS : [provider]).map((name) => {
|
|
9473
10097
|
const row = byProvider.get(name);
|
|
9474
10098
|
const duration = row ? [...row.agents.values()].reduce((n, a) => n + sum(unionIntervals(a, range)), 0) : 0;
|
|
9475
|
-
return { name, files: row?.files ?? 0, timedFiles: row?.timedFiles ?? 0, aiRunningMs: duration || null, status: duration ? "partial" : "missing" };
|
|
10099
|
+
return { name, files: row?.files ?? 0, timedFiles: row?.timedFiles ?? 0, aiRunningMs: duration || null, status: duration ? "partial" : "missing", recentMainTurnAt: recentMainTurns.get(name) };
|
|
9476
10100
|
}),
|
|
9477
10101
|
notes: [
|
|
9478
10102
|
"Retained local records only, since July 1, 2025. Missing history is unknown, not zero. Run time can include tools and waiting.",
|
|
9479
|
-
"
|
|
10103
|
+
"Legacy short reply gaps estimate reply-to-next-prompt gaps up to 5 minutes, merged across chats. Longer gaps and subagents are excluded; unidentified automation may be included. It does not measure attention.",
|
|
10104
|
+
...sessionEstimate ? ["Historical session time uses gaps between main-chat turn starts: 15-minute continuity, with a 10\u201330-minute sensitivity range. Overlap counts once; subagents, isolated turns and long gaps are excluded. This includes some AI waiting, may include main-chat automation, and cannot measure attention."] : [],
|
|
10105
|
+
...appActivity ? ["App activity uses foreground app and idle metadata from this Mac. Terminal/editor time needs a recent matching AI session and is separately marked as contextual. Browsers and unsupported apps are unknown. Daily totals have no project attribution; tracking gaps remain unknown."] : [],
|
|
9480
10106
|
...estimated.length ? ["Some AI time is inferred from prompt/response timestamps, limited to spans up to 30 minutes. Longer unmeasured gaps stay unknown."] : [],
|
|
9481
10107
|
...recovery.runnableFiles ? ["Timing recovery is in progress. Queued timing work continues in bounded steps; usage costs are unaffected."] : [],
|
|
9482
10108
|
...waitingFiles ? ["Some logs end mid-record. Their incomplete records remain unknown until more data is saved."] : [],
|
|
@@ -9586,12 +10212,12 @@ var MAX_DISK2 = 16 * 1024 ** 2;
|
|
|
9586
10212
|
var MAX_FILES = 2e4;
|
|
9587
10213
|
var BLOCK = 2 * 1024 ** 2;
|
|
9588
10214
|
var adapterVersion = (provider) => provider === "opencode" ? 2 : 1;
|
|
9589
|
-
var indexPath2 = () =>
|
|
10215
|
+
var indexPath2 = () => join31(getCacheDir(), "time-index-v1.json.gz");
|
|
9590
10216
|
var empty = () => ({ version: 1, files: /* @__PURE__ */ Object.create(null), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), warnings: [] });
|
|
9591
10217
|
async function readTimeIndex() {
|
|
9592
10218
|
try {
|
|
9593
10219
|
if ((await stat19(indexPath2())).size > MAX_DISK2) throw new Error("Timing index exceeds its disk limit");
|
|
9594
|
-
const value = JSON.parse((await decompress2(await
|
|
10220
|
+
const value = JSON.parse((await decompress2(await readFile15(indexPath2()), { maxOutputLength: MAX_INDEX })).toString("utf8"));
|
|
9595
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");
|
|
9596
10222
|
for (const row of Object.values(value.files)) {
|
|
9597
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");
|
|
@@ -9610,8 +10236,8 @@ async function save(index) {
|
|
|
9610
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.");
|
|
9611
10237
|
const encoded = await compress2(Buffer.from(data));
|
|
9612
10238
|
if (encoded.length > MAX_DISK2) throw new Error("Timing index reached its 16 MiB compressed disk limit.");
|
|
9613
|
-
const path = indexPath2(), temp = `${path}.${
|
|
9614
|
-
await
|
|
10239
|
+
const path = indexPath2(), temp = `${path}.${randomUUID6()}.tmp`;
|
|
10240
|
+
await mkdir14(getCacheDir(), { recursive: true });
|
|
9615
10241
|
try {
|
|
9616
10242
|
const handle = await open9(temp, "wx", 384);
|
|
9617
10243
|
try {
|
|
@@ -9620,9 +10246,9 @@ async function save(index) {
|
|
|
9620
10246
|
} finally {
|
|
9621
10247
|
await handle.close();
|
|
9622
10248
|
}
|
|
9623
|
-
await
|
|
10249
|
+
await rename12(temp, path);
|
|
9624
10250
|
} finally {
|
|
9625
|
-
await
|
|
10251
|
+
await unlink10(temp).catch(() => {
|
|
9626
10252
|
});
|
|
9627
10253
|
}
|
|
9628
10254
|
}
|
|
@@ -9649,11 +10275,11 @@ async function expandSources(sources, warnings) {
|
|
|
9649
10275
|
continue;
|
|
9650
10276
|
}
|
|
9651
10277
|
for (const entry of await readdir15(source.path, { withFileTypes: true }).catch(() => [])) {
|
|
9652
|
-
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
10278
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join31(source.path, entry.name) });
|
|
9653
10279
|
else if (entry.isDirectory()) {
|
|
9654
|
-
const folder =
|
|
10280
|
+
const folder = join31(source.path, entry.name, "subagents");
|
|
9655
10281
|
for (const agent of await readdir15(folder, { withFileTypes: true }).catch(() => [])) {
|
|
9656
|
-
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path:
|
|
10282
|
+
if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join31(folder, agent.name) });
|
|
9657
10283
|
}
|
|
9658
10284
|
}
|
|
9659
10285
|
}
|
|
@@ -9673,9 +10299,9 @@ async function anchor(path, offset) {
|
|
|
9673
10299
|
}
|
|
9674
10300
|
}
|
|
9675
10301
|
function fresh(source, identity, size, mtime, ctime) {
|
|
9676
|
-
const child = source.path.includes("/subagents/") || basename11(source.path).startsWith("agent-") || source.provider === "kimi" && basename11(
|
|
9677
|
-
const kimiSession = source.path.split("/").find((part) => part.startsWith("session_")) ?? basename11(
|
|
9678
|
-
const agent = source.provider === "copilot" ? basename11(
|
|
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")}`;
|
|
9679
10305
|
return {
|
|
9680
10306
|
provider: source.provider,
|
|
9681
10307
|
project: source.project,
|
|
@@ -9750,7 +10376,7 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9750
10376
|
let current;
|
|
9751
10377
|
let release;
|
|
9752
10378
|
try {
|
|
9753
|
-
release = await acquireFileLock(
|
|
10379
|
+
release = await acquireFileLock(join31(getCacheDir(), "time-index.lock"), 100);
|
|
9754
10380
|
} catch {
|
|
9755
10381
|
current = await readTimeIndex();
|
|
9756
10382
|
current.warnings.push("Another timing update is running; showing the saved timing snapshot.");
|
|
@@ -9853,12 +10479,13 @@ async function refreshTimeIndex(options = {}) {
|
|
|
9853
10479
|
return current;
|
|
9854
10480
|
}
|
|
9855
10481
|
async function getTimeReport(range, provider = "all", include, exclude, signal) {
|
|
10482
|
+
const activity = readActivityHistory();
|
|
9856
10483
|
try {
|
|
9857
|
-
return summarizeTime(await refreshTimeIndex({ signal }), range, provider, include, exclude);
|
|
10484
|
+
return summarizeTime(await refreshTimeIndex({ signal }), range, provider, include, exclude, activity);
|
|
9858
10485
|
} catch (error) {
|
|
9859
10486
|
const index = await readTimeIndex();
|
|
9860
10487
|
index.warnings.push(`Timing unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
9861
|
-
return summarizeTime(index, range, provider, include, exclude);
|
|
10488
|
+
return summarizeTime(index, range, provider, include, exclude, activity);
|
|
9862
10489
|
}
|
|
9863
10490
|
}
|
|
9864
10491
|
|
|
@@ -10572,8 +11199,25 @@ function TimePanel({ time, width }) {
|
|
|
10572
11199
|
] }),
|
|
10573
11200
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
10574
11201
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Your time \u2248 " }),
|
|
10575
|
-
formatDuration(time
|
|
10576
|
-
/* @__PURE__ */
|
|
11202
|
+
formatDuration(personalTimeMs(time)),
|
|
11203
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
11204
|
+
" \xB7 ",
|
|
11205
|
+
time?.personalTimeVersion === 1 || time?.sessionEstimate ? "historical sessions + tracked app activity" : "legacy short reply gaps"
|
|
11206
|
+
] })
|
|
11207
|
+
] }),
|
|
11208
|
+
time?.sessionEstimate && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
11209
|
+
"Historical range: ",
|
|
11210
|
+
formatDuration(time.sessionEstimate.lowerMs),
|
|
11211
|
+
" \u2013 ",
|
|
11212
|
+
formatDuration(time.sessionEstimate.upperMs),
|
|
11213
|
+
" (10\u201330 min session gaps)"
|
|
11214
|
+
] }),
|
|
11215
|
+
time?.appActivity && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
11216
|
+
"App activity: ",
|
|
11217
|
+
formatDuration(time.appActivity.activeMs + time.appActivity.contextualMs),
|
|
11218
|
+
" \xB7 ",
|
|
11219
|
+
formatDuration(time.appActivity.contextualMs),
|
|
11220
|
+
" inferred terminal/editor context"
|
|
10577
11221
|
] }),
|
|
10578
11222
|
/* @__PURE__ */ jsxs2(Text2, { color: ORANGE3, children: [
|
|
10579
11223
|
time?.aiRunningMs != null ? "Partial history" : "Timing unknown",
|
|
@@ -10697,11 +11341,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
|
|
|
10697
11341
|
useEffect2(() => {
|
|
10698
11342
|
let cancelled = false;
|
|
10699
11343
|
async function loadBudgets() {
|
|
10700
|
-
const claudeDir =
|
|
11344
|
+
const claudeDir = join32(homedir16(), ".claude", "projects");
|
|
10701
11345
|
const budgets = /* @__PURE__ */ new Map();
|
|
10702
11346
|
for (const project of projects.slice(0, 8)) {
|
|
10703
11347
|
if (cancelled) return;
|
|
10704
|
-
const cwd = await discoverProjectCwd(
|
|
11348
|
+
const cwd = await discoverProjectCwd(join32(claudeDir, project.project));
|
|
10705
11349
|
if (!cwd) continue;
|
|
10706
11350
|
budgets.set(project.project, await estimateContextBudget(cwd));
|
|
10707
11351
|
}
|
|
@@ -11246,8 +11890,8 @@ function watchMenubarParent(parentPid, runtime = live) {
|
|
|
11246
11890
|
}
|
|
11247
11891
|
|
|
11248
11892
|
// src/time-snapshots.ts
|
|
11249
|
-
function buildTimeSnapshots(index, ranges, provider = "all", include, exclude) {
|
|
11250
|
-
const views = (scope) => Object.fromEntries(Object.entries(ranges).map(([key2, range]) => [key2, summarizeTime(index, range, scope, include, exclude)]));
|
|
11893
|
+
function buildTimeSnapshots(index, ranges, provider = "all", include, exclude, activity) {
|
|
11894
|
+
const views = (scope) => Object.fromEntries(Object.entries(ranges).map(([key2, range]) => [key2, summarizeTime(index, range, scope, include, exclude, activity)]));
|
|
11251
11895
|
const periods = views(provider), allPeriods = provider === "all" ? void 0 : views("all");
|
|
11252
11896
|
return { generated: (/* @__PURE__ */ new Date()).toISOString(), provider, periods, allPeriods, recovery: (allPeriods ?? periods)["all"].recovery };
|
|
11253
11897
|
}
|
|
@@ -11368,9 +12012,32 @@ async function runJsonReport(period, provider, project, exclude) {
|
|
|
11368
12012
|
}
|
|
11369
12013
|
console.log(JSON.stringify({ ...report, pricing: getPricingMetadata() }, null, 2));
|
|
11370
12014
|
}
|
|
12015
|
+
var telemetry;
|
|
12016
|
+
var telemetryCommand = "unknown";
|
|
11371
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");
|
|
11372
12018
|
program.hook("preAction", async (thisCommand, command) => {
|
|
11373
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);
|
|
11374
12041
|
setModelAliases(config.modelAliases ?? {});
|
|
11375
12042
|
configuredHistoryStart = parseHistoryStartDate(config.historyStartDate);
|
|
11376
12043
|
if (thisCommand.opts().verbose) {
|
|
@@ -11875,12 +12542,12 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11875
12542
|
`);
|
|
11876
12543
|
}
|
|
11877
12544
|
}
|
|
11878
|
-
const exeOsDir =
|
|
12545
|
+
const exeOsDir = join33(homedir17(), ".exe-os");
|
|
11879
12546
|
const exeOsDetected = existsSync8(exeOsDir);
|
|
11880
12547
|
let agentStats = null;
|
|
11881
12548
|
let statsFileAge = null;
|
|
11882
12549
|
try {
|
|
11883
|
-
const statsPath =
|
|
12550
|
+
const statsPath = join33(exeOsDir, "agent-stats.json");
|
|
11884
12551
|
if (existsSync8(statsPath)) {
|
|
11885
12552
|
statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
|
|
11886
12553
|
const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
|
|
@@ -11940,9 +12607,10 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
11940
12607
|
warnings
|
|
11941
12608
|
) : void 0;
|
|
11942
12609
|
const timeIndex = await refreshTimeIndex();
|
|
11943
|
-
const
|
|
12610
|
+
const activity = readActivityHistory();
|
|
12611
|
+
const time = summarizeTime(timeIndex, getDateRange2(opts.period, now).range, pf, opts.project, opts.exclude, activity);
|
|
11944
12612
|
if (periodSnapshots) for (const [key2, snapshot] of Object.entries(periodSnapshots)) {
|
|
11945
|
-
snapshot.time = summarizeTime(timeIndex, getDateRange2(key2, now).range, pf, opts.project, opts.exclude);
|
|
12613
|
+
snapshot.time = summarizeTime(timeIndex, getDateRange2(key2, now).range, pf, opts.project, opts.exclude, activity);
|
|
11946
12614
|
}
|
|
11947
12615
|
const json = JSON.stringify({
|
|
11948
12616
|
...payload,
|
|
@@ -12250,20 +12918,23 @@ program.command("plan [action] [id]").description("Show or configure a subscript
|
|
|
12250
12918
|
});
|
|
12251
12919
|
program.command("time").description("AI running time, elapsed busy time and estimated interaction time since July 1, 2025").option("-p, --period <period>", "today, week, 30days, month, all", "all").option("--provider <provider>", "Filter by coding client", "all").option("--project <names...>", "Include projects by name").option("--exclude <names...>", "Exclude projects by name").option("--recover", "Advance one bounded historical recovery pass; safe to resume").option("--snapshots", "Return timing-only period snapshots for the menu bar").option("--format <format>", "text or json", "text").action(async (opts) => {
|
|
12252
12920
|
const index = await refreshTimeIndex({ history: opts.recover === true });
|
|
12921
|
+
const activity = readActivityHistory();
|
|
12253
12922
|
if (opts.snapshots) {
|
|
12254
12923
|
const now = /* @__PURE__ */ new Date();
|
|
12255
12924
|
const ranges = Object.fromEntries(["today", "week", "30days", "month", "all"].map((key2) => [key2, getDateRange2(key2, now).range]));
|
|
12256
|
-
console.log(JSON.stringify(buildTimeSnapshots(index, ranges, opts.provider, opts.project, opts.exclude)));
|
|
12925
|
+
console.log(JSON.stringify(buildTimeSnapshots(index, ranges, opts.provider, opts.project, opts.exclude, activity)));
|
|
12257
12926
|
return;
|
|
12258
12927
|
}
|
|
12259
|
-
const time = summarizeTime(index, getDateRange2(opts.period).range, opts.provider, opts.project, opts.exclude);
|
|
12928
|
+
const time = summarizeTime(index, getDateRange2(opts.period).range, opts.provider, opts.project, opts.exclude, activity);
|
|
12260
12929
|
if (opts.format === "json") console.log(JSON.stringify(time));
|
|
12261
12930
|
else {
|
|
12262
12931
|
console.log(`
|
|
12263
12932
|
AI time \xB7 ${getDateRange2(opts.period).label}`);
|
|
12264
12933
|
console.log(` AI running: ${formatDuration(time.aiRunningMs)} (added across agents)`);
|
|
12265
12934
|
console.log(` Elapsed busy: ${formatDuration(time.elapsedBusyMs)} (overlap counted once)`);
|
|
12266
|
-
console.log(`
|
|
12935
|
+
if (time.sessionEstimate) console.log(` Past sessions \u2248 ${formatDuration(time.sessionEstimate.milliseconds)} (10\u201330 min range: ${formatDuration(time.sessionEstimate.lowerMs)} \u2013 ${formatDuration(time.sessionEstimate.upperMs)})`);
|
|
12936
|
+
if (time.appActivity) console.log(` App activity: ${formatDuration(time.appActivity.activeMs + time.appActivity.contextualMs)} (${formatDuration(time.appActivity.contextualMs)} inferred terminal/editor context; tracking ${time.appActivity.enabled ? "on" : "off"})`);
|
|
12937
|
+
console.log(` Short reply gaps: ${formatDuration(time.userActiveMs)} (legacy interaction estimate)`);
|
|
12267
12938
|
console.log(` Partial history \xB7 ${time.indexedFiles} files \xB7 ${time.pendingFiles} awaiting recovery`);
|
|
12268
12939
|
for (const row of time.providers) console.log(` ${row.name}: ${formatDuration(row.aiRunningMs)} \xB7 ${row.timedFiles}/${row.files} files with timing in this period`);
|
|
12269
12940
|
for (const note of time.notes) console.log(` ${note}`);
|
|
@@ -12282,9 +12953,9 @@ program.command("compare").description("Compare two AI models side-by-side").opt
|
|
|
12282
12953
|
await renderCompare(range, opts.provider);
|
|
12283
12954
|
});
|
|
12284
12955
|
program.command("agent-status").description("Diagnose exe-os agent data pipeline").action(() => {
|
|
12285
|
-
const exeOsDir =
|
|
12286
|
-
const statsPath =
|
|
12287
|
-
const pidPath =
|
|
12956
|
+
const exeOsDir = join33(homedir17(), ".exe-os");
|
|
12957
|
+
const statsPath = join33(exeOsDir, "agent-stats.json");
|
|
12958
|
+
const pidPath = join33(exeOsDir, "exed.pid");
|
|
12288
12959
|
const dirExists = existsSync8(exeOsDir);
|
|
12289
12960
|
const statsExists = dirExists && existsSync8(statsPath);
|
|
12290
12961
|
const pidExists = dirExists && existsSync8(pidPath);
|
|
@@ -12316,15 +12987,55 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
|
|
|
12316
12987
|
}
|
|
12317
12988
|
console.log(lines.join("\n"));
|
|
12318
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
|
+
});
|
|
12319
13028
|
var releaseScan;
|
|
12320
13029
|
var stopParentWatch = watchMenubarParent(Number(process.env["EXE_WATCHER_PARENT_PID"]));
|
|
12321
13030
|
program.hook("preAction", async (_root, command) => {
|
|
12322
|
-
if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(
|
|
13031
|
+
if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join33(getCacheDir(), "scan.lock"));
|
|
12323
13032
|
});
|
|
12324
13033
|
program.hook("postAction", async () => {
|
|
12325
13034
|
await releaseScan?.();
|
|
12326
13035
|
releaseScan = void 0;
|
|
12327
13036
|
stopParentWatch();
|
|
13037
|
+
await telemetry?.capture("command_finished", telemetryCommand, !process.exitCode);
|
|
13038
|
+
await telemetry?.shutdown();
|
|
12328
13039
|
});
|
|
12329
13040
|
if (process.argv.length <= 2 && process.platform === "darwin") {
|
|
12330
13041
|
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
@@ -12353,6 +13064,9 @@ if (process.argv.length <= 2 && process.platform === "darwin") {
|
|
|
12353
13064
|
program.parseAsync().catch(async (error) => {
|
|
12354
13065
|
await releaseScan?.();
|
|
12355
13066
|
stopParentWatch();
|
|
13067
|
+
await telemetry?.error(error);
|
|
13068
|
+
await telemetry?.capture("command_finished", telemetryCommand, false);
|
|
13069
|
+
await telemetry?.shutdown();
|
|
12356
13070
|
console.error(error instanceof Error ? error.message : String(error));
|
|
12357
13071
|
process.exitCode = 1;
|
|
12358
13072
|
});
|