codexmeter 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-_br3iqvd.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-DhDq9dI8.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexmeter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Local telemetry dashboard for Codex CLI usage",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"dev": "node dev-runner.js",
|
|
19
19
|
"build": "vite build",
|
|
20
|
+
"test": "node --test test/*.test.js",
|
|
20
21
|
"start": "node bin/codexmeter.js",
|
|
21
22
|
"preview": "npm run build && node bin/codexmeter.js",
|
|
22
23
|
"prepublishOnly": "npm run build"
|
package/server/export-replay.js
CHANGED
|
@@ -30,7 +30,7 @@ export function beginReplayCapture(replay, ingestId, bootstrapPayload) {
|
|
|
30
30
|
replay.events.push({
|
|
31
31
|
event: 'bootstrap',
|
|
32
32
|
at_ms: 0,
|
|
33
|
-
payload:
|
|
33
|
+
payload: bootstrapPayload,
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -40,7 +40,7 @@ export function recordReplayEvent(replay, event, payload) {
|
|
|
40
40
|
replay.events.push({
|
|
41
41
|
event,
|
|
42
42
|
at_ms: Math.max(0, Date.now() - replay.started_at_ms),
|
|
43
|
-
payload
|
|
43
|
+
payload,
|
|
44
44
|
});
|
|
45
45
|
if (event === 'complete') {
|
|
46
46
|
replay.active = false;
|
package/server/export-video.js
CHANGED
|
@@ -24,6 +24,7 @@ const EXPORT_TOTAL_DURATION_MS =
|
|
|
24
24
|
const EXPORT_TAIL_SOURCE_FRACTION = OVERVIEW_INGEST_ANIMATION.videoExport?.tailSourceFraction ?? 0.035;
|
|
25
25
|
const EXPORT_CRF = OVERVIEW_INGEST_ANIMATION.videoExport?.crf ?? 20;
|
|
26
26
|
const EXPORT_ENCODER_PRESET = OVERVIEW_INGEST_ANIMATION.videoExport?.encoderPreset ?? 'fast';
|
|
27
|
+
const EXPORT_JOB_TTL_MS = 10 * 60 * 1000;
|
|
27
28
|
let portableBrowserSupportCache = null;
|
|
28
29
|
const require = createRequire(import.meta.url);
|
|
29
30
|
|
|
@@ -83,19 +84,23 @@ export function createExportManager({ getReplay, getSettledEnvelope, getBaseUrl
|
|
|
83
84
|
install_portable_browser: Boolean(opts.installPortableBrowser),
|
|
84
85
|
portable_browser_dir: null,
|
|
85
86
|
portable_browser_executable: null,
|
|
87
|
+
cleanup_timer: null,
|
|
88
|
+
cleanup_at_ms: 0,
|
|
86
89
|
error: null,
|
|
87
90
|
};
|
|
88
91
|
|
|
89
92
|
jobs.set(jobId, job);
|
|
90
93
|
void runOverviewVideoJob(job, replay, getBaseUrl);
|
|
91
|
-
return
|
|
94
|
+
return createJobSummary(job);
|
|
92
95
|
},
|
|
93
96
|
|
|
94
97
|
getJob(jobId) {
|
|
98
|
+
pruneExpiredJobs();
|
|
95
99
|
return jobs.get(jobId) || null;
|
|
96
100
|
},
|
|
97
101
|
|
|
98
102
|
listJobs() {
|
|
103
|
+
pruneExpiredJobs();
|
|
99
104
|
return [...jobs.values()];
|
|
100
105
|
},
|
|
101
106
|
|
|
@@ -123,26 +128,9 @@ export function createExportManager({ getReplay, getSettledEnvelope, getBaseUrl
|
|
|
123
128
|
};
|
|
124
129
|
},
|
|
125
130
|
|
|
126
|
-
|
|
131
|
+
pruneExpiredJobs,
|
|
127
132
|
};
|
|
128
133
|
|
|
129
|
-
function sanitizeJob(job) {
|
|
130
|
-
if (!job) return null;
|
|
131
|
-
return {
|
|
132
|
-
id: job.id,
|
|
133
|
-
type: job.type,
|
|
134
|
-
status: job.status,
|
|
135
|
-
phase: job.phase,
|
|
136
|
-
progress: job.progress,
|
|
137
|
-
created_at: job.created_at,
|
|
138
|
-
updated_at: job.updated_at,
|
|
139
|
-
replay_ingest_id: job.replay_ingest_id,
|
|
140
|
-
file_name: job.file_name,
|
|
141
|
-
download_url: job.status === 'complete' ? `/api/export/${job.id}/file` : null,
|
|
142
|
-
error: job.error,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
134
|
async function runOverviewVideoJob(job, replay, getBaseUrlFn) {
|
|
147
135
|
let portableBrowserDir = null;
|
|
148
136
|
try {
|
|
@@ -309,9 +297,11 @@ export function createExportManager({ getReplay, getSettledEnvelope, getBaseUrl
|
|
|
309
297
|
outputHeight: job.height,
|
|
310
298
|
});
|
|
311
299
|
updateJob(job, 'complete', 1, 'complete');
|
|
300
|
+
scheduleJobCleanup(job);
|
|
312
301
|
} catch (err) {
|
|
313
302
|
job.error = err instanceof Error ? err.message : String(err);
|
|
314
303
|
updateJob(job, 'failed', job.progress || 0, 'failed');
|
|
304
|
+
scheduleJobCleanup(job);
|
|
315
305
|
} finally {
|
|
316
306
|
if (portableBrowserDir) {
|
|
317
307
|
try {
|
|
@@ -320,6 +310,41 @@ export function createExportManager({ getReplay, getSettledEnvelope, getBaseUrl
|
|
|
320
310
|
}
|
|
321
311
|
}
|
|
322
312
|
}
|
|
313
|
+
|
|
314
|
+
function pruneExpiredJobs() {
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
for (const job of jobs.values()) {
|
|
317
|
+
if (job.status === 'queued' || job.status === 'running') continue;
|
|
318
|
+
if (!job.cleanup_at_ms || job.cleanup_at_ms > now) continue;
|
|
319
|
+
if (job.cleanup_timer) {
|
|
320
|
+
clearTimeout(job.cleanup_timer);
|
|
321
|
+
job.cleanup_timer = null;
|
|
322
|
+
}
|
|
323
|
+
jobs.delete(job.id);
|
|
324
|
+
void cleanupJobArtifacts(job);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function scheduleJobCleanup(job) {
|
|
329
|
+
if (!job || job.status === 'queued' || job.status === 'running') return;
|
|
330
|
+
if (job.cleanup_timer) {
|
|
331
|
+
clearTimeout(job.cleanup_timer);
|
|
332
|
+
job.cleanup_timer = null;
|
|
333
|
+
}
|
|
334
|
+
job.cleanup_at_ms = Date.now() + EXPORT_JOB_TTL_MS;
|
|
335
|
+
job.cleanup_timer = setTimeout(() => {
|
|
336
|
+
job.cleanup_timer = null;
|
|
337
|
+
jobs.delete(job.id);
|
|
338
|
+
void cleanupJobArtifacts(job);
|
|
339
|
+
}, EXPORT_JOB_TTL_MS);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function cleanupJobArtifacts(job) {
|
|
344
|
+
if (!job?.temp_dir) return;
|
|
345
|
+
try {
|
|
346
|
+
await fs.rm(job.temp_dir, { recursive: true, force: true });
|
|
347
|
+
} catch {}
|
|
323
348
|
}
|
|
324
349
|
|
|
325
350
|
function analyzeExportTrace(debugTrace, captureTrace) {
|
|
@@ -490,6 +515,7 @@ export function createJobSummary(job) {
|
|
|
490
515
|
updated_at: job.updated_at,
|
|
491
516
|
replay_ingest_id: job.replay_ingest_id,
|
|
492
517
|
file_name: job.file_name,
|
|
518
|
+
expires_at: job.cleanup_at_ms ? new Date(job.cleanup_at_ms).toISOString() : null,
|
|
493
519
|
download_url: job.status === 'complete' ? `/api/export/${job.id}/file` : null,
|
|
494
520
|
error: job.error || null,
|
|
495
521
|
};
|
package/server/index.js
CHANGED
|
@@ -92,7 +92,7 @@ export function createServer(codexHome, opts = {}) {
|
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
const appBaseUrl =
|
|
95
|
+
const appBaseUrl = opts.frontendBaseUrl || `${req.protocol}://${req.get('host')}`;
|
|
96
96
|
const settledEnvelope = state.aggregates ? {
|
|
97
97
|
overview: { data: state.aggregates.overview },
|
|
98
98
|
repos: { data: state.aggregates.repos },
|
|
@@ -108,7 +108,7 @@ export function createServer(codexHome, opts = {}) {
|
|
|
108
108
|
appBaseUrl,
|
|
109
109
|
installPortableBrowser: Boolean(req.body?.install_portable_browser),
|
|
110
110
|
});
|
|
111
|
-
res.status(202).json(createJobSummary(job
|
|
111
|
+
res.status(202).json(createJobSummary(job));
|
|
112
112
|
} catch (err) {
|
|
113
113
|
res.status(err.statusCode || 500).json({ error: err.message || String(err) });
|
|
114
114
|
}
|
|
@@ -120,7 +120,7 @@ export function createServer(codexHome, opts = {}) {
|
|
|
120
120
|
res.json({ job: null });
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
123
|
-
res.json({ job: createJobSummary(job
|
|
123
|
+
res.json({ job: createJobSummary(job) });
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
app.get('/api/export/support', async (_req, res) => {
|
|
@@ -133,7 +133,7 @@ export function createServer(codexHome, opts = {}) {
|
|
|
133
133
|
res.status(404).json({ error: 'Export job not found.' });
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
|
-
res.json(createJobSummary(job
|
|
136
|
+
res.json(createJobSummary(job));
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
app.get('/api/export/:jobId/render-data', (req, res) => {
|