idletime 0.1.2 → 0.1.3
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
CHANGED
|
@@ -64,6 +64,7 @@ bun run idletime
|
|
|
64
64
|
|
|
65
65
|
That shows:
|
|
66
66
|
|
|
67
|
+
- A gold `BEST` plaque in the header for your top concurrent agents, top 24-hour raw burn, and top agent-sum record
|
|
67
68
|
- A framed trailing-24h dashboard
|
|
68
69
|
- A `24h Rhythm` strip for `focus`, `active`, `quiet` or `idle`, and `burn`
|
|
69
70
|
- `Spike Callouts` for the biggest burn hours
|
|
@@ -171,6 +172,20 @@ Once published, that also works as:
|
|
|
171
172
|
idletime --version
|
|
172
173
|
```
|
|
173
174
|
|
|
175
|
+
## Record Tracking
|
|
176
|
+
|
|
177
|
+
`idletime` now keeps a local personal-best ledger under `~/.idletime/`.
|
|
178
|
+
|
|
179
|
+
- `bests-v1.json`: durable best values for the header plaque
|
|
180
|
+
- `best-events.ndjson`: append-only new-best history
|
|
181
|
+
- `near-best-notifications-v1.json`: opt-in state for “close to best” nudges
|
|
182
|
+
|
|
183
|
+
By default:
|
|
184
|
+
|
|
185
|
+
- the `BEST` plaque is always shown in the normal header
|
|
186
|
+
- genuine new-best events can trigger a local macOS notification
|
|
187
|
+
- near-best nudges are stored but disabled until you opt in by setting `nearBestEnabled` to `true`
|
|
188
|
+
|
|
174
189
|
## Validation
|
|
175
190
|
|
|
176
191
|
```bash
|
|
Binary file
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="24" y1="20" x2="228" y2="236" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0" stop-color="#202611"/>
|
|
5
|
+
<stop offset="1" stop-color="#12160A"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="glow" x1="44" y1="42" x2="212" y2="220" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop offset="0" stop-color="#C7BC61"/>
|
|
9
|
+
<stop offset="1" stop-color="#786B2A"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="top" x1="128" y1="62" x2="128" y2="116" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop offset="0" stop-color="#F0E6AB"/>
|
|
13
|
+
<stop offset="1" stop-color="#D7C770"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<linearGradient id="left" x1="90" y1="92" x2="118" y2="196" gradientUnits="userSpaceOnUse">
|
|
16
|
+
<stop offset="0" stop-color="#D0BC58"/>
|
|
17
|
+
<stop offset="1" stop-color="#937F34"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<linearGradient id="right" x1="166" y1="92" x2="138" y2="198" gradientUnits="userSpaceOnUse">
|
|
20
|
+
<stop offset="0" stop-color="#BFA747"/>
|
|
21
|
+
<stop offset="1" stop-color="#6E5E22"/>
|
|
22
|
+
</linearGradient>
|
|
23
|
+
</defs>
|
|
24
|
+
<rect x="16" y="16" width="224" height="224" rx="44" fill="url(#bg)"/>
|
|
25
|
+
<rect x="26" y="26" width="204" height="204" rx="34" stroke="url(#glow)" stroke-opacity=".42" stroke-width="4"/>
|
|
26
|
+
<rect x="42" y="42" width="172" height="22" rx="11" fill="url(#glow)" fill-opacity=".34"/>
|
|
27
|
+
<path d="M128 58 184 90 128 122 72 90 128 58Z" fill="url(#top)"/>
|
|
28
|
+
<path d="M72 90 128 122V190L72 158V90Z" fill="url(#left)"/>
|
|
29
|
+
<path d="M184 90 128 122V190L184 158V90Z" fill="url(#right)"/>
|
|
30
|
+
<path d="M128 58 184 90 128 122 72 90 128 58Z" stroke="#F5EDBD" stroke-opacity=".74" stroke-width="3"/>
|
|
31
|
+
<path d="M72 90 128 122M184 90 128 122M128 122V190" stroke="#FFF5C5" stroke-opacity=".42" stroke-width="3"/>
|
|
32
|
+
<path d="M86 178h84" stroke="#FFF0AB" stroke-opacity=".24" stroke-width="6" stroke-linecap="round"/>
|
|
33
|
+
</svg>
|
|
Binary file
|
package/dist/idletime.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "idletime",
|
|
5
|
-
version: "0.1.
|
|
5
|
+
version: "0.1.3",
|
|
6
6
|
description: "Visual CLI for Codex focus, token burn, spikes, and idle time from local session logs.",
|
|
7
7
|
author: "ParkerRex",
|
|
8
8
|
main: "./dist/idletime.js",
|
|
@@ -46,10 +46,10 @@ var package_default = {
|
|
|
46
46
|
sideEffects: false,
|
|
47
47
|
scripts: {
|
|
48
48
|
build: "bun run src/release/build-package.ts",
|
|
49
|
-
"check:release": "bun run typecheck && bun test && bun run qa && npm pack --dry-run",
|
|
49
|
+
"check:release": "bun run build && bun run typecheck && bun test && bun run qa && npm pack --dry-run",
|
|
50
50
|
dev: "bun run src/cli/idletime-bin.ts",
|
|
51
51
|
idletime: "bun run src/cli/idletime-bin.ts",
|
|
52
|
-
"pack:dry-run": "npm pack --dry-run",
|
|
52
|
+
"pack:dry-run": "bun run build && npm pack --dry-run",
|
|
53
53
|
"publish:dry-run": "bun run build && bun publish --dry-run --access public",
|
|
54
54
|
prepublishOnly: "bun run check:release",
|
|
55
55
|
qa: "bun run qa:gaps && bun run qa:journeys",
|
|
@@ -1061,18 +1061,66 @@ function shortenPath(pathText, maxLength) {
|
|
|
1061
1061
|
return `...${shortenedPath || pathText.slice(-(maxLength - 3))}`;
|
|
1062
1062
|
}
|
|
1063
1063
|
|
|
1064
|
+
// src/reporting/render-best-plaque.ts
|
|
1065
|
+
function buildBestPlaque(ledger) {
|
|
1066
|
+
return {
|
|
1067
|
+
label: "BEST",
|
|
1068
|
+
concurrentAgentsText: `${formatInteger(ledger?.bestConcurrentAgents?.value ?? 0)} concurrent agents`,
|
|
1069
|
+
rawBurnText: `${formatCompactInteger(ledger?.best24hRawBurn?.value ?? 0).toUpperCase()} 24hr raw burn`,
|
|
1070
|
+
agentSumText: `${formatAgentSumHours(ledger?.best24hAgentSumMs?.value ?? 0)} agent sum`
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function buildBestPlaqueRows(bestPlaque, availableWidth) {
|
|
1074
|
+
const wideRows = [
|
|
1075
|
+
bestPlaque.label,
|
|
1076
|
+
bestPlaque.concurrentAgentsText,
|
|
1077
|
+
bestPlaque.rawBurnText,
|
|
1078
|
+
bestPlaque.agentSumText,
|
|
1079
|
+
""
|
|
1080
|
+
];
|
|
1081
|
+
if (rowsFitWidth(wideRows, availableWidth)) {
|
|
1082
|
+
return wideRows;
|
|
1083
|
+
}
|
|
1084
|
+
const compactRows = [
|
|
1085
|
+
bestPlaque.label,
|
|
1086
|
+
bestPlaque.concurrentAgentsText.replace(" agents", ""),
|
|
1087
|
+
bestPlaque.rawBurnText.replace(" 24hr ", " "),
|
|
1088
|
+
bestPlaque.agentSumText,
|
|
1089
|
+
""
|
|
1090
|
+
];
|
|
1091
|
+
if (rowsFitWidth(compactRows, availableWidth)) {
|
|
1092
|
+
return compactRows;
|
|
1093
|
+
}
|
|
1094
|
+
const microRows = [
|
|
1095
|
+
bestPlaque.label,
|
|
1096
|
+
compactRows[1]?.replace("concurrent", "conc") ?? "",
|
|
1097
|
+
compactRows[2]?.replace(" burn", "") ?? "",
|
|
1098
|
+
compactRows[3]?.replace(" agent sum", " sum") ?? "",
|
|
1099
|
+
""
|
|
1100
|
+
];
|
|
1101
|
+
return rowsFitWidth(microRows, availableWidth) ? microRows : null;
|
|
1102
|
+
}
|
|
1103
|
+
function formatAgentSumHours(durationMs) {
|
|
1104
|
+
const hours = durationMs / 3600000;
|
|
1105
|
+
const roundedHours = hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
1106
|
+
return roundedHours.endsWith(".0") ? roundedHours.slice(0, -2) : roundedHours;
|
|
1107
|
+
}
|
|
1108
|
+
function rowsFitWidth(rows, availableWidth) {
|
|
1109
|
+
return availableWidth > 0 && rows.every((row) => row.length <= availableWidth);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1064
1112
|
// src/reporting/render-theme.ts
|
|
1065
1113
|
var roleStyles = {
|
|
1066
|
-
focus: "1;38;2;
|
|
1067
|
-
active: "1;38;2;
|
|
1068
|
-
agent: "1;38;2;
|
|
1069
|
-
idle: "1;38;2;
|
|
1070
|
-
burn: "1;38;2;
|
|
1071
|
-
raw: "1;38;2;
|
|
1072
|
-
frame: "1;38;2;
|
|
1073
|
-
heading: "1;38;2;
|
|
1074
|
-
muted: "38;2;
|
|
1075
|
-
value: "1;38;2;
|
|
1114
|
+
focus: "1;38;2;190;184;86",
|
|
1115
|
+
active: "1;38;2;157;168;60",
|
|
1116
|
+
agent: "1;38;2;124;138;55",
|
|
1117
|
+
idle: "1;38;2;105;118;50",
|
|
1118
|
+
burn: "1;38;2;190;161;55",
|
|
1119
|
+
raw: "1;38;2;153;132;52",
|
|
1120
|
+
frame: "1;38;2;118;126;50",
|
|
1121
|
+
heading: "1;38;2;201;190;102",
|
|
1122
|
+
muted: "38;2;111;115;78",
|
|
1123
|
+
value: "1;38;2;178;161;68"
|
|
1076
1124
|
};
|
|
1077
1125
|
function createRenderOptions(shareMode) {
|
|
1078
1126
|
return {
|
|
@@ -1110,7 +1158,9 @@ function measureVisibleTextWidth(text) {
|
|
|
1110
1158
|
|
|
1111
1159
|
// src/reporting/render-logo-section.ts
|
|
1112
1160
|
var baseBackgroundStyle = "48;2;12;15;8";
|
|
1113
|
-
var
|
|
1161
|
+
var plaqueInsetColumns = 3;
|
|
1162
|
+
var plaqueTextStyle = "1;38;2;244;235;164";
|
|
1163
|
+
var wordmarkStyle = `${baseBackgroundStyle};1;38;2;210;198;108`;
|
|
1114
1164
|
var wordmarkLines = [
|
|
1115
1165
|
" ▄▄ ▄▄",
|
|
1116
1166
|
"▀▀ ██ ██ ██ ▀▀",
|
|
@@ -1120,59 +1170,76 @@ var wordmarkLines = [
|
|
|
1120
1170
|
];
|
|
1121
1171
|
var patternColors = [
|
|
1122
1172
|
{ red: 20, green: 24, blue: 10 },
|
|
1123
|
-
{ red:
|
|
1124
|
-
{ red:
|
|
1125
|
-
{ red:
|
|
1126
|
-
{ red:
|
|
1127
|
-
{ red:
|
|
1173
|
+
{ red: 42, green: 50, blue: 16 },
|
|
1174
|
+
{ red: 71, green: 81, blue: 24 },
|
|
1175
|
+
{ red: 101, green: 112, blue: 31 },
|
|
1176
|
+
{ red: 136, green: 145, blue: 39 },
|
|
1177
|
+
{ red: 177, green: 169, blue: 58 }
|
|
1128
1178
|
];
|
|
1129
1179
|
var monochromePatternCharacters = ["░", "░", "▒", "▓", "█"];
|
|
1130
|
-
function buildLogoSection(requestedWidth, options) {
|
|
1180
|
+
function buildLogoSection(requestedWidth, options, bestPlaque = null) {
|
|
1131
1181
|
const wordmarkWidth = Math.max(...wordmarkLines.map((line) => line.length));
|
|
1132
1182
|
const sectionWidth = Math.max(requestedWidth, wordmarkWidth);
|
|
1133
1183
|
const patternWidth = Math.max(0, sectionWidth - wordmarkWidth);
|
|
1184
|
+
const plaqueRows = bestPlaque ? buildBestPlaqueRows(bestPlaque, Math.max(0, patternWidth - plaqueInsetColumns)) : null;
|
|
1134
1185
|
return wordmarkLines.map((line, rowIndex) => {
|
|
1135
1186
|
const paddedWordmark = padRight(line, wordmarkWidth);
|
|
1136
|
-
const patternTail = buildPatternTail(patternWidth, rowIndex, options);
|
|
1187
|
+
const patternTail = buildPatternTail(patternWidth, rowIndex, options, plaqueRows?.[rowIndex] ?? "");
|
|
1137
1188
|
return `${paintAnsi(paddedWordmark, wordmarkStyle, options)}${patternTail}`;
|
|
1138
1189
|
});
|
|
1139
1190
|
}
|
|
1140
1191
|
function resolveLogoSectionWidth(minimumWidth, options) {
|
|
1141
1192
|
return Math.max(minimumWidth, options.terminalWidth ?? 0);
|
|
1142
1193
|
}
|
|
1143
|
-
function buildPatternTail(width, rowIndex, options) {
|
|
1194
|
+
function buildPatternTail(width, rowIndex, options, plaqueRowText) {
|
|
1144
1195
|
if (!options.colorEnabled) {
|
|
1145
|
-
return buildMonochromePatternTail(width, rowIndex);
|
|
1196
|
+
return buildMonochromePatternTail(width, rowIndex, plaqueRowText);
|
|
1146
1197
|
}
|
|
1198
|
+
const overlayCharacters = createOverlayCharacters(width, plaqueRowText);
|
|
1199
|
+
const cellStyles = Array.from({ length: width }, (_, columnIndex) => getPatternCellStyle(getPatternIntensity(width, rowIndex, columnIndex)));
|
|
1147
1200
|
let patternTail = "";
|
|
1148
1201
|
let currentStyle = "";
|
|
1149
|
-
let
|
|
1202
|
+
let currentSegment = "";
|
|
1150
1203
|
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1151
|
-
const
|
|
1204
|
+
const overlayCharacter = overlayCharacters[columnIndex];
|
|
1205
|
+
const style = overlayCharacter === null ? cellStyles[columnIndex] : `${cellStyles[columnIndex]};${plaqueTextStyle}`;
|
|
1152
1206
|
if (style === currentStyle) {
|
|
1153
|
-
|
|
1207
|
+
currentSegment += overlayCharacter ?? " ";
|
|
1154
1208
|
continue;
|
|
1155
1209
|
}
|
|
1156
|
-
if (
|
|
1157
|
-
patternTail += paintAnsi(
|
|
1210
|
+
if (currentSegment.length > 0) {
|
|
1211
|
+
patternTail += paintAnsi(currentSegment, currentStyle, options);
|
|
1158
1212
|
}
|
|
1159
1213
|
currentStyle = style;
|
|
1160
|
-
|
|
1214
|
+
currentSegment = overlayCharacter ?? " ";
|
|
1161
1215
|
}
|
|
1162
|
-
if (
|
|
1163
|
-
patternTail += paintAnsi(
|
|
1216
|
+
if (currentSegment.length > 0) {
|
|
1217
|
+
patternTail += paintAnsi(currentSegment, currentStyle, options);
|
|
1164
1218
|
}
|
|
1165
1219
|
return patternTail;
|
|
1166
1220
|
}
|
|
1167
|
-
function buildMonochromePatternTail(width, rowIndex) {
|
|
1221
|
+
function buildMonochromePatternTail(width, rowIndex, plaqueRowText) {
|
|
1222
|
+
const overlayCharacters = createOverlayCharacters(width, plaqueRowText);
|
|
1168
1223
|
let patternTail = "";
|
|
1169
1224
|
for (let columnIndex = 0;columnIndex < width; columnIndex += 1) {
|
|
1225
|
+
const overlayCharacter = overlayCharacters[columnIndex];
|
|
1226
|
+
if (overlayCharacter !== null) {
|
|
1227
|
+
patternTail += overlayCharacter;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1170
1230
|
const intensity = getPatternIntensity(width, rowIndex, columnIndex);
|
|
1171
1231
|
const characterIndex = Math.min(monochromePatternCharacters.length - 1, Math.floor(intensity * monochromePatternCharacters.length));
|
|
1172
1232
|
patternTail += monochromePatternCharacters[characterIndex];
|
|
1173
1233
|
}
|
|
1174
1234
|
return patternTail;
|
|
1175
1235
|
}
|
|
1236
|
+
function createOverlayCharacters(width, plaqueRowText) {
|
|
1237
|
+
const overlayCharacters = Array.from({ length: width }, () => null);
|
|
1238
|
+
for (let index = 0;index < plaqueRowText.length && plaqueInsetColumns + index < width; index += 1) {
|
|
1239
|
+
overlayCharacters[plaqueInsetColumns + index] = plaqueRowText[index];
|
|
1240
|
+
}
|
|
1241
|
+
return overlayCharacters;
|
|
1242
|
+
}
|
|
1176
1243
|
function getPatternIntensity(width, rowIndex, columnIndex) {
|
|
1177
1244
|
const normalizedColumn = width <= 1 ? 0 : columnIndex / Math.max(1, width - 1);
|
|
1178
1245
|
const envelope = 0.18 + 0.82 * Math.pow(normalizedColumn, 0.82);
|
|
@@ -1355,6 +1422,631 @@ async function runHourlyCommand(command) {
|
|
|
1355
1422
|
}), createRenderOptions(command.shareMode));
|
|
1356
1423
|
}
|
|
1357
1424
|
|
|
1425
|
+
// src/best-metrics/notification-delivery.ts
|
|
1426
|
+
import { execFile } from "node:child_process";
|
|
1427
|
+
import { existsSync } from "node:fs";
|
|
1428
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1429
|
+
import { promisify } from "node:util";
|
|
1430
|
+
var execFileAsync = promisify(execFile);
|
|
1431
|
+
async function deliverLocalNotifications(notifications, options = {}) {
|
|
1432
|
+
const platform = options.platform ?? process.platform;
|
|
1433
|
+
if (platform !== "darwin" || notifications.length === 0) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const notifier = options.notifier ?? sendMacOsNotification;
|
|
1437
|
+
for (const notification of notifications) {
|
|
1438
|
+
try {
|
|
1439
|
+
await notifier(notification);
|
|
1440
|
+
} catch {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
async function sendMacOsNotification(notification) {
|
|
1446
|
+
const notificationIconPath = resolveNotificationIconPath();
|
|
1447
|
+
try {
|
|
1448
|
+
await execFileAsync("terminal-notifier", [
|
|
1449
|
+
"-title",
|
|
1450
|
+
notification.title,
|
|
1451
|
+
"-message",
|
|
1452
|
+
notification.body,
|
|
1453
|
+
...notificationIconPath ? ["-appIcon", pathToFileURL(notificationIconPath).href] : []
|
|
1454
|
+
]);
|
|
1455
|
+
return;
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
if (!isCommandMissingError(error)) {
|
|
1458
|
+
throw error;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
await execFileAsync("osascript", [
|
|
1462
|
+
"-e",
|
|
1463
|
+
`display notification "${escapeAppleScriptText(notification.body)}" with title "${escapeAppleScriptText(notification.title)}"`
|
|
1464
|
+
]);
|
|
1465
|
+
}
|
|
1466
|
+
function escapeAppleScriptText(value) {
|
|
1467
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
|
|
1468
|
+
}
|
|
1469
|
+
function resolveNotificationIconPath() {
|
|
1470
|
+
const candidatePaths = [
|
|
1471
|
+
fileURLToPath(new URL("../../assets/idle-time-notification-icon.png", import.meta.url)),
|
|
1472
|
+
fileURLToPath(new URL("../assets/idle-time-notification-icon.png", import.meta.url))
|
|
1473
|
+
];
|
|
1474
|
+
return candidatePaths.find((candidatePath) => existsSync(candidatePath)) ?? null;
|
|
1475
|
+
}
|
|
1476
|
+
function isCommandMissingError(error) {
|
|
1477
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// src/best-metrics/near-best-notifications.ts
|
|
1481
|
+
import { mkdir, readFile as readFile2, rename, writeFile } from "node:fs/promises";
|
|
1482
|
+
import { homedir as homedir2 } from "node:os";
|
|
1483
|
+
import { join as join2 } from "node:path";
|
|
1484
|
+
var nearBestNotificationStateFileName = "near-best-notifications-v1.json";
|
|
1485
|
+
var nearBestNotificationVersion = 1;
|
|
1486
|
+
async function notifyNearBestMetrics(currentMetrics, ledger, options = {}) {
|
|
1487
|
+
const now = options.now ?? new Date;
|
|
1488
|
+
const state = await ensureNearBestNotificationState(options);
|
|
1489
|
+
if (!state.nearBestEnabled) {
|
|
1490
|
+
return [];
|
|
1491
|
+
}
|
|
1492
|
+
const metricsToNotify = buildNearBestMetricKeys(currentMetrics, ledger, state, now);
|
|
1493
|
+
if (metricsToNotify.length === 0) {
|
|
1494
|
+
return [];
|
|
1495
|
+
}
|
|
1496
|
+
const nextState = {
|
|
1497
|
+
...state,
|
|
1498
|
+
lastNotifiedAt: {
|
|
1499
|
+
...state.lastNotifiedAt,
|
|
1500
|
+
...Object.fromEntries(metricsToNotify.map((metric) => [metric, now]))
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
await writeNearBestNotificationState(nextState, options);
|
|
1504
|
+
await deliverLocalNotifications(metricsToNotify.map((metric) => buildNearBestNotification(metric, currentMetrics[metric], ledger[metric]?.value ?? 0)), options);
|
|
1505
|
+
return metricsToNotify;
|
|
1506
|
+
}
|
|
1507
|
+
async function ensureNearBestNotificationState(options) {
|
|
1508
|
+
const existingState = await readNearBestNotificationState(options);
|
|
1509
|
+
if (existingState) {
|
|
1510
|
+
return existingState;
|
|
1511
|
+
}
|
|
1512
|
+
const defaultState = createDefaultNearBestNotificationState();
|
|
1513
|
+
await writeNearBestNotificationState(defaultState, options);
|
|
1514
|
+
return defaultState;
|
|
1515
|
+
}
|
|
1516
|
+
async function readNearBestNotificationState(options) {
|
|
1517
|
+
try {
|
|
1518
|
+
const rawStateText = await readFile2(resolveNearBestNotificationStatePath(options), "utf8");
|
|
1519
|
+
return parseNearBestNotificationState(JSON.parse(rawStateText));
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
throw error;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function parseNearBestNotificationState(value) {
|
|
1528
|
+
const stateRecord = expectObject(value, "nearBestNotificationState");
|
|
1529
|
+
const version = readNumber(stateRecord, "version", "nearBestNotificationState");
|
|
1530
|
+
if (version !== nearBestNotificationVersion) {
|
|
1531
|
+
throw new Error(`nearBestNotificationState.version must be ${nearBestNotificationVersion}.`);
|
|
1532
|
+
}
|
|
1533
|
+
const lastNotifiedAtRecord = expectObject(stateRecord.lastNotifiedAt, "nearBestNotificationState.lastNotifiedAt");
|
|
1534
|
+
return {
|
|
1535
|
+
version,
|
|
1536
|
+
nearBestEnabled: Boolean(stateRecord.nearBestEnabled),
|
|
1537
|
+
thresholdRatio: readNumber(stateRecord, "thresholdRatio", "nearBestNotificationState"),
|
|
1538
|
+
cooldownMs: readNumber(stateRecord, "cooldownMs", "nearBestNotificationState"),
|
|
1539
|
+
lastNotifiedAt: {
|
|
1540
|
+
bestConcurrentAgents: readOptionalIsoTimestamp(lastNotifiedAtRecord.bestConcurrentAgents, "nearBestNotificationState.lastNotifiedAt.bestConcurrentAgents"),
|
|
1541
|
+
best24hRawBurn: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hRawBurn, "nearBestNotificationState.lastNotifiedAt.best24hRawBurn"),
|
|
1542
|
+
best24hAgentSumMs: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hAgentSumMs, "nearBestNotificationState.lastNotifiedAt.best24hAgentSumMs")
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
async function writeNearBestNotificationState(state, options) {
|
|
1547
|
+
const statePath = resolveNearBestNotificationStatePath(options);
|
|
1548
|
+
const stateDirectory = options.stateDirectory ?? join2(homedir2(), ".idletime");
|
|
1549
|
+
await mkdir(stateDirectory, { recursive: true });
|
|
1550
|
+
const temporaryPath = join2(stateDirectory, `.near-best-notifications.${process.pid}.${Date.now()}.tmp`);
|
|
1551
|
+
await writeFile(temporaryPath, `${JSON.stringify({
|
|
1552
|
+
version: state.version,
|
|
1553
|
+
nearBestEnabled: state.nearBestEnabled,
|
|
1554
|
+
thresholdRatio: state.thresholdRatio,
|
|
1555
|
+
cooldownMs: state.cooldownMs,
|
|
1556
|
+
lastNotifiedAt: {
|
|
1557
|
+
bestConcurrentAgents: state.lastNotifiedAt.bestConcurrentAgents?.toISOString() ?? null,
|
|
1558
|
+
best24hRawBurn: state.lastNotifiedAt.best24hRawBurn?.toISOString() ?? null,
|
|
1559
|
+
best24hAgentSumMs: state.lastNotifiedAt.best24hAgentSumMs?.toISOString() ?? null
|
|
1560
|
+
}
|
|
1561
|
+
}, null, 2)}
|
|
1562
|
+
`, "utf8");
|
|
1563
|
+
await rename(temporaryPath, statePath);
|
|
1564
|
+
}
|
|
1565
|
+
function buildNearBestMetricKeys(currentMetrics, ledger, state, now) {
|
|
1566
|
+
return [
|
|
1567
|
+
"bestConcurrentAgents",
|
|
1568
|
+
"best24hRawBurn",
|
|
1569
|
+
"best24hAgentSumMs"
|
|
1570
|
+
].filter((metric) => {
|
|
1571
|
+
const bestValue = ledger[metric]?.value ?? 0;
|
|
1572
|
+
if (bestValue <= 0) {
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
const currentValue = currentMetrics[metric];
|
|
1576
|
+
if (currentValue <= 0 || currentValue >= bestValue) {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
if (currentValue / bestValue < state.thresholdRatio) {
|
|
1580
|
+
return false;
|
|
1581
|
+
}
|
|
1582
|
+
const lastNotifiedAt = state.lastNotifiedAt[metric];
|
|
1583
|
+
return lastNotifiedAt === null || now.getTime() - lastNotifiedAt.getTime() >= state.cooldownMs;
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
function buildNearBestNotification(metric, currentValue, bestValue) {
|
|
1587
|
+
return {
|
|
1588
|
+
title: metric === "bestConcurrentAgents" ? "Close to best concurrent agents" : metric === "best24hRawBurn" ? "Close to best 24hr raw burn" : "Close to best agent sum",
|
|
1589
|
+
body: metric === "bestConcurrentAgents" ? `${formatInteger2(currentValue)} of ${formatInteger2(bestValue)} concurrent agents` : metric === "best24hRawBurn" ? `${formatCompactInteger2(currentValue)} of ${formatCompactInteger2(bestValue)} 24hr raw burn` : `${formatAgentSumHours2(currentValue)} of ${formatAgentSumHours2(bestValue)} agent sum`
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function createDefaultNearBestNotificationState() {
|
|
1593
|
+
return {
|
|
1594
|
+
version: nearBestNotificationVersion,
|
|
1595
|
+
nearBestEnabled: false,
|
|
1596
|
+
thresholdRatio: 0.97,
|
|
1597
|
+
cooldownMs: 24 * 60 * 60 * 1000,
|
|
1598
|
+
lastNotifiedAt: {
|
|
1599
|
+
bestConcurrentAgents: null,
|
|
1600
|
+
best24hRawBurn: null,
|
|
1601
|
+
best24hAgentSumMs: null
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function resolveNearBestNotificationStatePath(options) {
|
|
1606
|
+
return join2(options.stateDirectory ?? join2(homedir2(), ".idletime"), nearBestNotificationStateFileName);
|
|
1607
|
+
}
|
|
1608
|
+
function readOptionalIsoTimestamp(value, label) {
|
|
1609
|
+
if (value === null || value === undefined) {
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1612
|
+
return readIsoTimestamp(value, label);
|
|
1613
|
+
}
|
|
1614
|
+
function formatAgentSumHours2(durationMs) {
|
|
1615
|
+
const hours = durationMs / 3600000;
|
|
1616
|
+
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
1617
|
+
}
|
|
1618
|
+
function formatCompactInteger2(value) {
|
|
1619
|
+
return new Intl.NumberFormat("en-US", {
|
|
1620
|
+
notation: "compact",
|
|
1621
|
+
maximumFractionDigits: 1
|
|
1622
|
+
}).format(Math.round(value)).toUpperCase();
|
|
1623
|
+
}
|
|
1624
|
+
function formatInteger2(value) {
|
|
1625
|
+
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/best-metrics/notify-best-events.ts
|
|
1629
|
+
async function notifyBestEvents(bestEvents, options = {}) {
|
|
1630
|
+
await deliverLocalNotifications(bestEvents.map((bestEvent) => buildBestEventNotification(bestEvent)), options);
|
|
1631
|
+
}
|
|
1632
|
+
function buildBestEventNotification(bestEvent) {
|
|
1633
|
+
return {
|
|
1634
|
+
title: resolveNotificationTitle(bestEvent.metric),
|
|
1635
|
+
body: resolveNotificationBody(bestEvent)
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
function resolveNotificationTitle(bestMetricKey) {
|
|
1639
|
+
return bestMetricKey === "bestConcurrentAgents" ? "New best concurrent agents" : bestMetricKey === "best24hRawBurn" ? "New best 24hr raw burn" : "New best agent sum";
|
|
1640
|
+
}
|
|
1641
|
+
function resolveNotificationBody(bestEvent) {
|
|
1642
|
+
return bestEvent.metric === "bestConcurrentAgents" ? `${formatInteger3(bestEvent.value)} concurrent agents` : bestEvent.metric === "best24hRawBurn" ? `${formatCompactInteger3(bestEvent.value)} 24hr raw burn` : `${formatAgentSumHours3(bestEvent.value)} agent sum`;
|
|
1643
|
+
}
|
|
1644
|
+
function formatAgentSumHours3(durationMs) {
|
|
1645
|
+
const hours = durationMs / 3600000;
|
|
1646
|
+
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
1647
|
+
}
|
|
1648
|
+
function formatCompactInteger3(value) {
|
|
1649
|
+
return new Intl.NumberFormat("en-US", {
|
|
1650
|
+
notation: "compact",
|
|
1651
|
+
maximumFractionDigits: 1
|
|
1652
|
+
}).format(Math.round(value)).toUpperCase();
|
|
1653
|
+
}
|
|
1654
|
+
function formatInteger3(value) {
|
|
1655
|
+
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/best-metrics/types.ts
|
|
1659
|
+
var bestMetricsLedgerVersion = 1;
|
|
1660
|
+
var rollingWindowDurationMs = 24 * 60 * 60 * 1000;
|
|
1661
|
+
var defaultBestMetricsIdleCutoffMs = 15 * 60 * 1000;
|
|
1662
|
+
|
|
1663
|
+
// src/best-metrics/build-current-best-metrics.ts
|
|
1664
|
+
function buildCurrentBestMetricValues(sessions, options = {}) {
|
|
1665
|
+
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
1666
|
+
const now = options.now ?? new Date;
|
|
1667
|
+
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs);
|
|
1668
|
+
const currentWindow = {
|
|
1669
|
+
start: new Date(now.getTime() - rollingWindowDurationMs),
|
|
1670
|
+
end: now
|
|
1671
|
+
};
|
|
1672
|
+
return {
|
|
1673
|
+
bestConcurrentAgents: countLiveSubagents(activityMetrics.perSubagentBlocks, now),
|
|
1674
|
+
best24hRawBurn: sessions.reduce((rawBurnTotal, session) => rawBurnTotal + buildTokenDeltaPoints(session.tokenPoints).filter((tokenDeltaPoint) => tokenDeltaPoint.timestamp.getTime() >= currentWindow.start.getTime() && tokenDeltaPoint.timestamp.getTime() <= currentWindow.end.getTime()).reduce((sessionTotal, tokenDeltaPoint) => sessionTotal + tokenDeltaPoint.deltaUsage.totalTokens, 0), 0),
|
|
1675
|
+
best24hAgentSumMs: measureOverlapMs(activityMetrics.perSubagentBlocks.flatMap((sessionBlocks) => sessionBlocks), currentWindow)
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function countLiveSubagents(intervalGroups, now) {
|
|
1679
|
+
return intervalGroups.reduce((liveCount, intervalGroup) => liveCount + Number(intervalGroup.some((interval) => interval.start.getTime() <= now.getTime() && interval.end.getTime() > now.getTime())), 0);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/best-metrics/append-best-events.ts
|
|
1683
|
+
import { appendFile, mkdir as mkdir2 } from "node:fs/promises";
|
|
1684
|
+
import { homedir as homedir3 } from "node:os";
|
|
1685
|
+
import { join as join3 } from "node:path";
|
|
1686
|
+
var bestEventsFileName = "best-events.ndjson";
|
|
1687
|
+
async function appendBestEvents(bestEvents, options = {}) {
|
|
1688
|
+
if (bestEvents.length === 0) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
const stateDirectory = resolveBestStateDirectory(options);
|
|
1692
|
+
await mkdir2(stateDirectory, { recursive: true });
|
|
1693
|
+
await appendFile(join3(stateDirectory, bestEventsFileName), `${bestEvents.map(serializeBestEvent).join(`
|
|
1694
|
+
`)}
|
|
1695
|
+
`, "utf8");
|
|
1696
|
+
}
|
|
1697
|
+
function serializeBestEvent(bestEvent) {
|
|
1698
|
+
return JSON.stringify({
|
|
1699
|
+
metric: bestEvent.metric,
|
|
1700
|
+
previousValue: bestEvent.previousValue,
|
|
1701
|
+
value: bestEvent.value,
|
|
1702
|
+
observedAt: bestEvent.observedAt.toISOString(),
|
|
1703
|
+
windowStart: bestEvent.windowStart.toISOString(),
|
|
1704
|
+
windowEnd: bestEvent.windowEnd.toISOString(),
|
|
1705
|
+
version: bestEvent.version
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
function resolveBestStateDirectory(options) {
|
|
1709
|
+
return options.stateDirectory ?? join3(homedir3(), ".idletime");
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/best-metrics/build-rolling-24h-windows.ts
|
|
1713
|
+
function findBestRollingWindowTotal(weightedPoints) {
|
|
1714
|
+
const sortedPoints = weightedPoints.filter((point) => point.value > 0).slice().sort((leftPoint, rightPoint) => leftPoint.timestamp.getTime() - rightPoint.timestamp.getTime());
|
|
1715
|
+
if (sortedPoints.length === 0) {
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
let bestValue = 0;
|
|
1719
|
+
let bestTimestampMs = 0;
|
|
1720
|
+
let currentTotal = 0;
|
|
1721
|
+
let leftIndex = 0;
|
|
1722
|
+
for (let rightIndex = 0;rightIndex < sortedPoints.length; rightIndex += 1) {
|
|
1723
|
+
const rightPoint = sortedPoints[rightIndex];
|
|
1724
|
+
currentTotal += rightPoint.value;
|
|
1725
|
+
while (rightPoint.timestamp.getTime() - sortedPoints[leftIndex].timestamp.getTime() > rollingWindowDurationMs) {
|
|
1726
|
+
currentTotal -= sortedPoints[leftIndex].value;
|
|
1727
|
+
leftIndex += 1;
|
|
1728
|
+
}
|
|
1729
|
+
if (currentTotal > bestValue) {
|
|
1730
|
+
bestValue = currentTotal;
|
|
1731
|
+
bestTimestampMs = rightPoint.timestamp.getTime();
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
if (bestValue === 0) {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
return createRollingRecord(bestValue, bestTimestampMs);
|
|
1738
|
+
}
|
|
1739
|
+
function findBestRollingWindowOverlap(intervals) {
|
|
1740
|
+
const slopeChanges = intervals.flatMap(buildSlopeChanges);
|
|
1741
|
+
if (slopeChanges.length === 0) {
|
|
1742
|
+
return null;
|
|
1743
|
+
}
|
|
1744
|
+
slopeChanges.sort((leftChange, rightChange) => leftChange.timestampMs - rightChange.timestampMs);
|
|
1745
|
+
let bestTimestampMs = 0;
|
|
1746
|
+
let bestValue = 0;
|
|
1747
|
+
let currentSlope = 0;
|
|
1748
|
+
let currentValue = 0;
|
|
1749
|
+
let previousTimestampMs = slopeChanges[0].timestampMs;
|
|
1750
|
+
let index = 0;
|
|
1751
|
+
while (index < slopeChanges.length) {
|
|
1752
|
+
const timestampMs = slopeChanges[index].timestampMs;
|
|
1753
|
+
currentValue += currentSlope * (timestampMs - previousTimestampMs);
|
|
1754
|
+
if (currentValue > bestValue) {
|
|
1755
|
+
bestValue = currentValue;
|
|
1756
|
+
bestTimestampMs = timestampMs;
|
|
1757
|
+
}
|
|
1758
|
+
while (index < slopeChanges.length && slopeChanges[index].timestampMs === timestampMs) {
|
|
1759
|
+
currentSlope += slopeChanges[index].deltaSlope;
|
|
1760
|
+
index += 1;
|
|
1761
|
+
}
|
|
1762
|
+
previousTimestampMs = timestampMs;
|
|
1763
|
+
}
|
|
1764
|
+
if (bestValue === 0) {
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
return createRollingRecord(bestValue, bestTimestampMs);
|
|
1768
|
+
}
|
|
1769
|
+
function buildSlopeChanges(interval) {
|
|
1770
|
+
const startMs = interval.start.getTime();
|
|
1771
|
+
const endMs = interval.end.getTime();
|
|
1772
|
+
if (endMs <= startMs) {
|
|
1773
|
+
return [];
|
|
1774
|
+
}
|
|
1775
|
+
return [
|
|
1776
|
+
{ timestampMs: startMs, deltaSlope: 1 },
|
|
1777
|
+
{ timestampMs: endMs, deltaSlope: -1 },
|
|
1778
|
+
{ timestampMs: startMs + rollingWindowDurationMs, deltaSlope: -1 },
|
|
1779
|
+
{ timestampMs: endMs + rollingWindowDurationMs, deltaSlope: 1 }
|
|
1780
|
+
];
|
|
1781
|
+
}
|
|
1782
|
+
function createRollingRecord(value, observedAtMs) {
|
|
1783
|
+
return {
|
|
1784
|
+
value,
|
|
1785
|
+
observedAt: new Date(observedAtMs),
|
|
1786
|
+
windowStart: new Date(observedAtMs - rollingWindowDurationMs),
|
|
1787
|
+
windowEnd: new Date(observedAtMs)
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// src/best-metrics/build-best-metrics.ts
|
|
1792
|
+
function buildBestMetricCandidates(sessions, options = {}) {
|
|
1793
|
+
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
1794
|
+
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs);
|
|
1795
|
+
return {
|
|
1796
|
+
bestConcurrentAgents: findBestConcurrentAgents(activityMetrics.perSubagentBlocks),
|
|
1797
|
+
best24hRawBurn: findBestRollingWindowTotal(sessions.flatMap((session) => buildTokenDeltaPoints(session.tokenPoints).map((tokenDeltaPoint) => ({
|
|
1798
|
+
timestamp: tokenDeltaPoint.timestamp,
|
|
1799
|
+
value: tokenDeltaPoint.deltaUsage.totalTokens
|
|
1800
|
+
})))),
|
|
1801
|
+
best24hAgentSumMs: findBestRollingWindowOverlap(activityMetrics.perSubagentBlocks.flatMap((sessionBlocks) => sessionBlocks))
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function findBestConcurrentAgents(intervalGroups) {
|
|
1805
|
+
const concurrencyEdges = intervalGroups.flatMap((intervalGroup) => intervalGroup.flatMap((interval) => [
|
|
1806
|
+
{ timestampMs: interval.start.getTime(), delta: 1 },
|
|
1807
|
+
{ timestampMs: interval.end.getTime(), delta: -1 }
|
|
1808
|
+
]));
|
|
1809
|
+
if (concurrencyEdges.length === 0) {
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
concurrencyEdges.sort((leftEdge, rightEdge) => leftEdge.timestampMs - rightEdge.timestampMs);
|
|
1813
|
+
let activeCount = 0;
|
|
1814
|
+
let bestRecord = null;
|
|
1815
|
+
let index = 0;
|
|
1816
|
+
while (index < concurrencyEdges.length) {
|
|
1817
|
+
const timestampMs = concurrencyEdges[index].timestampMs;
|
|
1818
|
+
while (index < concurrencyEdges.length && concurrencyEdges[index].timestampMs === timestampMs) {
|
|
1819
|
+
activeCount += concurrencyEdges[index].delta;
|
|
1820
|
+
index += 1;
|
|
1821
|
+
}
|
|
1822
|
+
const nextTimestampMs = concurrencyEdges[index]?.timestampMs ?? timestampMs;
|
|
1823
|
+
if (nextTimestampMs <= timestampMs || activeCount <= 0) {
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (!bestRecord || activeCount > bestRecord.value) {
|
|
1827
|
+
bestRecord = {
|
|
1828
|
+
value: activeCount,
|
|
1829
|
+
observedAt: new Date(timestampMs),
|
|
1830
|
+
windowStart: new Date(timestampMs),
|
|
1831
|
+
windowEnd: new Date(nextTimestampMs)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
return bestRecord;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// src/best-metrics/read-all-codex-sessions.ts
|
|
1839
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
1840
|
+
import { homedir as homedir4 } from "node:os";
|
|
1841
|
+
import { join as join4 } from "node:path";
|
|
1842
|
+
var defaultSessionRootDirectory2 = join4(homedir4(), ".codex", "sessions");
|
|
1843
|
+
async function readAllCodexSessions(options = {}) {
|
|
1844
|
+
const sessionRootDirectory = options.sessionRootDirectory ?? defaultSessionRootDirectory2;
|
|
1845
|
+
const sessionFiles = await listAllSessionFiles(sessionRootDirectory);
|
|
1846
|
+
const parsedSessionResults = await Promise.allSettled(sessionFiles.map((sessionFilePath) => parseCodexSession(sessionFilePath)));
|
|
1847
|
+
const parsedSessions = parsedSessionResults.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1848
|
+
return parsedSessions.sort((leftSession, rightSession) => leftSession.firstTimestamp.getTime() - rightSession.firstTimestamp.getTime());
|
|
1849
|
+
}
|
|
1850
|
+
async function listAllSessionFiles(rootDirectory) {
|
|
1851
|
+
const pendingDirectories = [rootDirectory];
|
|
1852
|
+
const sessionFiles = [];
|
|
1853
|
+
while (pendingDirectories.length > 0) {
|
|
1854
|
+
const currentDirectory = pendingDirectories.pop();
|
|
1855
|
+
const directoryEntries = await readDirectoryEntries2(currentDirectory);
|
|
1856
|
+
for (const directoryEntry of directoryEntries) {
|
|
1857
|
+
const entryPath = join4(currentDirectory, directoryEntry.name);
|
|
1858
|
+
if (directoryEntry.isDirectory()) {
|
|
1859
|
+
pendingDirectories.push(entryPath);
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (directoryEntry.isFile() && directoryEntry.name.endsWith(".jsonl")) {
|
|
1863
|
+
sessionFiles.push(entryPath);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return sessionFiles.sort();
|
|
1868
|
+
}
|
|
1869
|
+
async function readDirectoryEntries2(directoryPath) {
|
|
1870
|
+
try {
|
|
1871
|
+
return await readdir2(directoryPath, { withFileTypes: true });
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
if (isMissingDirectoryError2(error)) {
|
|
1874
|
+
return [];
|
|
1875
|
+
}
|
|
1876
|
+
throw error;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function isMissingDirectoryError2(error) {
|
|
1880
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/best-metrics/read-best-ledger.ts
|
|
1884
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1885
|
+
import { homedir as homedir5 } from "node:os";
|
|
1886
|
+
import { join as join5 } from "node:path";
|
|
1887
|
+
var bestLedgerFileName = "bests-v1.json";
|
|
1888
|
+
async function readBestLedger(options = {}) {
|
|
1889
|
+
try {
|
|
1890
|
+
const rawLedgerText = await readFile3(resolveBestLedgerPath(options), "utf8");
|
|
1891
|
+
return parseBestLedger(JSON.parse(rawLedgerText));
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
if (isMissingFileError(error)) {
|
|
1894
|
+
return null;
|
|
1895
|
+
}
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
function parseBestLedger(value) {
|
|
1900
|
+
const ledgerRecord = expectObject(value, "bestMetricsLedger");
|
|
1901
|
+
const version = readNumber(ledgerRecord, "version", "bestMetricsLedger");
|
|
1902
|
+
if (version !== bestMetricsLedgerVersion) {
|
|
1903
|
+
throw new Error(`bestMetricsLedger.version must be ${bestMetricsLedgerVersion}.`);
|
|
1904
|
+
}
|
|
1905
|
+
return {
|
|
1906
|
+
version: bestMetricsLedgerVersion,
|
|
1907
|
+
initializedAt: readIsoTimestamp(ledgerRecord.initializedAt, "bestMetricsLedger.initializedAt"),
|
|
1908
|
+
lastScannedAt: readIsoTimestamp(ledgerRecord.lastScannedAt, "bestMetricsLedger.lastScannedAt"),
|
|
1909
|
+
bestConcurrentAgents: parseBestMetricRecord(ledgerRecord.bestConcurrentAgents, "bestMetricsLedger.bestConcurrentAgents"),
|
|
1910
|
+
best24hRawBurn: parseBestMetricRecord(ledgerRecord.best24hRawBurn, "bestMetricsLedger.best24hRawBurn"),
|
|
1911
|
+
best24hAgentSumMs: parseBestMetricRecord(ledgerRecord.best24hAgentSumMs, "bestMetricsLedger.best24hAgentSumMs")
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
function resolveBestLedgerPath(options = {}) {
|
|
1915
|
+
return join5(resolveBestStateDirectory2(options), bestLedgerFileName);
|
|
1916
|
+
}
|
|
1917
|
+
function serializeBestLedger(ledger) {
|
|
1918
|
+
const serializedLedger = {
|
|
1919
|
+
version: ledger.version,
|
|
1920
|
+
initializedAt: ledger.initializedAt.toISOString(),
|
|
1921
|
+
lastScannedAt: ledger.lastScannedAt.toISOString(),
|
|
1922
|
+
bestConcurrentAgents: serializeBestMetricRecord(ledger.bestConcurrentAgents),
|
|
1923
|
+
best24hRawBurn: serializeBestMetricRecord(ledger.best24hRawBurn),
|
|
1924
|
+
best24hAgentSumMs: serializeBestMetricRecord(ledger.best24hAgentSumMs)
|
|
1925
|
+
};
|
|
1926
|
+
return `${JSON.stringify(serializedLedger, null, 2)}
|
|
1927
|
+
`;
|
|
1928
|
+
}
|
|
1929
|
+
function parseBestMetricRecord(value, label) {
|
|
1930
|
+
if (value === null || value === undefined) {
|
|
1931
|
+
return null;
|
|
1932
|
+
}
|
|
1933
|
+
const record = expectObject(value, label);
|
|
1934
|
+
return {
|
|
1935
|
+
value: readNumber(record, "value", label),
|
|
1936
|
+
observedAt: readIsoTimestamp(record.observedAt, `${label}.observedAt`),
|
|
1937
|
+
windowStart: readIsoTimestamp(record.windowStart, `${label}.windowStart`),
|
|
1938
|
+
windowEnd: readIsoTimestamp(record.windowEnd, `${label}.windowEnd`)
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function serializeBestMetricRecord(record) {
|
|
1942
|
+
if (!record) {
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
return {
|
|
1946
|
+
value: record.value,
|
|
1947
|
+
observedAt: record.observedAt.toISOString(),
|
|
1948
|
+
windowStart: record.windowStart.toISOString(),
|
|
1949
|
+
windowEnd: record.windowEnd.toISOString()
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
function resolveBestStateDirectory2(options) {
|
|
1953
|
+
return options.stateDirectory ?? join5(homedir5(), ".idletime");
|
|
1954
|
+
}
|
|
1955
|
+
function isMissingFileError(error) {
|
|
1956
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/best-metrics/write-best-ledger.ts
|
|
1960
|
+
import { mkdir as mkdir3, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1961
|
+
import { join as join6 } from "node:path";
|
|
1962
|
+
async function writeBestLedger(ledger, options = {}) {
|
|
1963
|
+
const ledgerPath = resolveBestLedgerPath(options);
|
|
1964
|
+
const stateDirectory = options.stateDirectory ?? ledgerPath.slice(0, ledgerPath.lastIndexOf("/"));
|
|
1965
|
+
await mkdir3(stateDirectory, { recursive: true });
|
|
1966
|
+
const temporaryPath = join6(stateDirectory, `.bests-v1.${process.pid}.${Date.now()}.tmp`);
|
|
1967
|
+
await writeFile2(temporaryPath, serializeBestLedger(ledger), "utf8");
|
|
1968
|
+
await rename2(temporaryPath, ledgerPath);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// src/best-metrics/refresh-best-metrics.ts
|
|
1972
|
+
async function refreshBestMetrics(options = {}) {
|
|
1973
|
+
const refreshedAt = options.now ?? new Date;
|
|
1974
|
+
const existingLedger = await readBestLedger(options);
|
|
1975
|
+
const sessions = await readAllCodexSessions({
|
|
1976
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
1977
|
+
});
|
|
1978
|
+
const bestMetricCandidates = buildBestMetricCandidates(sessions);
|
|
1979
|
+
const currentMetrics = buildCurrentBestMetricValues(sessions, {
|
|
1980
|
+
now: refreshedAt
|
|
1981
|
+
});
|
|
1982
|
+
if (!existingLedger) {
|
|
1983
|
+
const bootstrappedLedger = {
|
|
1984
|
+
version: bestMetricsLedgerVersion,
|
|
1985
|
+
initializedAt: refreshedAt,
|
|
1986
|
+
lastScannedAt: refreshedAt,
|
|
1987
|
+
...bestMetricCandidates
|
|
1988
|
+
};
|
|
1989
|
+
await writeBestLedger(bootstrappedLedger, options);
|
|
1990
|
+
return {
|
|
1991
|
+
currentMetrics,
|
|
1992
|
+
ledger: bootstrappedLedger,
|
|
1993
|
+
newBestEvents: [],
|
|
1994
|
+
refreshMode: "bootstrap"
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
const newBestEvents = buildNewBestEvents(existingLedger, bestMetricCandidates);
|
|
1998
|
+
const refreshedLedger = {
|
|
1999
|
+
...existingLedger,
|
|
2000
|
+
lastScannedAt: refreshedAt,
|
|
2001
|
+
...mergeBestMetricCandidates(existingLedger, bestMetricCandidates)
|
|
2002
|
+
};
|
|
2003
|
+
await writeBestLedger(refreshedLedger, options);
|
|
2004
|
+
await appendBestEvents(newBestEvents, options);
|
|
2005
|
+
return {
|
|
2006
|
+
currentMetrics,
|
|
2007
|
+
ledger: refreshedLedger,
|
|
2008
|
+
newBestEvents,
|
|
2009
|
+
refreshMode: "refresh"
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function mergeBestMetricCandidates(currentLedger, candidateLedger) {
|
|
2013
|
+
return {
|
|
2014
|
+
bestConcurrentAgents: pickBetterRecord(currentLedger.bestConcurrentAgents, candidateLedger.bestConcurrentAgents),
|
|
2015
|
+
best24hRawBurn: pickBetterRecord(currentLedger.best24hRawBurn, candidateLedger.best24hRawBurn),
|
|
2016
|
+
best24hAgentSumMs: pickBetterRecord(currentLedger.best24hAgentSumMs, candidateLedger.best24hAgentSumMs)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
function pickBetterRecord(currentRecord, candidateRecord) {
|
|
2020
|
+
if (!candidateRecord) {
|
|
2021
|
+
return currentRecord;
|
|
2022
|
+
}
|
|
2023
|
+
if (!currentRecord || candidateRecord.value > currentRecord.value) {
|
|
2024
|
+
return candidateRecord;
|
|
2025
|
+
}
|
|
2026
|
+
return currentRecord;
|
|
2027
|
+
}
|
|
2028
|
+
function buildNewBestEvents(currentLedger, candidateLedger) {
|
|
2029
|
+
return [
|
|
2030
|
+
buildNewBestEvent("bestConcurrentAgents", currentLedger.bestConcurrentAgents, candidateLedger.bestConcurrentAgents),
|
|
2031
|
+
buildNewBestEvent("best24hRawBurn", currentLedger.best24hRawBurn, candidateLedger.best24hRawBurn),
|
|
2032
|
+
buildNewBestEvent("best24hAgentSumMs", currentLedger.best24hAgentSumMs, candidateLedger.best24hAgentSumMs)
|
|
2033
|
+
].flatMap((bestEvent) => bestEvent ? [bestEvent] : []);
|
|
2034
|
+
}
|
|
2035
|
+
function buildNewBestEvent(metric, currentRecord, candidateRecord) {
|
|
2036
|
+
if (!candidateRecord || currentRecord !== null && candidateRecord.value <= currentRecord.value) {
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
return {
|
|
2040
|
+
metric,
|
|
2041
|
+
previousValue: currentRecord?.value ?? null,
|
|
2042
|
+
value: candidateRecord.value,
|
|
2043
|
+
observedAt: candidateRecord.observedAt,
|
|
2044
|
+
windowStart: candidateRecord.windowStart,
|
|
2045
|
+
windowEnd: candidateRecord.windowEnd,
|
|
2046
|
+
version: bestMetricsLedgerVersion
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
|
|
1358
2050
|
// src/reporting/build-summary-report.ts
|
|
1359
2051
|
function buildSummaryReport(sessions, query) {
|
|
1360
2052
|
const filteredSessions = filterSessions(sessions, query.filters);
|
|
@@ -1452,23 +2144,19 @@ function clipActivityMetricsToWindow(metrics, windowInterval) {
|
|
|
1452
2144
|
|
|
1453
2145
|
// src/reporting/render-summary-report.ts
|
|
1454
2146
|
var summaryBarWidth = 18;
|
|
1455
|
-
function renderSummaryReport(report, options, hourlyReport) {
|
|
1456
|
-
return options.shareMode ? renderShareSummaryReport(report, options, hourlyReport) : renderFullSummaryReport(report, options, hourlyReport);
|
|
2147
|
+
function renderSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
2148
|
+
return options.shareMode ? renderShareSummaryReport(report, options, hourlyReport, bestPlaque) : renderFullSummaryReport(report, options, hourlyReport, bestPlaque);
|
|
1457
2149
|
}
|
|
1458
|
-
function renderFullSummaryReport(report, options, hourlyReport) {
|
|
2150
|
+
function renderFullSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
1459
2151
|
const lines = [];
|
|
1460
2152
|
const requestedMetrics = report.metrics;
|
|
1461
2153
|
const actualComparisonMetrics = report.comparisonMetrics;
|
|
1462
2154
|
const windowDurationMs = report.window.end.getTime() - report.window.start.getTime();
|
|
1463
|
-
const headerLines =
|
|
1464
|
-
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1465
|
-
`${report.sessionCounts.total} sessions · ${formatDurationHours(requestedMetrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1466
|
-
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1467
|
-
];
|
|
2155
|
+
const headerLines = buildSummaryHeaderLines(report, hourlyReport);
|
|
1468
2156
|
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1469
2157
|
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1470
2158
|
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1471
|
-
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
2159
|
+
lines.push(...buildLogoSection(logoSectionWidth, options, bestPlaque));
|
|
1472
2160
|
lines.push("");
|
|
1473
2161
|
lines.push(...panelLines);
|
|
1474
2162
|
if (hourlyReport) {
|
|
@@ -1519,17 +2207,13 @@ function renderFullSummaryReport(report, options, hourlyReport) {
|
|
|
1519
2207
|
return lines.join(`
|
|
1520
2208
|
`);
|
|
1521
2209
|
}
|
|
1522
|
-
function renderShareSummaryReport(report, options, hourlyReport) {
|
|
2210
|
+
function renderShareSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
1523
2211
|
const lines = [];
|
|
1524
|
-
const headerLines =
|
|
1525
|
-
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
1526
|
-
`${report.sessionCounts.total} sessions · ${formatDurationHours(report.metrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
1527
|
-
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
1528
|
-
];
|
|
2212
|
+
const headerLines = buildSummaryHeaderLines(report, hourlyReport);
|
|
1529
2213
|
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
1530
2214
|
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
1531
2215
|
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
1532
|
-
lines.push(...buildLogoSection(logoSectionWidth, options));
|
|
2216
|
+
lines.push(...buildLogoSection(logoSectionWidth, options, bestPlaque));
|
|
1533
2217
|
lines.push("");
|
|
1534
2218
|
lines.push(...panelLines);
|
|
1535
2219
|
if (hourlyReport) {
|
|
@@ -1563,6 +2247,93 @@ function formatAppliedFilters(report) {
|
|
|
1563
2247
|
}
|
|
1564
2248
|
return appliedFilters;
|
|
1565
2249
|
}
|
|
2250
|
+
function buildSummaryHeaderLines(report, hourlyReport) {
|
|
2251
|
+
if (!hourlyReport) {
|
|
2252
|
+
return [
|
|
2253
|
+
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
2254
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(report.metrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
2255
|
+
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
2256
|
+
];
|
|
2257
|
+
}
|
|
2258
|
+
return [
|
|
2259
|
+
buildPostureLine(report, hourlyReport),
|
|
2260
|
+
buildBiggestStoryLine(report, hourlyReport),
|
|
2261
|
+
buildSupportFactsLine(report),
|
|
2262
|
+
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
2263
|
+
];
|
|
2264
|
+
}
|
|
2265
|
+
function buildPostureLine(report, hourlyReport) {
|
|
2266
|
+
const directActivityMs = Math.max(report.metrics.directActivityMs, 1);
|
|
2267
|
+
const focusRatio = report.metrics.strictEngagementMs / directActivityMs;
|
|
2268
|
+
const agentCoverageRatio = report.metrics.agentCoverageMs / directActivityMs;
|
|
2269
|
+
const quietRatio = sumQuietMs(hourlyReport) / Math.max(1, report.window.end.getTime() - report.window.start.getTime());
|
|
2270
|
+
const posture = quietRatio >= 0.45 ? "Fragmented day" : agentCoverageRatio >= 0.75 && focusRatio < 0.65 ? "Mostly orchestrating" : focusRatio >= 0.8 ? "Mostly in the loop" : report.metrics.peakConcurrentAgents >= 6 ? "Heavy agent day" : "Balanced day";
|
|
2271
|
+
return `${posture}: ${formatDurationHours(report.metrics.strictEngagementMs)} focused, ${formatDurationHours(report.metrics.agentCoverageMs)} agent live`;
|
|
2272
|
+
}
|
|
2273
|
+
function buildBiggestStoryLine(report, hourlyReport) {
|
|
2274
|
+
const longestQuietRun = findLongestQuietRun(hourlyReport);
|
|
2275
|
+
const peakBurnBucket = hourlyReport.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, hourlyReport.buckets[0]);
|
|
2276
|
+
const quietPhrase = longestQuietRun.durationMs >= 2 * 3600000 ? `long quiet stretch ${describeDayPeriod(longestQuietRun.start, report)}` : "steady rhythm overall";
|
|
2277
|
+
return `Biggest story: ${quietPhrase}, big burn ${describeDayPeriod(peakBurnBucket.start, report)}`;
|
|
2278
|
+
}
|
|
2279
|
+
function buildSupportFactsLine(report) {
|
|
2280
|
+
return `${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent • ${report.metrics.peakConcurrentAgents} peak • ${formatCompactInteger(report.tokenTotals.practicalBurn)} burn`;
|
|
2281
|
+
}
|
|
2282
|
+
function sumQuietMs(hourlyReport) {
|
|
2283
|
+
return hourlyReport.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs), 0);
|
|
2284
|
+
}
|
|
2285
|
+
function findLongestQuietRun(hourlyReport) {
|
|
2286
|
+
let longestQuietRun = {
|
|
2287
|
+
durationMs: 0,
|
|
2288
|
+
start: hourlyReport.buckets[0]?.start ?? new Date(0)
|
|
2289
|
+
};
|
|
2290
|
+
let currentStart = null;
|
|
2291
|
+
let currentDurationMs = 0;
|
|
2292
|
+
for (const bucket of hourlyReport.buckets) {
|
|
2293
|
+
const quietMs = Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs);
|
|
2294
|
+
const isQuietBucket = quietMs >= 30 * 60000;
|
|
2295
|
+
if (isQuietBucket) {
|
|
2296
|
+
currentStart ??= bucket.start;
|
|
2297
|
+
currentDurationMs += quietMs;
|
|
2298
|
+
continue;
|
|
2299
|
+
}
|
|
2300
|
+
if (currentStart && currentDurationMs > longestQuietRun.durationMs) {
|
|
2301
|
+
longestQuietRun = {
|
|
2302
|
+
durationMs: currentDurationMs,
|
|
2303
|
+
start: currentStart
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
currentStart = null;
|
|
2307
|
+
currentDurationMs = 0;
|
|
2308
|
+
}
|
|
2309
|
+
if (currentStart && currentDurationMs > longestQuietRun.durationMs) {
|
|
2310
|
+
longestQuietRun = {
|
|
2311
|
+
durationMs: currentDurationMs,
|
|
2312
|
+
start: currentStart
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
if (longestQuietRun.durationMs > 0) {
|
|
2316
|
+
return longestQuietRun;
|
|
2317
|
+
}
|
|
2318
|
+
const quietestBucket = hourlyReport.buckets.reduce((currentQuietest, bucket) => {
|
|
2319
|
+
const quietMs = Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs);
|
|
2320
|
+
return quietMs > currentQuietest.durationMs ? { durationMs: quietMs, start: bucket.start } : currentQuietest;
|
|
2321
|
+
}, longestQuietRun);
|
|
2322
|
+
return quietestBucket;
|
|
2323
|
+
}
|
|
2324
|
+
function describeDayPeriod(timestamp, report) {
|
|
2325
|
+
const hourOfDay = Number.parseInt(formatHourOfDay(timestamp, report.window), 10);
|
|
2326
|
+
if (hourOfDay >= 21 || hourOfDay < 5) {
|
|
2327
|
+
return "overnight";
|
|
2328
|
+
}
|
|
2329
|
+
if (hourOfDay < 12) {
|
|
2330
|
+
return "this morning";
|
|
2331
|
+
}
|
|
2332
|
+
if (hourOfDay < 17) {
|
|
2333
|
+
return "this afternoon";
|
|
2334
|
+
}
|
|
2335
|
+
return "this evening";
|
|
2336
|
+
}
|
|
1566
2337
|
function formatDurationLabel(durationMs) {
|
|
1567
2338
|
return `${Math.round(durationMs / 60000)}m`;
|
|
1568
2339
|
}
|
|
@@ -1576,6 +2347,9 @@ function renderSnapshotRow(label, primaryText, detailText, role, options) {
|
|
|
1576
2347
|
// src/cli/run-last24h-command.ts
|
|
1577
2348
|
async function runLast24hCommand(command) {
|
|
1578
2349
|
const window = resolveTrailingReportWindow({ durationMs: command.hourlyWindowMs });
|
|
2350
|
+
const bestMetrics = await refreshBestMetrics();
|
|
2351
|
+
await notifyBestEvents(bestMetrics.newBestEvents);
|
|
2352
|
+
await notifyNearBestMetrics(bestMetrics.currentMetrics, bestMetrics.ledger);
|
|
1579
2353
|
const sessions = await readCodexSessions({
|
|
1580
2354
|
windowStart: window.start,
|
|
1581
2355
|
windowEnd: window.end
|
|
@@ -1593,12 +2367,15 @@ async function runLast24hCommand(command) {
|
|
|
1593
2367
|
wakeWindow: command.wakeWindow,
|
|
1594
2368
|
window
|
|
1595
2369
|
});
|
|
1596
|
-
return renderSummaryReport(summaryReport, createRenderOptions(command.shareMode), hourlyReport);
|
|
2370
|
+
return renderSummaryReport(summaryReport, createRenderOptions(command.shareMode), hourlyReport, buildBestPlaque(bestMetrics.ledger));
|
|
1597
2371
|
}
|
|
1598
2372
|
|
|
1599
2373
|
// src/cli/run-today-command.ts
|
|
1600
2374
|
async function runTodayCommand(command) {
|
|
1601
2375
|
const window = resolveTodayReportWindow();
|
|
2376
|
+
const bestMetrics = await refreshBestMetrics();
|
|
2377
|
+
await notifyBestEvents(bestMetrics.newBestEvents);
|
|
2378
|
+
await notifyNearBestMetrics(bestMetrics.currentMetrics, bestMetrics.ledger);
|
|
1602
2379
|
const sessions = await readCodexSessions({
|
|
1603
2380
|
windowStart: window.start,
|
|
1604
2381
|
windowEnd: window.end
|
|
@@ -1609,7 +2386,7 @@ async function runTodayCommand(command) {
|
|
|
1609
2386
|
idleCutoffMs: command.idleCutoffMs,
|
|
1610
2387
|
wakeWindow: command.wakeWindow,
|
|
1611
2388
|
window
|
|
1612
|
-
}), createRenderOptions(command.shareMode));
|
|
2389
|
+
}), createRenderOptions(command.shareMode), undefined, buildBestPlaque(bestMetrics.ledger));
|
|
1613
2390
|
}
|
|
1614
2391
|
|
|
1615
2392
|
// src/cli/run-idletime.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idletime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Visual CLI for Codex focus, token burn, spikes, and idle time from local session logs.",
|
|
5
5
|
"author": "ParkerRex",
|
|
6
6
|
"main": "./dist/idletime.js",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"sideEffects": false,
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "bun run src/release/build-package.ts",
|
|
47
|
-
"check:release": "bun run typecheck && bun test && bun run qa && npm pack --dry-run",
|
|
47
|
+
"check:release": "bun run build && bun run typecheck && bun test && bun run qa && npm pack --dry-run",
|
|
48
48
|
"dev": "bun run src/cli/idletime-bin.ts",
|
|
49
49
|
"idletime": "bun run src/cli/idletime-bin.ts",
|
|
50
|
-
"pack:dry-run": "npm pack --dry-run",
|
|
50
|
+
"pack:dry-run": "bun run build && npm pack --dry-run",
|
|
51
51
|
"publish:dry-run": "bun run build && bun publish --dry-run --access public",
|
|
52
52
|
"prepublishOnly": "bun run check:release",
|
|
53
53
|
"qa": "bun run qa:gaps && bun run qa:journeys",
|