codexmeter 1.0.0
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 +60 -0
- package/bin/codexmeter.js +57 -0
- package/dist/assets/index-DJWqyRDh.css +1 -0
- package/dist/assets/index-DZKogILW.js +113 -0
- package/dist/index.html +16 -0
- package/package.json +43 -0
- package/server/aggregator.js +428 -0
- package/server/cost-catalog.js +85 -0
- package/server/day-key.js +16 -0
- package/server/index.js +134 -0
- package/server/ingest.js +595 -0
- package/server/live-state.js +464 -0
- package/server/normalize.js +72 -0
- package/server/pricing-fetch.js +59 -0
- package/server/rollout-reader.js +108 -0
- package/server/rollout-worker-pool.js +159 -0
- package/server/rollout-worker.js +21 -0
- package/server/sqlite-reader.js +59 -0
package/server/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { attachLiveSubscriber, createIngestState, detachLiveSubscriber, restartIngest, runIngest } from './ingest.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export function createServer(codexHome, opts = {}) {
|
|
9
|
+
const app = express();
|
|
10
|
+
const state = createIngestState();
|
|
11
|
+
const distDir = path.join(__dirname, '..', 'dist');
|
|
12
|
+
const apiOnly = opts.devApiOnly === true;
|
|
13
|
+
const ingestOpts = { ...opts };
|
|
14
|
+
|
|
15
|
+
if (!apiOnly) {
|
|
16
|
+
app.use(express.static(distDir));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
app.get('/api/progress', (_req, res) => {
|
|
20
|
+
res.json({
|
|
21
|
+
phase: state.phase,
|
|
22
|
+
total_threads: state.total_threads,
|
|
23
|
+
inventoried: state.inventoried,
|
|
24
|
+
needs_enrichment: state.needs_enrichment,
|
|
25
|
+
enriched: state.enriched,
|
|
26
|
+
current_date_bucket: state.current_date_bucket,
|
|
27
|
+
percent: state.percent,
|
|
28
|
+
complete: state.complete,
|
|
29
|
+
error: state.error,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
app.get('/api/live', (req, res) => {
|
|
34
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
35
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
36
|
+
res.setHeader('Connection', 'keep-alive');
|
|
37
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
38
|
+
if (res.flushHeaders) res.flushHeaders();
|
|
39
|
+
|
|
40
|
+
attachLiveSubscriber(state, res);
|
|
41
|
+
|
|
42
|
+
const heartbeat = setInterval(() => {
|
|
43
|
+
try {
|
|
44
|
+
res.write(`event: heartbeat\ndata: ${JSON.stringify({ ingest_id: state.ingest_id, seq: state.live_seq })}\n\n`);
|
|
45
|
+
} catch {}
|
|
46
|
+
}, 10000);
|
|
47
|
+
|
|
48
|
+
req.on('close', () => {
|
|
49
|
+
clearInterval(heartbeat);
|
|
50
|
+
detachLiveSubscriber(state, res);
|
|
51
|
+
res.end();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.post('/api/rerun', (_req, res) => {
|
|
56
|
+
restartIngest(codexHome, state, ingestOpts);
|
|
57
|
+
res.status(202).json({
|
|
58
|
+
ok: true,
|
|
59
|
+
ingest_id: state.ingest_id,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const wrap = (key) => (_req, res) => {
|
|
64
|
+
const agg = state.aggregates;
|
|
65
|
+
res.json({
|
|
66
|
+
data: agg ? agg[key] : (key === 'overview' ? {} : []),
|
|
67
|
+
complete: state.complete,
|
|
68
|
+
coverage: agg?.overview?.total?.coverage || { total: 0, enriched: 0, priced: 0, time_valid: 0 },
|
|
69
|
+
generated_at: state.generated_at,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
app.get('/api/overview', wrap('overview'));
|
|
74
|
+
app.get('/api/repos', wrap('repos'));
|
|
75
|
+
app.get('/api/models', wrap('models'));
|
|
76
|
+
app.get('/api/daily', wrap('daily'));
|
|
77
|
+
app.get('/api/heatmap', wrap('heatmap'));
|
|
78
|
+
app.get('/api/families', wrap('families'));
|
|
79
|
+
|
|
80
|
+
app.get('/api/sessions', (req, res) => {
|
|
81
|
+
const q = (req.query.q || '').toLowerCase();
|
|
82
|
+
let sessions = state.sessions || [];
|
|
83
|
+
if (q) {
|
|
84
|
+
sessions = sessions.filter(s =>
|
|
85
|
+
(s.repo_label?.toLowerCase().includes(q)) ||
|
|
86
|
+
(s.model_name?.toLowerCase().includes(q)) ||
|
|
87
|
+
(s.agent_role?.toLowerCase().includes(q)) ||
|
|
88
|
+
(s.agent_nickname?.toLowerCase().includes(q)) ||
|
|
89
|
+
(s.title?.toLowerCase().includes(q)) ||
|
|
90
|
+
(s.descendant_models || []).some(v => v?.toLowerCase().includes(q)) ||
|
|
91
|
+
(s.descendant_families || []).some(v => v?.toLowerCase().includes(q)) ||
|
|
92
|
+
(s.descendant_roles || []).some(v => v?.toLowerCase().includes(q)) ||
|
|
93
|
+
(s.descendant_nicknames || []).some(v => v?.toLowerCase().includes(q)) ||
|
|
94
|
+
(s.related_titles || []).some(v => v?.toLowerCase().includes(q))
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
res.json({
|
|
98
|
+
data: sessions.map(s => ({
|
|
99
|
+
thread_id: s.thread_id, root_thread_id: s.root_thread_id, repo_label: s.repo_label,
|
|
100
|
+
model_name: s.model_name, reasoning_effort: s.reasoning_effort,
|
|
101
|
+
agent_role: s.agent_role, agent_nickname: s.agent_nickname,
|
|
102
|
+
agent_family: s.agent_family, is_subagent: s.is_subagent,
|
|
103
|
+
started_at: s.started_at, ended_at: s.ended_at,
|
|
104
|
+
elapsed_seconds: s.elapsed_seconds, tokens_used: s.tokens_used,
|
|
105
|
+
cost: s.cost, cost_source: s.cost_source, title: s.title,
|
|
106
|
+
thread_count: s.thread_count, subagent_count: s.subagent_count,
|
|
107
|
+
descendant_models: s.descendant_models,
|
|
108
|
+
descendant_families: s.descendant_families,
|
|
109
|
+
descendant_roles: s.descendant_roles,
|
|
110
|
+
descendant_nicknames: s.descendant_nicknames,
|
|
111
|
+
related_titles: s.related_titles,
|
|
112
|
+
})),
|
|
113
|
+
complete: state.complete,
|
|
114
|
+
coverage: state.aggregates?.overview?.total?.coverage || { total: 0, enriched: 0, priced: 0, time_valid: 0 },
|
|
115
|
+
generated_at: state.generated_at,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!apiOnly) {
|
|
120
|
+
app.get('/{*splat}', (_req, res) => {
|
|
121
|
+
res.sendFile(path.join(distDir, 'index.html'));
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
app.get('/', (_req, res) => {
|
|
125
|
+
res.type('text/plain').send('codexmeter dev backend is API-only. Open the Vite dev URL for the UI.');
|
|
126
|
+
});
|
|
127
|
+
app.get('/{*splat}', (_req, res) => {
|
|
128
|
+
res.status(404).type('text/plain').send('codexmeter dev backend is API-only. Open the Vite dev URL for the UI.');
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
runIngest(codexHome, state, ingestOpts);
|
|
133
|
+
return app;
|
|
134
|
+
}
|