@statforge/claudestat 1.6.1 → 1.8.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 +36 -3
- package/dashboard/dist/assets/AnalyticsView-DDGLDoCN.js +7 -0
- package/dashboard/dist/assets/HistoryView-DkPfrNrv.js +1 -0
- package/dashboard/dist/assets/LineChart-BOWYkkEW.js +2 -0
- package/dashboard/dist/assets/ProjectsView-VRoRiEL4.js +6 -0
- package/dashboard/dist/assets/SystemView-B2zbIxhY.js +1 -0
- package/dashboard/dist/assets/TopView-C2qdsy0Y.js +1 -0
- package/dashboard/dist/assets/index-CMhe3KaT.js +84 -0
- package/dashboard/dist/assets/shared-BbBtsdh1.js +1 -0
- package/dashboard/dist/assets/{vendor-lucide-Cym0q5l_.js → vendor-lucide-ClCW-axQ.js} +79 -64
- package/dashboard/dist/assets/{vendor-react-B_Jzs0gY.js → vendor-react-gHSHIE2L.js} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/dist/config.d.ts +7 -0
- package/dist/config.js +36 -0
- package/dist/daemon.js +113 -9
- package/dist/db.d.ts +87 -2
- package/dist/db.js +325 -65
- package/dist/doctor.js +21 -3
- package/dist/enricher.d.ts +3 -2
- package/dist/enricher.js +10 -5
- package/dist/export.d.ts +2 -1
- package/dist/export.js +41 -6
- package/dist/index.js +406 -20
- package/dist/insights.d.ts +1 -0
- package/dist/insights.js +26 -0
- package/dist/install.js +28 -1
- package/dist/intelligence.d.ts +66 -4
- package/dist/intelligence.js +205 -17
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +49 -0
- package/dist/notifier.d.ts +15 -0
- package/dist/notifier.js +26 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.js +42 -0
- package/dist/pricing.d.ts +2 -0
- package/dist/pricing.js +12 -1
- package/dist/routes/events.js +136 -5
- package/dist/routes/helpers.d.ts +5 -0
- package/dist/routes/helpers.js +21 -1
- package/dist/routes/history.js +6 -2
- package/dist/routes/intents.d.ts +1 -0
- package/dist/routes/intents.js +155 -0
- package/dist/routes/misc.js +150 -4
- package/dist/routes/opencode-reader.js +39 -3
- package/dist/routes/projects.js +19 -1
- package/dist/routes/replay.d.ts +1 -0
- package/dist/routes/replay.js +29 -0
- package/dist/routes/reports.js +7 -0
- package/dist/routes/top.js +8 -1
- package/dist/service.js +11 -0
- package/dist/watchers/adapter.d.ts +1 -0
- package/dist/watchers/claude-code.d.ts +16 -1
- package/dist/watchers/claude-code.js +201 -76
- package/dist/watchers/opencode.d.ts +1 -0
- package/dist/watchers/opencode.js +152 -14
- package/hooks/event.js +44 -26
- package/package.json +1 -1
- package/dashboard/dist/assets/AnalyticsView-5bUM3UHp.js +0 -8
- package/dashboard/dist/assets/HistoryView-C-AsEqos.js +0 -1
- package/dashboard/dist/assets/ProjectsView-D9bZBdY2.js +0 -6
- package/dashboard/dist/assets/SystemView-DIYDCCF3.js +0 -1
- package/dashboard/dist/assets/TopView-DhdLpsiA.js +0 -1
- package/dashboard/dist/assets/index-DgbWvj42.js +0 -84
package/dist/config.js
CHANGED
|
@@ -43,6 +43,13 @@ const DEFAULTS = {
|
|
|
43
43
|
reportDay: 1,
|
|
44
44
|
reportTime: '09:00',
|
|
45
45
|
alertsEnabled: true,
|
|
46
|
+
killSwitchForce: false,
|
|
47
|
+
logLevel: 'info',
|
|
48
|
+
port: 7337,
|
|
49
|
+
loopThreshold: 8,
|
|
50
|
+
loopWindowSecs: 120,
|
|
51
|
+
projectAliases: {},
|
|
52
|
+
webhookUrl: null,
|
|
46
53
|
};
|
|
47
54
|
/** Lee la config del disco. Valores ausentes se rellenan con defaults. */
|
|
48
55
|
function readConfig() {
|
|
@@ -100,6 +107,30 @@ function validateConfig(raw) {
|
|
|
100
107
|
}
|
|
101
108
|
if ('alertsEnabled' in cfg && typeof cfg.alertsEnabled !== 'boolean')
|
|
102
109
|
return 'alertsEnabled debe ser boolean';
|
|
110
|
+
if ('killSwitchForce' in cfg && typeof cfg.killSwitchForce !== 'boolean')
|
|
111
|
+
return 'killSwitchForce must be boolean';
|
|
112
|
+
if ('logLevel' in cfg && !['debug', 'info', 'warn', 'error'].includes(cfg.logLevel))
|
|
113
|
+
return 'logLevel must be: debug, info, warn, error';
|
|
114
|
+
if ('loopThreshold' in cfg) {
|
|
115
|
+
const v = cfg.loopThreshold;
|
|
116
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 2 || v > 50)
|
|
117
|
+
return 'loopThreshold must be an integer between 2 and 50';
|
|
118
|
+
}
|
|
119
|
+
if ('loopWindowSecs' in cfg) {
|
|
120
|
+
const v = cfg.loopWindowSecs;
|
|
121
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 10 || v > 600)
|
|
122
|
+
return 'loopWindowSecs must be an integer between 10 and 600';
|
|
123
|
+
}
|
|
124
|
+
if ('projectAliases' in cfg) {
|
|
125
|
+
const v = cfg.projectAliases;
|
|
126
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v))
|
|
127
|
+
return 'projectAliases must be an object { "/path": "Alias" }';
|
|
128
|
+
}
|
|
129
|
+
if ('webhookUrl' in cfg) {
|
|
130
|
+
const v = cfg.webhookUrl;
|
|
131
|
+
if (v !== null && (typeof v !== 'string' || (!v.startsWith('http://') && !v.startsWith('https://'))))
|
|
132
|
+
return 'webhookUrl must be null or a valid http/https URL';
|
|
133
|
+
}
|
|
103
134
|
if ('reportsEnabled' in cfg && typeof cfg.reportsEnabled !== 'boolean')
|
|
104
135
|
return 'reportsEnabled debe ser boolean';
|
|
105
136
|
if ('reportFrequency' in cfg && !['weekly', 'biweekly', 'monthly'].includes(cfg.reportFrequency))
|
|
@@ -113,6 +144,11 @@ function validateConfig(raw) {
|
|
|
113
144
|
if (typeof cfg.reportTime !== 'string' || !/^\d{2}:\d{2}$/.test(cfg.reportTime))
|
|
114
145
|
return 'reportTime debe tener formato HH:MM';
|
|
115
146
|
}
|
|
147
|
+
if ('port' in cfg) {
|
|
148
|
+
const v = cfg.port;
|
|
149
|
+
if (typeof v !== 'number' || !Number.isInteger(v) || v < 1024 || v > 65535)
|
|
150
|
+
return 'port must be an integer between 1024 and 65535';
|
|
151
|
+
}
|
|
116
152
|
return null;
|
|
117
153
|
}
|
|
118
154
|
/** Devuelve el nivel de warning para un % dado, o null si no alcanza ningún threshold. */
|
package/dist/daemon.js
CHANGED
|
@@ -51,9 +51,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
};
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
53
|
exports.startDaemon = startDaemon;
|
|
54
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
54
55
|
const express_1 = __importDefault(require("express"));
|
|
55
56
|
const path_1 = __importDefault(require("path"));
|
|
56
57
|
const fs_1 = __importDefault(require("fs"));
|
|
58
|
+
const os_1 = __importDefault(require("os"));
|
|
57
59
|
const db_1 = require("./db");
|
|
58
60
|
const enricher_1 = require("./enricher");
|
|
59
61
|
const config_1 = require("./config");
|
|
@@ -67,11 +69,14 @@ const misc_1 = require("./routes/misc");
|
|
|
67
69
|
const reports_1 = require("./routes/reports");
|
|
68
70
|
const top_1 = require("./routes/top");
|
|
69
71
|
const opencode_reader_1 = require("./routes/opencode-reader");
|
|
72
|
+
const replay_1 = require("./routes/replay");
|
|
73
|
+
const intents_1 = require("./routes/intents");
|
|
70
74
|
const projects_cache_1 = require("./cache/projects-cache");
|
|
71
75
|
const rate_limiter_1 = require("./middleware/rate-limiter");
|
|
72
76
|
const summarizer_1 = require("./summarizer");
|
|
73
77
|
const paths_1 = require("./paths");
|
|
74
|
-
const
|
|
78
|
+
const logger_1 = require("./logger");
|
|
79
|
+
const PORT = (0, config_1.readConfig)().port;
|
|
75
80
|
const app = (0, express_1.default)();
|
|
76
81
|
app.use(express_1.default.json());
|
|
77
82
|
// ─── Shutdown graceful (cross-platform, no depende de SIGTERM) ────────────────
|
|
@@ -91,6 +96,8 @@ app.use(misc_1.miscRouter);
|
|
|
91
96
|
app.use(reports_1.reportsRouter);
|
|
92
97
|
app.use(top_1.topRouter);
|
|
93
98
|
app.use(opencode_reader_1.opencodeReaderRouter);
|
|
99
|
+
app.use(replay_1.replayRouter);
|
|
100
|
+
app.use(intents_1.intentsRouter);
|
|
94
101
|
// ─── GET /health — necesita acceso al tamaño del pool SSE ─────────────────────
|
|
95
102
|
app.get('/health', (_req, res) => {
|
|
96
103
|
res.json({ status: 'ok', port: PORT, clients: (0, stream_1.getSseClientsSize)() });
|
|
@@ -112,9 +119,10 @@ app.get('*', (_req, res) => {
|
|
|
112
119
|
// ─── Migración de arranque: etiquetar sesiones históricas ────────────────────
|
|
113
120
|
function migrateSessionProjects() {
|
|
114
121
|
const sessions = db_1.dbOps.getAllSessions();
|
|
122
|
+
const HOME = os_1.default.homedir();
|
|
115
123
|
let tagged = 0;
|
|
116
124
|
for (const session of sessions) {
|
|
117
|
-
if (session?.project_path)
|
|
125
|
+
if (session?.project_path && session.project_path !== HOME)
|
|
118
126
|
continue;
|
|
119
127
|
const events = db_1.dbOps.getSessionEvents(session.id);
|
|
120
128
|
const projectCwd = (0, projects_1.inferProjectCwd)(events);
|
|
@@ -124,7 +132,7 @@ function migrateSessionProjects() {
|
|
|
124
132
|
}
|
|
125
133
|
}
|
|
126
134
|
if (tagged > 0)
|
|
127
|
-
|
|
135
|
+
logger_1.logger.info(`${tagged} sessions tagged with project`);
|
|
128
136
|
}
|
|
129
137
|
/**
|
|
130
138
|
* Genera summaries IA para las últimas N sesiones que no tienen uno.
|
|
@@ -141,18 +149,22 @@ async function migrateSessionSummaries(limit = 5) {
|
|
|
141
149
|
const summary = await (0, summarizer_1.summarizeSession)(events, s.total_cost_usd ?? 0, projectName);
|
|
142
150
|
if (summary) {
|
|
143
151
|
db_1.dbOps.updateSessionSummary(s.id, summary);
|
|
144
|
-
|
|
152
|
+
logger_1.logger.info(`Summary generated for session ${s.id.slice(0, 8)}: "${summary}"`);
|
|
145
153
|
}
|
|
146
154
|
}
|
|
147
155
|
catch (err) {
|
|
148
|
-
|
|
156
|
+
logger_1.logger.error(`Error generating summary: ${err}`);
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
}
|
|
160
|
+
// ─── Previous hashes map for external change detection ──────────────────────────
|
|
161
|
+
let _prevHashes = new Map();
|
|
152
162
|
// ─── Interval refs for cleanup ────────────────────────────────────────────────
|
|
153
163
|
let projectCacheInterval = null;
|
|
154
164
|
let reportInterval = null;
|
|
155
165
|
let alertInterval = null;
|
|
166
|
+
let intentCleanupInterval = null;
|
|
167
|
+
let intentWatchInterval = null;
|
|
156
168
|
function shutdown(server) {
|
|
157
169
|
(0, enricher_1.stopEnricher)();
|
|
158
170
|
(0, rate_limiter_1.stopRateLimiter)();
|
|
@@ -168,6 +180,15 @@ function shutdown(server) {
|
|
|
168
180
|
clearInterval(alertInterval);
|
|
169
181
|
alertInterval = null;
|
|
170
182
|
}
|
|
183
|
+
if (intentCleanupInterval) {
|
|
184
|
+
clearInterval(intentCleanupInterval);
|
|
185
|
+
intentCleanupInterval = null;
|
|
186
|
+
}
|
|
187
|
+
if (intentWatchInterval) {
|
|
188
|
+
clearInterval(intentWatchInterval);
|
|
189
|
+
intentWatchInterval = null;
|
|
190
|
+
}
|
|
191
|
+
_prevHashes.clear();
|
|
171
192
|
cleanPid();
|
|
172
193
|
server.close(() => { });
|
|
173
194
|
}
|
|
@@ -186,6 +207,7 @@ function checkAlertLevel(level, lastLevel, logMsg, notifTitle, notifBody) {
|
|
|
186
207
|
const prevRank = lastLevel ? LEVEL_RANK[lastLevel] ?? 0 : 0;
|
|
187
208
|
const currRank = LEVEL_RANK[level];
|
|
188
209
|
if (currRank > prevRank) {
|
|
210
|
+
logger_1.logger.warn(logMsg);
|
|
189
211
|
process.stderr.write(`${LEVEL_COLOR[level]}${logMsg}\x1b[0m\n`);
|
|
190
212
|
(0, notifier_1.sendDesktopNotification)(notifTitle, notifBody);
|
|
191
213
|
}
|
|
@@ -200,9 +222,37 @@ function startAlertPolling() {
|
|
|
200
222
|
const data = (0, quota_tracker_1.computeQuota)(cfg.plan ?? undefined);
|
|
201
223
|
const resetMins = Math.ceil(data.cycleResetMs / 60000);
|
|
202
224
|
// ── Cycle 5h alerts ──────────────────────────────────────────────────────
|
|
203
|
-
|
|
225
|
+
const cycleLevel = (0, config_1.getWarnLevel)(data.cyclePct, cfg.warnThresholds);
|
|
226
|
+
if (cycleLevel && cycleLevel !== _lastCycleAlertLevel) {
|
|
227
|
+
const webhookUrl = (0, config_1.readConfig)().webhookUrl;
|
|
228
|
+
if (webhookUrl) {
|
|
229
|
+
(0, notifier_1.sendWebhookAlert)(webhookUrl, {
|
|
230
|
+
title: 'claudestat — 5h cycle alert',
|
|
231
|
+
body: `${data.cyclePct}% of cycle used · resets in ${resetMins}m`,
|
|
232
|
+
level: cycleLevel,
|
|
233
|
+
cyclePct: data.cyclePct,
|
|
234
|
+
resetInMins: resetMins,
|
|
235
|
+
burnRate: data.burnRateTokensPerMin,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
_lastCycleAlertLevel = checkAlertLevel(cycleLevel, _lastCycleAlertLevel, `[claudestat] ⚠️ 5h cycle at ${data.cyclePct}% (${data.cyclePrompts}/${data.cycleLimit} prompts)`, 'claudestat — 5h cycle alert', `${data.cyclePct}% of cycle used · resets in ${resetMins}m`);
|
|
204
240
|
// ── Weekly alerts ────────────────────────────────────────────────────────
|
|
205
|
-
|
|
241
|
+
const weeklyLevel = (0, config_1.getWarnLevel)(data.weeklyPctAll, cfg.weeklyWarnThresholds);
|
|
242
|
+
if (weeklyLevel && weeklyLevel !== _lastWeeklyAlertLevel) {
|
|
243
|
+
const webhookUrl = (0, config_1.readConfig)().webhookUrl;
|
|
244
|
+
if (webhookUrl) {
|
|
245
|
+
(0, notifier_1.sendWebhookAlert)(webhookUrl, {
|
|
246
|
+
title: 'claudestat — Weekly usage alert',
|
|
247
|
+
body: `${data.weeklyPctAll}% of weekly quota used`,
|
|
248
|
+
level: weeklyLevel,
|
|
249
|
+
cyclePct: data.cyclePct,
|
|
250
|
+
weeklyPct: data.weeklyPctAll,
|
|
251
|
+
resetInMins: resetMins,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
_lastWeeklyAlertLevel = checkAlertLevel(weeklyLevel, _lastWeeklyAlertLevel, `[claudestat] ⚠️ Weekly usage at ${data.weeklyPctAll}%`, 'claudestat — Weekly usage alert', `${data.weeklyPctAll}% of weekly quota used`);
|
|
206
256
|
// ── Reset reminder ───────────────────────────────────────────────────────
|
|
207
257
|
const reminderMs = (cfg.resetReminderMins ?? 10) * 60000;
|
|
208
258
|
if (reminderMs > 0) {
|
|
@@ -211,11 +261,28 @@ function startAlertPolling() {
|
|
|
211
261
|
}
|
|
212
262
|
else if (data.cycleResetMs <= reminderMs && data.cycleResetMs > 0 && !_resetReminderFired) {
|
|
213
263
|
const mins = Math.ceil(data.cycleResetMs / 60000);
|
|
264
|
+
logger_1.logger.info(`Quota resets in ${mins}m — good time to wrap up`);
|
|
214
265
|
process.stderr.write(`\x1b[36m[claudestat] ⏰ Quota resets in ${mins}m — good time to wrap up\x1b[0m\n`);
|
|
215
266
|
(0, notifier_1.sendDesktopNotification)('claudestat — Quota reset soon', `Your 5h cycle resets in ${mins} min — good time to start a new task`);
|
|
216
267
|
_resetReminderFired = true;
|
|
217
268
|
}
|
|
218
269
|
}
|
|
270
|
+
// Write or remove pause signal file based on kill switch state
|
|
271
|
+
const signalFile = (0, paths_1.getPauseSignalFile)();
|
|
272
|
+
if (cfg.killSwitchEnabled && data.cyclePct >= cfg.killSwitchThreshold) {
|
|
273
|
+
const msg = `Quota at ${data.cyclePct}% — threshold is ${cfg.killSwitchThreshold}%. Resets in ${Math.ceil(data.cycleResetMs / 60000)}m.`;
|
|
274
|
+
try {
|
|
275
|
+
fs_1.default.writeFileSync(signalFile, msg);
|
|
276
|
+
}
|
|
277
|
+
catch { }
|
|
278
|
+
(0, stream_1.broadcast)({ type: 'kill_switch', payload: { blocked: true, reason: msg } });
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
try {
|
|
282
|
+
fs_1.default.unlinkSync(signalFile);
|
|
283
|
+
}
|
|
284
|
+
catch { }
|
|
285
|
+
}
|
|
219
286
|
}
|
|
220
287
|
catch {
|
|
221
288
|
// quota read failed — ignore
|
|
@@ -242,6 +309,7 @@ function cleanPid() {
|
|
|
242
309
|
function startDaemon() {
|
|
243
310
|
_server = app.listen(PORT, '127.0.0.1', () => {
|
|
244
311
|
writePid();
|
|
312
|
+
(0, paths_1.writePortFile)(PORT);
|
|
245
313
|
process.on('exit', cleanPid);
|
|
246
314
|
process.on('SIGTERM', () => { if (_server)
|
|
247
315
|
shutdown(_server); process.exit(0); });
|
|
@@ -269,7 +337,7 @@ function startDaemon() {
|
|
|
269
337
|
// Se ejecuta en background para no retrasar el inicio del servidor
|
|
270
338
|
setImmediate(() => {
|
|
271
339
|
const projects = (0, projects_cache_1.getProjectsCached)();
|
|
272
|
-
|
|
340
|
+
logger_1.logger.info(`${projects?.length ?? 0} projects scanned`);
|
|
273
341
|
});
|
|
274
342
|
// Refresh automático del cache de proyectos cada 2 minutos
|
|
275
343
|
// Recoge cambios en HANDOFF.md aunque el daemon lleve horas corriendo
|
|
@@ -291,8 +359,44 @@ function startDaemon() {
|
|
|
291
359
|
return; // ya existe
|
|
292
360
|
const markdown = (0, reports_1.generateReport)(dateLabel, cfg);
|
|
293
361
|
db_1.dbOps.insertWeeklyReport(dateLabel, markdown);
|
|
294
|
-
|
|
362
|
+
logger_1.logger.info(`Report auto-generated: ${dateLabel}`);
|
|
295
363
|
}, 60000);
|
|
364
|
+
// Al arrancar: liberar intents activos de sesiones anteriores (ya no hay nadie que los use)
|
|
365
|
+
db_1.dbOps.releaseOrphanedIntents();
|
|
366
|
+
// Cleanup de intents — cada 2min marca stale los sin heartbeat > 10min, borra viejos > 1h
|
|
367
|
+
intentCleanupInterval = setInterval(() => {
|
|
368
|
+
db_1.dbOps.markStaleIntents();
|
|
369
|
+
db_1.dbOps.deleteOldStaleIntents();
|
|
370
|
+
}, 2 * 60000);
|
|
371
|
+
// Watcher de cambios externos — cada 10s compara hashes de archivos en intents activos
|
|
372
|
+
intentWatchInterval = setInterval(() => {
|
|
373
|
+
const active = db_1.dbOps.getActiveIntents();
|
|
374
|
+
if (active.length === 0)
|
|
375
|
+
return;
|
|
376
|
+
const fileSet = new Set(active.map(a => a.file_path));
|
|
377
|
+
for (const filePath of fileSet) {
|
|
378
|
+
try {
|
|
379
|
+
if (!fs_1.default.existsSync(filePath))
|
|
380
|
+
continue;
|
|
381
|
+
const content = fs_1.default.readFileSync(filePath);
|
|
382
|
+
const hash = crypto_1.default.createHash('sha256').update(content).digest('hex');
|
|
383
|
+
const prev = _prevHashes.get(filePath);
|
|
384
|
+
if (prev && prev !== hash) {
|
|
385
|
+
// Check if any active intent covers this file
|
|
386
|
+
const coveringIntent = active.find(a => a.file_path === filePath);
|
|
387
|
+
const tool = coveringIntent?.tool ?? 'unknown';
|
|
388
|
+
(0, stream_1.broadcast)({ type: 'external_change', payload: { file: filePath, tool, hash_prev: prev, hash_now: hash } });
|
|
389
|
+
}
|
|
390
|
+
_prevHashes.set(filePath, hash);
|
|
391
|
+
}
|
|
392
|
+
catch { }
|
|
393
|
+
}
|
|
394
|
+
// Cleanup stale entries from prevHashes
|
|
395
|
+
for (const [fp] of _prevHashes) {
|
|
396
|
+
if (!fileSet.has(fp))
|
|
397
|
+
_prevHashes.delete(fp);
|
|
398
|
+
}
|
|
399
|
+
}, 10000);
|
|
296
400
|
// Summaries IA solo si opt-in explícito (CLAUDESTAT_AI_SUMMARY=true)
|
|
297
401
|
if (process.env.CLAUDESTAT_AI_SUMMARY === 'true') {
|
|
298
402
|
migrateSessionSummaries(5).catch(() => { });
|
package/dist/db.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* El warning "ExperimentalWarning" se suprime en index.ts.
|
|
10
10
|
*/
|
|
11
11
|
export declare const CLAUDESTAT_DIR: string;
|
|
12
|
+
export declare const TOOL_RESPONSE_MAX_BYTES = 8192;
|
|
13
|
+
export declare function capToolResponse(raw: string): string;
|
|
12
14
|
export interface SessionRow {
|
|
13
15
|
id: string;
|
|
14
16
|
cwd?: string;
|
|
@@ -26,6 +28,22 @@ export interface SessionRow {
|
|
|
26
28
|
dominant_model?: string;
|
|
27
29
|
parent_session_id?: string;
|
|
28
30
|
source?: string;
|
|
31
|
+
exact_retries?: number;
|
|
32
|
+
error_rate?: number;
|
|
33
|
+
file_churn_score?: number;
|
|
34
|
+
seq_cycle_count?: number;
|
|
35
|
+
avg_output_chars?: number;
|
|
36
|
+
error_block_count?: number;
|
|
37
|
+
semantic_loop_count?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface AssistantTurnRow {
|
|
40
|
+
turn_index: number;
|
|
41
|
+
ts?: number;
|
|
42
|
+
text_preview?: string;
|
|
43
|
+
tool_calls?: string[];
|
|
44
|
+
error_count: number;
|
|
45
|
+
output_chars: number;
|
|
46
|
+
context_used: number;
|
|
29
47
|
}
|
|
30
48
|
export interface EventRow {
|
|
31
49
|
id?: number;
|
|
@@ -58,17 +76,20 @@ export interface CostUpdate {
|
|
|
58
76
|
lastEntry?: BlockCostEntry;
|
|
59
77
|
lastModel?: string;
|
|
60
78
|
firstTs?: number;
|
|
79
|
+
lastTs?: number;
|
|
61
80
|
}
|
|
62
81
|
export declare const dbOps: {
|
|
63
82
|
upsertSession(s: SessionRow): void;
|
|
64
83
|
insertEvent(e: EventRow): number;
|
|
84
|
+
insertOcEvent(sessionId: string, toolName: string, ts: number, externalId: string): void;
|
|
85
|
+
insertSessionIfAbsent(s: SessionRow): void;
|
|
65
86
|
/**
|
|
66
87
|
* Al llegar PostToolUse, actualizamos el PreToolUse pendiente más reciente
|
|
67
88
|
* del mismo tool para esta sesión. Esto convierte el par Pre+Post en
|
|
68
89
|
* un único registro de tipo 'Done' con duration_ms calculado.
|
|
69
90
|
*/
|
|
70
91
|
pairPostWithPre(sessionId: string, toolName: string, response: string, postTs: number): number | null;
|
|
71
|
-
updateSessionCost(sessionId: string, cost: CostUpdate, efficiencyScore: number, loopsDetected: number): void;
|
|
92
|
+
updateSessionCost(sessionId: string, cost: CostUpdate, efficiencyScore: number, loopsDetected: number, exactRetries?: number, errorRate?: number, fileChurnScore?: number, seqCycleCount?: number): void;
|
|
72
93
|
getSessionEvents(sessionId: string): EventRow[];
|
|
73
94
|
getSession(sessionId: string): SessionRow | undefined;
|
|
74
95
|
getLatestSession(): SessionRow | undefined;
|
|
@@ -83,6 +104,11 @@ export declare const dbOps: {
|
|
|
83
104
|
count: number;
|
|
84
105
|
}[];
|
|
85
106
|
getProjectSessionStats(projectPath: string): any;
|
|
107
|
+
getProjectCliHours(): {
|
|
108
|
+
project_path: string;
|
|
109
|
+
source: string;
|
|
110
|
+
total_ms: number;
|
|
111
|
+
}[];
|
|
86
112
|
updateSessionSummary(sessionId: string, summary: string): void;
|
|
87
113
|
updateSessionParent(sessionId: string, parentId: string): void;
|
|
88
114
|
getChildSessions(parentSessionId: string): {
|
|
@@ -125,8 +151,16 @@ export declare const dbOps: {
|
|
|
125
151
|
getAnalyticsDaily(since: number): any[];
|
|
126
152
|
getAnalyticsByModel(since: number): any[];
|
|
127
153
|
getProjectHours(since: number): any[];
|
|
128
|
-
|
|
154
|
+
getCoachEvents(sessionIds: string[]): {
|
|
155
|
+
session_id: string;
|
|
156
|
+
type: string;
|
|
157
|
+
tool_name: string | null;
|
|
158
|
+
tool_input: string | null;
|
|
159
|
+
ts: number;
|
|
160
|
+
}[];
|
|
161
|
+
getTopTools(days?: number, by?: "cost" | "count" | "duration", limit?: number, source?: "all" | "claude-code" | "opencode"): {
|
|
129
162
|
tool_name: string;
|
|
163
|
+
source: string;
|
|
130
164
|
count: number;
|
|
131
165
|
total_duration_ms: number;
|
|
132
166
|
total_cost_usd: number;
|
|
@@ -142,6 +176,17 @@ export declare const dbOps: {
|
|
|
142
176
|
week_start: number;
|
|
143
177
|
week_end: number;
|
|
144
178
|
};
|
|
179
|
+
getPrevWeekInsight(): {
|
|
180
|
+
total_sessions: any;
|
|
181
|
+
total_cost: any;
|
|
182
|
+
input_tokens: any;
|
|
183
|
+
output_tokens: any;
|
|
184
|
+
cache_read: any;
|
|
185
|
+
total_loops: any;
|
|
186
|
+
avg_efficiency: any;
|
|
187
|
+
week_start: any;
|
|
188
|
+
week_end: any;
|
|
189
|
+
};
|
|
145
190
|
setMeta(key: string, value: string): void;
|
|
146
191
|
getMeta(key: string): string | undefined;
|
|
147
192
|
getCostProjection(days?: number): {
|
|
@@ -158,6 +203,9 @@ export declare const dbOps: {
|
|
|
158
203
|
hour: number;
|
|
159
204
|
session_count: number;
|
|
160
205
|
}[];
|
|
206
|
+
updateSessionSemantic(sessionId: string, avgOutputChars: number, errorBlockCount: number, semanticLoopCount?: number): void;
|
|
207
|
+
upsertAssistantTurns(sessionId: string, turns: AssistantTurnRow[]): void;
|
|
208
|
+
getAssistantTurns(sessionId: string): AssistantTurnRow[];
|
|
161
209
|
getCacheReadByModel(days: number): {
|
|
162
210
|
model: string;
|
|
163
211
|
cache_read: number;
|
|
@@ -167,4 +215,41 @@ export declare const dbOps: {
|
|
|
167
215
|
total_cost: number;
|
|
168
216
|
session_count: number;
|
|
169
217
|
}[];
|
|
218
|
+
insertIntent(tool: string, sessionId: string, taskDesc: string | undefined): number;
|
|
219
|
+
insertIntentFile(intentId: number, filePath: string, operation: string, lineStart?: number, lineEnd?: number): void;
|
|
220
|
+
getWriteConflicts(filePaths: string[], excludeTool: string): {
|
|
221
|
+
file_path: string;
|
|
222
|
+
tool: string;
|
|
223
|
+
session_id: string;
|
|
224
|
+
task_desc: string | null;
|
|
225
|
+
acquired_at: number;
|
|
226
|
+
}[];
|
|
227
|
+
getIntent(id: number): {
|
|
228
|
+
tool: string;
|
|
229
|
+
session_id: string;
|
|
230
|
+
task_desc: string | null;
|
|
231
|
+
} | undefined;
|
|
232
|
+
releaseIntent(id: number): void;
|
|
233
|
+
heartbeatIntent(id: number): void;
|
|
234
|
+
getIntentFiles(id: number): {
|
|
235
|
+
file_path: string;
|
|
236
|
+
operation: string;
|
|
237
|
+
}[];
|
|
238
|
+
getActiveToolsInProject(projectPath: string, excludeTool: string): {
|
|
239
|
+
source: string;
|
|
240
|
+
last_event_at: number;
|
|
241
|
+
}[];
|
|
242
|
+
releaseOrphanedIntents(): void;
|
|
243
|
+
markStaleIntents(): {
|
|
244
|
+
id: number;
|
|
245
|
+
tool: string;
|
|
246
|
+
task_desc: string | null;
|
|
247
|
+
}[];
|
|
248
|
+
deleteOldStaleIntents(): void;
|
|
249
|
+
hasActiveIntent(tool: string, filePath: string): boolean;
|
|
250
|
+
getActiveIntents(): {
|
|
251
|
+
id: number;
|
|
252
|
+
tool: string;
|
|
253
|
+
file_path: string;
|
|
254
|
+
}[];
|
|
170
255
|
};
|